diff --git a/.github/workflows/android_ci.yaml.bak b/.github/workflows/android_ci.yaml.bak index 002255d159..81e132cbf8 100644 --- a/.github/workflows/android_ci.yaml.bak +++ b/.github/workflows/android_ci.yaml.bak @@ -7,7 +7,6 @@ on: paths: - ".github/workflows/mobile_ci.yaml" - "frontend/**" - - "!frontend/appflowy_tauri/**" pull_request: branches: @@ -19,8 +18,8 @@ on: env: CARGO_TERM_COLOR: always - FLUTTER_VERSION: "3.22.3" - RUST_TOOLCHAIN: "1.80.1" + FLUTTER_VERSION: "3.27.4" + RUST_TOOLCHAIN: "1.81.0" CARGO_MAKE_VERSION: "0.37.18" CLOUD_VERSION: 0.6.54-amd64 diff --git a/.github/workflows/flutter_ci.yaml b/.github/workflows/flutter_ci.yaml index 86d5b7ef30..1fc1b0e052 100644 --- a/.github/workflows/flutter_ci.yaml +++ b/.github/workflows/flutter_ci.yaml @@ -25,8 +25,8 @@ on: env: CARGO_TERM_COLOR: always - FLUTTER_VERSION: "3.22.2" - RUST_TOOLCHAIN: "1.80.1" + FLUTTER_VERSION: "3.27.4" + RUST_TOOLCHAIN: "1.81.0" CARGO_MAKE_VERSION: "0.37.18" CLOUD_VERSION: 0.6.54-amd64 diff --git a/.github/workflows/ios_ci.yaml b/.github/workflows/ios_ci.yaml index 1cdc997180..e13863f4a7 100644 --- a/.github/workflows/ios_ci.yaml +++ b/.github/workflows/ios_ci.yaml @@ -7,7 +7,6 @@ on: paths: - ".github/workflows/mobile_ci.yaml" - "frontend/**" - - "!frontend/appflowy_tauri/**" - "!frontend/appflowy_web_app/**" pull_request: @@ -16,12 +15,11 @@ on: paths: - ".github/workflows/mobile_ci.yaml" - "frontend/**" - - "!frontend/appflowy_tauri/**" - "!frontend/appflowy_web_app/**" env: - FLUTTER_VERSION: "3.22.3" - RUST_TOOLCHAIN: "1.80.1" + FLUTTER_VERSION: "3.27.4" + RUST_TOOLCHAIN: "1.81.0" concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 86ad99c6dc..a4582ffa74 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,8 +6,8 @@ on: - "*" env: - FLUTTER_VERSION: "3.22.0" - RUST_TOOLCHAIN: "1.80.1" + FLUTTER_VERSION: "3.27.4" + RUST_TOOLCHAIN: "1.81.0" jobs: create-release: @@ -338,7 +338,7 @@ jobs: - { arch: x86_64, target: x86_64-unknown-linux-gnu, - os: ubuntu-20.04, + os: ubuntu-22.04, extra-build-args: "", flutter_profile: production-linux-x86_64, } diff --git a/.github/workflows/rust_ci.yaml b/.github/workflows/rust_ci.yaml index a67fd9d6ef..36c2e82064 100644 --- a/.github/workflows/rust_ci.yaml +++ b/.github/workflows/rust_ci.yaml @@ -19,7 +19,7 @@ on: env: CARGO_TERM_COLOR: always CLOUD_VERSION: 0.8.3-amd64 - RUST_TOOLCHAIN: "1.80.1" + RUST_TOOLCHAIN: "1.81.0" jobs: ubuntu-job: diff --git a/.github/workflows/rust_coverage.yml b/.github/workflows/rust_coverage.yml index 94a80aa95a..53a5f66748 100644 --- a/.github/workflows/rust_coverage.yml +++ b/.github/workflows/rust_coverage.yml @@ -10,8 +10,8 @@ on: env: CARGO_TERM_COLOR: always - FLUTTER_VERSION: "3.22.0" - RUST_TOOLCHAIN: "1.80.1" + FLUTTER_VERSION: "3.27.4" + RUST_TOOLCHAIN: "1.81.0" jobs: tests: diff --git a/.github/workflows/tauri2_ci.yaml b/.github/workflows/tauri2_ci.yaml deleted file mode 100644 index b4b9183d01..0000000000 --- a/.github/workflows/tauri2_ci.yaml +++ /dev/null @@ -1,98 +0,0 @@ -name: Tauri-CI - -on: - pull_request: - paths: - - ".github/workflows/tauri2_ci.yaml" - - "frontend/rust-lib/**" - paths-ignore: - - "frontend/appflowy_web_app/**" - - "frontend/resources/**" - -env: - NODE_VERSION: "18.16.0" - PNPM_VERSION: "8.5.0" - RUST_TOOLCHAIN: "1.80.1" - CARGO_MAKE_VERSION: "0.36.6" - CI: true - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - tauri-build-ubuntu: - runs-on: ubuntu-20.04 - - steps: - - uses: actions/checkout@v4 - - name: Maximize build space - run: | - sudo rm -rf /usr/share/dotnet - sudo rm -rf /opt/ghc - sudo rm -rf "/usr/local/share/boost" - sudo rm -rf "$AGENT_TOOLSDIRECTORY" - sudo docker image prune --all --force - sudo rm -rf /opt/hostedtoolcache/codeQL - sudo rm -rf ${GITHUB_WORKSPACE}/.git - sudo rm -rf $ANDROID_HOME/ndk - - - name: setup node - uses: actions/setup-node@v3 - with: - node-version: ${{ env.NODE_VERSION }} - - - name: setup pnpm - uses: pnpm/action-setup@v2 - with: - version: ${{ env.PNPM_VERSION }} - - - name: Install Rust toolchain - id: rust_toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ env.RUST_TOOLCHAIN }} - override: true - profile: minimal - - - name: Node_modules cache - uses: actions/cache@v2 - with: - path: frontend/appflowy_web_app/node_modules - key: node-modules-${{ runner.os }} - - - name: install dependencies - working-directory: frontend - run: | - sudo apt-get update - sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf - - - uses: taiki-e/install-action@v2 - with: - tool: cargo-make@${{ env.CARGO_MAKE_VERSION }} - - - name: install tauri deps tools - working-directory: frontend - run: | - cargo make appflowy-tauri-deps-tools - shell: bash - - - name: install frontend dependencies - working-directory: frontend/appflowy_web_app - run: | - mkdir dist - pnpm install - cd src-tauri && cargo build - - - name: test and lint - working-directory: frontend/appflowy_web_app - run: | - pnpm run lint:tauri - - - uses: tauri-apps/tauri-action@v0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tauriScript: pnpm tauri - projectPath: frontend/appflowy_web_app - args: "--debug" diff --git a/.github/workflows/tauri_ci.yaml b/.github/workflows/tauri_ci.yaml deleted file mode 100644 index 7d9c67e25a..0000000000 --- a/.github/workflows/tauri_ci.yaml +++ /dev/null @@ -1,111 +0,0 @@ -name: Tauri-CI -on: - push: - branches: - - build/tauri - -env: - NODE_VERSION: "18.16.0" - PNPM_VERSION: "8.5.0" - RUST_TOOLCHAIN: "1.80.1" - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - tauri-build: - if: github.event.pull_request.draft != true - strategy: - fail-fast: false - matrix: - platform: [ubuntu-20.04] - - runs-on: ${{ matrix.platform }} - - env: - CI: true - steps: - - uses: actions/checkout@v4 - - - name: Maximize build space (ubuntu only) - if: matrix.platform == 'ubuntu-20.04' - run: | - sudo rm -rf /usr/share/dotnet - sudo rm -rf /opt/ghc - sudo rm -rf "/usr/local/share/boost" - sudo rm -rf "$AGENT_TOOLSDIRECTORY" - sudo docker image prune --all --force - sudo rm -rf /opt/hostedtoolcache/codeQL - sudo rm -rf ${GITHUB_WORKSPACE}/.git - sudo rm -rf $ANDROID_HOME/ndk - - - name: setup node - uses: actions/setup-node@v3 - with: - node-version: ${{ env.NODE_VERSION }} - - - name: setup pnpm - uses: pnpm/action-setup@v2 - with: - version: ${{ env.PNPM_VERSION }} - - - name: Install Rust toolchain - id: rust_toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ env.RUST_TOOLCHAIN }} - override: true - profile: minimal - - - name: Rust cache - uses: swatinem/rust-cache@v2 - with: - workspaces: "./frontend/appflowy_tauri/src-tauri -> target" - - - name: Node_modules cache - uses: actions/cache@v2 - with: - path: frontend/appflowy_tauri/node_modules - key: node-modules-${{ runner.os }} - - - name: install dependencies (windows only) - if: matrix.platform == 'windows-latest' - working-directory: frontend - run: | - cargo install --force --locked duckscript_cli - vcpkg integrate install - - - name: install dependencies (ubuntu only) - if: matrix.platform == 'ubuntu-20.04' - working-directory: frontend - run: | - sudo apt-get update - sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf - - - name: install cargo-make - working-directory: frontend - run: | - cargo install --force --locked cargo-make - cargo make appflowy-tauri-deps-tools - - - name: install frontend dependencies - working-directory: frontend/appflowy_tauri - run: | - mkdir dist - pnpm install - cargo make --cwd .. tauri_build - - - name: frontend tests and linting - working-directory: frontend/appflowy_tauri - run: | - pnpm test - pnpm test:errors - - - uses: tauri-apps/tauri-action@v0 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tauriScript: pnpm tauri - projectPath: frontend/appflowy_tauri - args: "--debug" diff --git a/.github/workflows/tauri_release.yml b/.github/workflows/tauri_release.yml deleted file mode 100644 index 4612d5c3aa..0000000000 --- a/.github/workflows/tauri_release.yml +++ /dev/null @@ -1,152 +0,0 @@ -name: Publish Tauri Release - -on: - workflow_dispatch: - inputs: - branch: - description: "The branch to release" - required: true - default: "main" - version: - description: "The version to release" - required: true - default: "0.0.0" -env: - NODE_VERSION: "18.16.0" - PNPM_VERSION: "8.5.0" - RUST_TOOLCHAIN: "1.80.1" - -jobs: - publish-tauri: - permissions: - contents: write - strategy: - fail-fast: false - matrix: - settings: - - platform: windows-latest - args: "--verbose" - target: "windows-x86_64" - - platform: macos-latest - args: "--target x86_64-apple-darwin" - target: "macos-x86_64" - - platform: ubuntu-20.04 - args: "--target x86_64-unknown-linux-gnu" - target: "linux-x86_64" - - runs-on: ${{ matrix.settings.platform }} - - env: - CI: true - PACKAGE_PREFIX: AppFlowy_Tauri-${{ github.event.inputs.version }}-${{ matrix.settings.target }} - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.inputs.branch }} - - - name: Maximize build space (ubuntu only) - if: matrix.settings.platform == 'ubuntu-20.04' - run: | - sudo rm -rf /usr/share/dotnet - sudo rm -rf /opt/ghc - sudo rm -rf "/usr/local/share/boost" - sudo rm -rf "$AGENT_TOOLSDIRECTORY" - sudo docker image prune --all --force - sudo rm -rf /opt/hostedtoolcache/codeQL - sudo rm -rf ${GITHUB_WORKSPACE}/.git - sudo rm -rf $ANDROID_HOME/ndk - - - name: setup node - uses: actions/setup-node@v3 - with: - node-version: ${{ env.NODE_VERSION }} - - - name: setup pnpm - uses: pnpm/action-setup@v2 - with: - version: ${{ env.PNPM_VERSION }} - - - name: Install Rust toolchain - id: rust_toolchain - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ env.RUST_TOOLCHAIN }} - override: true - profile: minimal - - - name: Rust cache - uses: swatinem/rust-cache@v2 - with: - workspaces: "./frontend/appflowy_tauri/src-tauri -> target" - - - name: install dependencies (windows only) - if: matrix.settings.platform == 'windows-latest' - working-directory: frontend - run: | - cargo install --force --locked duckscript_cli - vcpkg integrate install - - - name: install dependencies (ubuntu only) - if: matrix.settings.platform == 'ubuntu-20.04' - working-directory: frontend - run: | - sudo apt-get update - sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf - - - name: install cargo-make - working-directory: frontend - run: | - cargo install --force --locked cargo-make - cargo make appflowy-tauri-deps-tools - - - name: install frontend dependencies - working-directory: frontend/appflowy_tauri - run: | - mkdir dist - pnpm install - pnpm exec node scripts/update_version.cjs ${{ github.event.inputs.version }} - cargo make --cwd .. tauri_build - - - uses: tauri-apps/tauri-action@dev - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - APPLE_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} - APPLE_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PWD }} - APPLE_SIGNING_IDENTITY: ${{ secrets.MACOS_TEAM_ID }} - APPLE_ID: ${{ secrets.MACOS_NOTARY_USER }} - APPLE_TEAM_ID: ${{ secrets.MACOS_TEAM_ID }} - APPLE_PASSWORD: ${{ secrets.MACOS_NOTARY_PWD }} - CI: true - with: - args: ${{ matrix.settings.args }} - appVersion: ${{ github.event.inputs.version }} - tauriScript: pnpm tauri - projectPath: frontend/appflowy_tauri - - - name: Upload EXE package(windows only) - uses: actions/upload-artifact@v4 - if: matrix.settings.platform == 'windows-latest' - with: - name: ${{ env.PACKAGE_PREFIX }}.exe - path: frontend/appflowy_tauri/src-tauri/target/release/bundle/nsis/AppFlowy_${{ github.event.inputs.version }}_x64-setup.exe - - - name: Upload DMG package(macos only) - uses: actions/upload-artifact@v4 - if: matrix.settings.platform == 'macos-latest' - with: - name: ${{ env.PACKAGE_PREFIX }}.dmg - path: frontend/appflowy_tauri/src-tauri/target/x86_64-apple-darwin/release/bundle/dmg/AppFlowy_${{ github.event.inputs.version }}_x64.dmg - - - name: Upload Deb package(ubuntu only) - uses: actions/upload-artifact@v4 - if: matrix.settings.platform == 'ubuntu-20.04' - with: - name: ${{ env.PACKAGE_PREFIX }}.deb - path: frontend/appflowy_tauri/src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/deb/app-flowy_${{ github.event.inputs.version }}_amd64.deb - - - name: Upload AppImage package(ubuntu only) - uses: actions/upload-artifact@v4 - if: matrix.settings.platform == 'ubuntu-20.04' - with: - name: ${{ env.PACKAGE_PREFIX }}.AppImage - path: frontend/appflowy_tauri/src-tauri/target/x86_64-unknown-linux-gnu/release/bundle/appimage/app-flowy_${{ github.event.inputs.version }}_amd64.AppImage diff --git a/.github/workflows/web2_ci.yaml b/.github/workflows/web2_ci.yaml deleted file mode 100644 index c52f71dd84..0000000000 --- a/.github/workflows/web2_ci.yaml +++ /dev/null @@ -1,75 +0,0 @@ -name: Web-CI -on: - pull_request: - paths: - - ".github/workflows/web2_ci.yaml" - - "frontend/appflowy_web_app/**" - - "frontend/resources/**" -env: - NODE_VERSION: "18.16.0" - PNPM_VERSION: "8.5.0" -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true -jobs: - web-build: - if: github.event.pull_request.draft != true - strategy: - fail-fast: false - matrix: - platform: [ ubuntu-20.04 ] - - runs-on: ${{ matrix.platform }} - - steps: - - uses: actions/checkout@v4 - - name: Maximize build space (ubuntu only) - if: matrix.platform == 'ubuntu-20.04' - run: | - sudo rm -rf /usr/share/dotnet - sudo rm -rf /opt/ghc - sudo rm -rf "/usr/local/share/boost" - sudo rm -rf "$AGENT_TOOLSDIRECTORY" - sudo docker image prune --all --force - sudo rm -rf /opt/hostedtoolcache/codeQL - sudo rm -rf ${GITHUB_WORKSPACE}/.git - sudo rm -rf $ANDROID_HOME/ndk - - name: setup node - uses: actions/setup-node@v3 - with: - node-version: ${{ env.NODE_VERSION }} - - name: setup pnpm - uses: pnpm/action-setup@v2 - with: - version: ${{ env.PNPM_VERSION }} - - name: Node_modules cache - uses: actions/cache@v2 - with: - path: frontend/appflowy_web_app/node_modules - key: node-modules-${{ runner.os }} - - - name: install frontend dependencies - working-directory: frontend/appflowy_web_app - run: | - pnpm install - - name: Run lint check - working-directory: frontend/appflowy_web_app - run: | - pnpm run lint - - - name: build and analyze - working-directory: frontend/appflowy_web_app - run: | - pnpm run analyze >> analyze-size.txt - - name: Upload analyze-size.txt - uses: actions/upload-artifact@v4 - with: - name: analyze-size.txt - path: frontend/appflowy_web_app/analyze-size.txt - retention-days: 30 - - name: Upload stats.html - uses: actions/upload-artifact@v4 - with: - name: stats.html - path: frontend/appflowy_web_app/dist/stats.html - retention-days: 30 diff --git a/.github/workflows/web_coverage.yaml b/.github/workflows/web_coverage.yaml deleted file mode 100644 index 7803f719c9..0000000000 --- a/.github/workflows/web_coverage.yaml +++ /dev/null @@ -1,65 +0,0 @@ -name: Web Code Coverage - -on: - pull_request: - paths: - - ".github/workflows/web2_ci.yaml" - - "frontend/appflowy_web_app/**" - - "frontend/resources/**" - -env: - NODE_VERSION: "18.16.0" - PNPM_VERSION: "8.5.0" -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true -jobs: - test: - if: github.event.pull_request.draft != true - runs-on: ubuntu-22.04 - steps: - - uses: actions/checkout@v4 - - name: Maximize build space (ubuntu only) - run: | - sudo rm -rf /usr/share/dotnet - sudo rm -rf /opt/ghc - sudo rm -rf "/usr/local/share/boost" - sudo rm -rf "$AGENT_TOOLSDIRECTORY" - sudo docker image prune --all --force - sudo rm -rf /opt/hostedtoolcache/codeQL - sudo rm -rf ${GITHUB_WORKSPACE}/.git - sudo rm -rf $ANDROID_HOME/ndk - - name: setup node - uses: actions/setup-node@v3 - with: - node-version: ${{ env.NODE_VERSION }} - - name: setup pnpm - uses: pnpm/action-setup@v2 - with: - version: ${{ env.PNPM_VERSION }} - # Install pnpm dependencies, cache them correctly - # and run all Cypress tests - - name: Cypress run - uses: cypress-io/github-action@v6 - with: - working-directory: frontend/appflowy_web_app - component: true - build: pnpm run build - start: pnpm run start - browser: chrome - - - name: Jest run - working-directory: frontend/appflowy_web_app - run: | - pnpm run test:unit - - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2 - with: - token: cf9245e0-e136-4e21-b0ee-35755fa0c493 - files: frontend/appflowy_web_app/coverage/jest/lcov.info,frontend/appflowy_web_app/coverage/cypress/lcov.info - flags: appflowy_web_app - name: frontend/appflowy_web_app - fail_ci_if_error: true - verbose: true - diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e4f1e5931..a5e7e268a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,143 @@ # Release Notes -## Version 0.7.9 - 25/12/2024 -### New Features +## Version 0.8.9 - 16/04/2025 +### Desktop +#### New Features +- Supported pasting a link as a mention, providing a more condensed visualization of linked content +- Supported converting between link formats (e.g. transforming a mention into a bookmark) +- Improved the link editing experience with enhanced UX +- Added OTP (One-Time Password) support for sign-in authentication +- Added latest AI models: GPT-4.1, GPT-4.1-mini, and Claude 3.7 Sonnet +#### Bug Fixes +- Fixed an issue where properties were not displaying in the row detail page +- Fixed a bug where Undo didn't work in the row detail page +- Fixed an issue where blocks didn't grow when the grid got bigger +- Fixed several bugs related to AI writers +### Mobile +#### New Features +- Added sign-in with OTP (One-Time Password) +#### Bug Fixes +- Fixed an issue where the slash menu sometimes failed to display +- Updated the mention page block to handle page selection with more context. +## Version 0.8.8 - 01/04/2025 +### New Features +- Added support for selecting AI models in AI writer +- Revamped link menu in toolbar +- Added support for using ":" to add emojis in documents +- Passed the history of past AI prompts and responses to AI writer ### Bug Fixes +- Improved AI writer scrolling user experience +- Fixed issue where checklist items would disappear during reordering +- Fixed numbered lists generated by AI to maintain the same index as the input + +## Version 0.8.7 - 18/03/2025 +### New Features +- Made local AI free and integrated with Ollama +- Supported nested lists within callout and quote blocks +- Revamped the document's floating toolbar and added Turn Into +- Enabled custom icons in callout blocks +### Bug Fixes +- Fixed occasional incorrect positioning of the slash menu +- Improved AI Chat and AI Writers with various bug fixes +- Adjusted the columns block to match the width of the editor +- Fixed a potential segfault caused by infinite recursion in the trash view +- Resolved an issue where the first added cover might be invisible +- Fixed adding cover images via Unsplash + +## Version 0.8.6 - 06/03/2025 +### Bug Fixes +- Fix the incorrect title positioning when adjusting the document width setting +- Enhance the user experience of the icon color picker for smoother interactions +- Add missing icons to the database to ensure completeness and consistency +- Resolve the issue with links not functioning correctly on Linux systems +- Improve the outline feature to work seamlessly within columns +- Center the bulleted list icon within columns for better visual alignment +- Enable dragging blocks under tables in the second column to enhance flexibility +- Disable the AI writer feature within tables to prevent conflicts and improve usability +- Automatically enable the header row when converting content from Markdown to ensure proper formatting +- Use the "Undo" function to revert the auto-formatting + +## Version 0.8.5 - 04/03/2025 +### New Features +- Columns in Documents: Arrange content side by side using drag-and-drop or the slash menu +- AI Writers: New AI assistants in documents with response formatting options (list, table, text with images, image-only), follow-up questions, contextual memory, and more +- Compact Mode for Databases: Enable compact mode for grid and kanban views (full-page and inline) to increase information density, displaying more data per screen +### Bug Fixes +- Fixed an issue where callout blocks couldn’t be deleted when appearing as the first line in a document +- Fixed a bug preventing the relation field in databases from opening +- Fixed an issue where links in documents were unclickable on Linux + +## Version 0.8.4 - 18/02/2025 +### New Features +- Switch AI mode on mobile +- Support locking page +- Support uploading svg file as icon +- Support the slash, at, and plus menus on mobile +### Bug Fixes +- Gallery not rendering in row page +- Save image should not copy the image (mobile) +- Support exporting more content to markdown + +## Version 0.8.2 - 23/01/2025 +### New Features +- Customized database view icons +- Support for uploading images as custom icons +- Enabled selecting multiple AI messages to save into a document +- Added the ability to scale the app's display size on mobile +- Support for pasting image links without file extensions +### Bug Fixes +- Fixed an issue where pasting tables from other apps wasn't working +- Fixed homepage URL issues in Settings +- Fixed an issue where the 'Cancel' button was not visible on the Shortcuts page + +## Version 0.8.1 - 14/01/2025 +### New Features +- AI Chat Layout Options: Customize how AI responses appear with new layouts—List, Table, Image with Text, and Media Only +- DALL-E Integration: Generate stunning AI images from text prompts, now available in AI Chat +- Improved Desktop Search: Find what you need faster using keywords or by asking questions in natural language +- Self-Hosting: Configure web server URLs directly in Settings to enable features like Publish, Copy Link to Share, Custom URLs, and more +- Sidebar Enhancement: Drag to reorder your favorited pages in the Sidebar +- Mobile Table Resizing: Adjust column widths in Simple Tables by long pressing the column borders on mobile +### Bug Fixes +- Resolved an icon rendering issue in callout blocks, tab bars, and search results +- Enhanced image reliability: Retry functionality ensures images load successfully if the first attempt fails + +## Version 0.8.0 - 06/01/2025 +### Bug Fixes +- Fixed error displaying in the page style menu +- Fixed filter logic in the icon picker +- Fixed error displaying in the Favorite/Recent page +- Fixed the color picker displaying when tapping down +- Fixed icons not being supported in subpage blocks +- Fixed recent icon functionality in the space icon menu +- Fixed "Insert Below" not auto-scrolling the table +- Fixed a to-do item with an emoji automatically creating a soft break +- Fixed header row/column tap areas being too small +- Fixed simple table alignment not working for items that wrap +- Fixed web content reverting after removing the inline code format on desktop +- Fixed inability to make changes to a row or column in the table when opening a new tab +- Fixed changing the language to CKB-KU causing a gray screen on mobile + +## Version 0.7.9 - 30/12/2024 +### New Features +- Meet AppFlowy Web (Lite): Use AppFlowy directly in your browser. + - Create beautiful documents with 22 content types and markdown support + - Use Quick Note to save anything you want to remember—like meeting notes, a grocery list, or to-dos + - Invite members to your workspace for seamless collaboration + - Create multiple public/private spaces to better organize your content +- Simple Table is now available on Mobile, designed specifically for mobile devices. + - Create and manage Simple Table blocks on Mobile with easy-to-use action menus. + - Use the '+' button in the fixed toolbar to easily add a content block into a table cell on Mobile + - Use '/' to insert a content block into a table cell on Desktop +- Add pages as AI sources in AI chat, enabling you to ask questions about the selected sources +- Add messages to an editable document while chatting with AI side by side +- The new Emoji menu now includes Icons with a Recent section for quickly reusing emojis/icons +- Drag a page from the sidebar into a document to easily mention the page without typing its title +- Paste as plain text, a new option in the right-click paste menu +### Bug Fixes +- Fixed misalignment in numbered lists +- Resolved several bugs in the emoji menu +- Fixed a bug with checklist items ## Version 0.7.8 - 18/12/2024 ### New Features diff --git a/README.md b/README.md index b9606b8844..565908e756 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- AppFlowy.IO
+ AppFlowy
⭐️ The Open Source Alternative To Notion ⭐️

@@ -18,18 +18,18 @@ AppFlowy is the AI workspace where you achieve more without losing control of yo

- Website • + WebsiteForumDiscordRedditTwitter

-

AppFlowy Kanban Board for To-dos

-

AppFlowy Databases for Tasks and Projects

-

AppFlowy Sites for Beautiful documentation

-

AppFlowy AI

-

AppFlowy Templates

+

AppFlowy Kanban Board for To-dos

+

AppFlowy Databases for Tasks and Projects

+

AppFlowy Sites for Beautiful documentation

+

AppFlowy AI

+

AppFlowy Templates



@@ -48,7 +48,7 @@ AppFlowy is the AI workspace where you achieve more without losing control of yo - [App Store](https://apps.apple.com/app/appflowy/id6457261352): iPhone - [Play Store](https://play.google.com/store/apps/details?id=io.appflowy.appflowy): Android 10 or above; ARMv7 is not supported -- [Self-hosting AppFlowy](https://docs.appflowy.io/docs/guides/appflowy/self-hosting-appflowy) +- [Self-hosting AppFlowy](https://appflowy.com/docs/self-host-appflowy-overview) - [Source](https://docs.appflowy.io/docs/documentation/appflowy/from-source) ## Built With @@ -78,7 +78,7 @@ report [here](https://github.com/AppFlowy-IO/AppFlowy/issues/new?assignees=&labe ## **Releases** -Please see the [changelog](https://www.appflowy.io/whatsnew) for more details about a given release. +Please see the [changelog](https://appflowy.com/what-is-new) for more details about a given release. ## Contributing @@ -89,9 +89,7 @@ for details. If your Pull Request is accepted as it fixes a bug, adds functionality, or makes AppFlowy's codebase significantly easier to use or understand, **Congratulations!** If your administrative and managerial work behind the scenes sustains -the community, **Congratulations!** You are now an official contributor to AppFlowy. Get in touch with -us ([link](https://tally.so/r/mKP5z3)) to receive the very special Contributor T-shirt! -Proudly wear your T-shirt and show it to us by tagging [@appflowy](https://twitter.com/appflowy) on Twitter. +the community, **Congratulations!** You are now an official contributor to AppFlowy. ## Translations 🌎🗺 @@ -152,8 +150,8 @@ more information. ## Acknowledgments -Special thanks to these amazing projects which help power AppFlowy.IO: +Special thanks to these amazing projects which help power AppFlowy: - [cargo-make](https://github.com/sagiegurari/cargo-make) - [contrib.rocks](https://contrib.rocks) -- [flutter_chat_ui](https://pub.dev/packages/flutter_chat_ui) \ No newline at end of file +- [flutter_chat_ui](https://pub.dev/packages/flutter_chat_ui) diff --git a/codemagic.yaml b/codemagic.yaml index b8934d8be8..9ba2a1a562 100644 --- a/codemagic.yaml +++ b/codemagic.yaml @@ -4,7 +4,7 @@ workflows: instance_type: mac_mini_m2 max_build_duration: 30 environment: - flutter: 3.22.3 + flutter: 3.27.4 xcode: latest cocoapods: default diff --git a/frontend/.vscode/launch.json b/frontend/.vscode/launch.json index 09965baee1..d4ff85a2dd 100644 --- a/frontend/.vscode/launch.json +++ b/frontend/.vscode/launch.json @@ -1,140 +1,125 @@ { - // 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": [ - { - // This task only builds the Dart code of AppFlowy. - // It supports both the desktop and mobile version. - "name": "AF: Build Dart Only", - "request": "launch", - "program": "./lib/main.dart", - "type": "dart", - "env": { - "RUST_LOG": "debug", - }, - // uncomment the following line to testing performance. - // "flutterMode": "profile", - "cwd": "${workspaceRoot}/appflowy_flutter" - }, - { - // This task builds the Rust and Dart code of AppFlowy. - "name": "AF-desktop: Build All", - "request": "launch", - "program": "./lib/main.dart", - "type": "dart", - "preLaunchTask": "AF: Build Appflowy Core", - "env": { - "RUST_LOG": "trace", - "RUST_BACKTRACE": "1" - }, - "cwd": "${workspaceRoot}/appflowy_flutter" - }, - { - // 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-desktop: Clean + Rebuild All", - "request": "launch", - "program": "./lib/main.dart", - "type": "dart", - "preLaunchTask": "AF: Clean + Rebuild All", - "env": { - "RUST_LOG": "trace" - }, - "cwd": "${workspaceRoot}/appflowy_flutter" - }, - { - "name": "AF-iOS: Build All", - "request": "launch", - "program": "./lib/main.dart", - "type": "dart", - "preLaunchTask": "AF: Build Appflowy Core For iOS", - "env": { - "RUST_LOG": "trace" - }, - "cwd": "${workspaceRoot}/appflowy_flutter" - }, - { - "name": "AF-iOS: Clean + Rebuild All", - "request": "launch", - "program": "./lib/main.dart", - "type": "dart", - "preLaunchTask": "AF: Clean + Rebuild All (iOS)", - "env": { - "RUST_LOG": "trace" - }, - "cwd": "${workspaceRoot}/appflowy_flutter" - }, - { - "name": "AF-iOS-Simulator: Build All", - "request": "launch", - "program": "./lib/main.dart", - "type": "dart", - "preLaunchTask": "AF: Build Appflowy Core For iOS Simulator", - "env": { - "RUST_LOG": "trace" - }, - "cwd": "${workspaceRoot}/appflowy_flutter" - }, - { - "name": "AF-iOS-Simulator: Clean + Rebuild All", - "request": "launch", - "program": "./lib/main.dart", - "type": "dart", - "preLaunchTask": "AF: Clean + Rebuild All (iOS Simulator)", - "env": { - "RUST_LOG": "trace" - }, - "cwd": "${workspaceRoot}/appflowy_flutter" - }, - { - "name": "AF-Android: Build All", - "request": "launch", - "program": "./lib/main.dart", - "type": "dart", - "preLaunchTask": "AF: Build Appflowy Core For Android", - "env": { - "RUST_LOG": "trace" - }, - "cwd": "${workspaceRoot}/appflowy_flutter" - }, - { - "name": "AF-Android: Clean + Rebuild All", - "request": "launch", - "program": "./lib/main.dart", - "type": "dart", - "preLaunchTask": "AF: Clean + Rebuild All (Android)", - "env": { - "RUST_LOG": "trace" - }, - "cwd": "${workspaceRoot}/appflowy_flutter" - }, - { - "name": "AF-desktop: Debug Rust", - "type": "lldb", - "request": "attach", - "pid": "${command:pickMyProcess}" - // To launch the application directly, use the following configuration: - // "request": "launch", - // "program": "[YOUR_APPLICATION_PATH]", - }, - { - // https://tauri.app/v1/guides/debugging/vs-code - "type": "lldb", - "request": "launch", - "name": "AF-tauri: Debug backend", - "cargo": { - "args": [ - "build", - "--manifest-path=./appflowy_tauri/src-tauri/Cargo.toml", - "--no-default-features" - ] - }, - "preLaunchTask": "AF: Tauri UI Dev", - "cwd": "${workspaceRoot}/appflowy_tauri/" - }, - ] + // 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": [ + { + // This task only builds the Dart code of AppFlowy. + // It supports both the desktop and mobile version. + "name": "AF: Build Dart Only", + "request": "launch", + "program": "./lib/main.dart", + "type": "dart", + "env": { + "RUST_LOG": "debug", + }, + // uncomment the following line to testing performance. + // "flutterMode": "profile", + "cwd": "${workspaceRoot}/appflowy_flutter" + }, + { + // This task builds the Rust and Dart code of AppFlowy. + "name": "AF-desktop: Build All", + "request": "launch", + "program": "./lib/main.dart", + "type": "dart", + "preLaunchTask": "AF: Build Appflowy Core", + "env": { + "RUST_LOG": "trace", + "RUST_BACKTRACE": "1" + }, + "cwd": "${workspaceRoot}/appflowy_flutter" + }, + { + // 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-desktop: Clean + Rebuild All", + "request": "launch", + "program": "./lib/main.dart", + "type": "dart", + "preLaunchTask": "AF: Clean + Rebuild All", + "env": { + "RUST_LOG": "trace" + }, + "cwd": "${workspaceRoot}/appflowy_flutter" + }, + { + "name": "AF-iOS: Build All", + "request": "launch", + "program": "./lib/main.dart", + "type": "dart", + "preLaunchTask": "AF: Build Appflowy Core For iOS", + "env": { + "RUST_LOG": "trace" + }, + "cwd": "${workspaceRoot}/appflowy_flutter" + }, + { + "name": "AF-iOS: Clean + Rebuild All", + "request": "launch", + "program": "./lib/main.dart", + "type": "dart", + "preLaunchTask": "AF: Clean + Rebuild All (iOS)", + "env": { + "RUST_LOG": "trace" + }, + "cwd": "${workspaceRoot}/appflowy_flutter" + }, + { + "name": "AF-iOS-Simulator: Build All", + "request": "launch", + "program": "./lib/main.dart", + "type": "dart", + "preLaunchTask": "AF: Build Appflowy Core For iOS Simulator", + "env": { + "RUST_LOG": "trace" + }, + "cwd": "${workspaceRoot}/appflowy_flutter" + }, + { + "name": "AF-iOS-Simulator: Clean + Rebuild All", + "request": "launch", + "program": "./lib/main.dart", + "type": "dart", + "preLaunchTask": "AF: Clean + Rebuild All (iOS Simulator)", + "env": { + "RUST_LOG": "trace" + }, + "cwd": "${workspaceRoot}/appflowy_flutter" + }, + { + "name": "AF-Android: Build All", + "request": "launch", + "program": "./lib/main.dart", + "type": "dart", + "preLaunchTask": "AF: Build Appflowy Core For Android", + "env": { + "RUST_LOG": "trace" + }, + "cwd": "${workspaceRoot}/appflowy_flutter" + }, + { + "name": "AF-Android: Clean + Rebuild All", + "request": "launch", + "program": "./lib/main.dart", + "type": "dart", + "preLaunchTask": "AF: Clean + Rebuild All (Android)", + "env": { + "RUST_LOG": "trace" + }, + "cwd": "${workspaceRoot}/appflowy_flutter" + }, + { + "name": "AF-desktop: Debug Rust", + "type": "lldb", + "request": "attach", + "pid": "${command:pickMyProcess}" + // To launch the application directly, use the following configuration: + // "request": "launch", + // "program": "[YOUR_APPLICATION_PATH]", + }, + ] } diff --git a/frontend/.vscode/tasks.json b/frontend/.vscode/tasks.json index d940eef0a8..0be167fb12 100644 --- a/frontend/.vscode/tasks.json +++ b/frontend/.vscode/tasks.json @@ -245,51 +245,6 @@ "problemMatcher": [], "detail": "appflowy_flutter" }, - { - "label": "AF: Tauri UI Build", - "type": "shell", - "command": "pnpm run build", - "options": { - "cwd": "${workspaceFolder}/appflowy_tauri" - } - }, - { - "label": "AF: Tauri UI Dev", - "type": "shell", - "isBackground": true, - "command": "pnpm sync:i18n && pnpm run dev", - "options": { - "cwd": "${workspaceFolder}/appflowy_tauri" - } - }, - { - "label": "AF: Tauri Clean", - "type": "shell", - "command": "cargo make tauri_clean", - "options": { - "cwd": "${workspaceFolder}" - } - }, - { - "label": "AF: Tauri Clean + Dev", - "type": "shell", - "dependsOrder": "sequence", - "dependsOn": [ - "AF: Tauri Clean", - "AF: Tauri UI Dev" - ], - "options": { - "cwd": "${workspaceFolder}" - } - }, - { - "label": "AF: Tauri ESLint", - "type": "shell", - "command": "npx eslint --fix src", - "options": { - "cwd": "${workspaceFolder}/appflowy_tauri" - } - }, { "label": "AF: Generate Env File", "type": "shell", diff --git a/frontend/Makefile.toml b/frontend/Makefile.toml index bcf4baaeea..41fdffb1af 100644 --- a/frontend/Makefile.toml +++ b/frontend/Makefile.toml @@ -26,7 +26,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true CARGO_MAKE_CRATE_FS_NAME = "dart_ffi" CARGO_MAKE_CRATE_NAME = "dart-ffi" LIB_NAME = "dart_ffi" -APPFLOWY_VERSION = "0.7.9" +APPFLOWY_VERSION = "0.8.9" FLUTTER_DESKTOP_FEATURES = "dart" PRODUCT_NAME = "AppFlowy" MACOSX_DEPLOYMENT_TARGET = "11.0" diff --git a/frontend/appflowy_flutter/analysis_options.yaml b/frontend/appflowy_flutter/analysis_options.yaml index 8da401ef26..4579b2d8c5 100644 --- a/frontend/appflowy_flutter/analysis_options.yaml +++ b/frontend/appflowy_flutter/analysis_options.yaml @@ -4,6 +4,7 @@ analyzer: exclude: - "**/*.g.dart" - "**/*.freezed.dart" + - "packages/**/*.dart" linter: rules: diff --git a/frontend/appflowy_flutter/android/app/build.gradle b/frontend/appflowy_flutter/android/app/build.gradle index 3110b5b8ff..0b96e32472 100644 --- a/frontend/appflowy_flutter/android/app/build.gradle +++ b/frontend/appflowy_flutter/android/app/build.gradle @@ -53,7 +53,7 @@ android { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "io.appflowy.appflowy" minSdkVersion 29 - targetSdkVersion 34 + targetSdkVersion 35 versionCode flutterVersionCode.toInteger() versionName flutterVersionName multiDexEnabled true diff --git a/frontend/appflowy_flutter/android/app/src/main/AndroidManifest.xml b/frontend/appflowy_flutter/android/app/src/main/AndroidManifest.xml index 74d5c5e494..f746eeb610 100644 --- a/frontend/appflowy_flutter/android/app/src/main/AndroidManifest.xml +++ b/frontend/appflowy_flutter/android/app/src/main/AndroidManifest.xml @@ -43,6 +43,8 @@ This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> + diff --git a/frontend/appflowy_flutter/assets/fonts/.gitkeep b/frontend/appflowy_flutter/assets/fonts/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/appflowy_flutter/assets/fonts/FlowyIconData.ttf b/frontend/appflowy_flutter/assets/fonts/FlowyIconData.ttf deleted file mode 100644 index 8f03a5c8f9..0000000000 Binary files a/frontend/appflowy_flutter/assets/fonts/FlowyIconData.ttf and /dev/null differ diff --git a/frontend/appflowy_flutter/assets/test/images/sample.svg b/frontend/appflowy_flutter/assets/test/images/sample.svg new file mode 100644 index 0000000000..7dcd6907d8 --- /dev/null +++ b/frontend/appflowy_flutter/assets/test/images/sample.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/frontend/appflowy_flutter/assets/translations/mr-IN.json b/frontend/appflowy_flutter/assets/translations/mr-IN.json new file mode 100644 index 0000000000..f86a1e0081 --- /dev/null +++ b/frontend/appflowy_flutter/assets/translations/mr-IN.json @@ -0,0 +1,3210 @@ +{ + "appName": "AppFlowy", + "defaultUsername": "मी", + "welcomeText": "@:appName मध्ये आ पले स्वागत आहे.", + "welcomeTo": "मध्ये आ पले स्वागत आ हे", + "githubStarText": "GitHub वर स्टार करा", + "subscribeNewsletterText": "वृत्तपत्राची सदस्यता घ्या", + "letsGoButtonText": "क्विक स्टार्ट", + "title": "Title", + "youCanAlso": "तुम्ही देखील", + "and": "आ णि", + "failedToOpenUrl": "URL उघडण्यात अयशस्वी: {}", + "blockActions": { + "addBelowTooltip": "खाली जोडण्यासाठी क्लिक करा", + "addAboveCmd": "Alt+click", + "addAboveMacCmd": "Option+click", + "addAboveTooltip": "वर जोडण्यासाठी", + "dragTooltip": "Drag to move", + "openMenuTooltip": "मेनू उघडण्यासाठी क्लिक करा" + }, + "signUp": { + "buttonText": "साइन अप", + "title": "साइन अप to @:appName", + "getStartedText": "सुरुवात करा", + "emptyPasswordError": "पासवर्ड रिकामा असू शकत नाही", + "repeatPasswordEmptyError": "Repeat पासवर्ड रिकामा असू शकत नाही", + "unmatchedPasswordError": "पुन्हा लिहिलेला पासवर्ड मूळ पासवर्डशी जुळत नाही", + "alreadyHaveAnAccount": "आधीच खाते आहे?", + "emailHint": "Email", + "passwordHint": "Password", + "repeatPasswordHint": "पासवर्ड पुन्हा लिहा", + "signUpWith": "यामध्ये साइन अप करा:" + }, + "signIn": { + "loginTitle": "@:appName मध्ये लॉगिन करा", + "loginButtonText": "लॉगिन", + "loginStartWithAnonymous": "अनामिक सत्रासह पुढे जा", + "continueAnonymousUser": "अनामिक सत्रासह पुढे जा", + "anonymous": "अनामिक", + "buttonText": "साइन इन", + "signingInText": "साइन इन होत आहे...", + "forgotPassword": "पासवर्ड विसरलात?", + "emailHint": "ईमेल", + "passwordHint": "पासवर्ड", + "dontHaveAnAccount": "तुमचं खाते नाही?", + "createAccount": "खाते तयार करा", + "repeatPasswordEmptyError": "पुन्हा पासवर्ड रिकामा असू शकत नाही", + "unmatchedPasswordError": "पुन्हा लिहिलेला पासवर्ड मूळ पासवर्डशी जुळत नाही", + "syncPromptMessage": "डेटा सिंक होण्यास थोडा वेळ लागू शकतो. कृपया हे पृष्ठ बंद करू नका", + "or": "किंवा", + "signInWithGoogle": "Google सह पुढे जा", + "signInWithGithub": "GitHub सह पुढे जा", + "signInWithDiscord": "Discord सह पुढे जा", + "signInWithApple": "Apple सह पुढे जा", + "continueAnotherWay": "इतर पर्यायांनी पुढे जा", + "signUpWithGoogle": "Google सह साइन अप करा", + "signUpWithGithub": "GitHub सह साइन अप करा", + "signUpWithDiscord": "Discord सह साइन अप करा", + "signInWith": "यासह पुढे जा:", + "signInWithEmail": "ईमेलसह पुढे जा", + "signInWithMagicLink": "पुढे जा", + "signUpWithMagicLink": "Magic Link सह साइन अप करा", + "pleaseInputYourEmail": "कृपया तुमचा ईमेल पत्ता टाका", + "settings": "सेटिंग्ज", + "magicLinkSent": "Magic Link पाठवण्यात आली आहे!", + "invalidEmail": "कृपया वैध ईमेल पत्ता टाका", + "alreadyHaveAnAccount": "आधीच खाते आहे?", + "logIn": "लॉगिन", + "generalError": "काहीतरी चुकलं. कृपया नंतर प्रयत्न करा", + "limitRateError": "सुरक्षेच्या कारणास्तव, तुम्ही दर ६० सेकंदांतून एकदाच Magic Link मागवू शकता", + "magicLinkSentDescription": "तुमच्या ईमेलवर Magic Link पाठवण्यात आली आहे. लॉगिन पूर्ण करण्यासाठी लिंकवर क्लिक करा. ही लिंक ५ मिनिटांत कालबाह्य होईल." + }, + "workspace": { + "chooseWorkspace": "तुमचे workspace निवडा", + "defaultName": "माझे Workspace", + "create": "नवीन workspace तयार करा", + "new": "नवीन workspace", + "importFromNotion": "Notion मधून आयात करा", + "learnMore": "अधिक जाणून घ्या", + "reset": "workspace रीसेट करा", + "renameWorkspace": "workspace चे नाव बदला", + "workspaceNameCannotBeEmpty": "workspace चे नाव रिकामे असू शकत नाही", + "resetWorkspacePrompt": "workspace रीसेट केल्यास त्यातील सर्व पृष्ठे आणि डेटा हटवले जातील. तुम्हाला workspace रीसेट करायचे आहे का? पर्यायी म्हणून तुम्ही सपोर्ट टीमशी संपर्क करू शकता.", + "hint": "workspace", + "notFoundError": "workspace सापडले नाही", + "failedToLoad": "काहीतरी चूक झाली! workspace लोड होण्यात अयशस्वी. कृपया @:appName चे कोणतेही उघडे instance बंद करा आणि पुन्हा प्रयत्न करा.", + "errorActions": { + "reportIssue": "समस्या नोंदवा", + "reportIssueOnGithub": "Github वर समस्या नोंदवा", + "exportLogFiles": "लॉग फाइल्स निर्यात करा", + "reachOut": "Discord वर संपर्क करा" + }, + "menuTitle": "कार्यक्षेत्रे", + "deleteWorkspaceHintText": "तुम्हाला हे कार्यक्षेत्र हटवायचे आहे का? ही कृती पूर्ववत केली जाऊ शकत नाही आणि तुम्ही प्रकाशित केलेली कोणतीही पृष्ठे अप्रकाशित होतील.", + "createSuccess": "कार्यक्षेत्र यशस्वीरित्या तयार झाले", + "createFailed": "कार्यक्षेत्र तयार करण्यात अयशस्वी", + "createLimitExceeded": "तुम्ही तुमच्या खात्यासाठी परवानगी दिलेल्या कार्यक्षेत्र मर्यादेपर्यंत पोहोचलात. अधिक कार्यक्षेत्रासाठी GitHub वर विनंती करा.", + "deleteSuccess": "कार्यक्षेत्र यशस्वीरित्या हटवले गेले", + "deleteFailed": "कार्यक्षेत्र हटवण्यात अयशस्वी", + "openSuccess": "कार्यक्षेत्र यशस्वीरित्या उघडले", + "openFailed": "कार्यक्षेत्र उघडण्यात अयशस्वी", + "renameSuccess": "कार्यक्षेत्राचे नाव यशस्वीरित्या बदलले", + "renameFailed": "कार्यक्षेत्राचे नाव बदलण्यात अयशस्वी", + "updateIconSuccess": "कार्यक्षेत्राचे चिन्ह यशस्वीरित्या अद्यतनित केले", + "updateIconFailed": "कार्यक्षेत्राचे चिन्ह अद्यतनित करण्यात अयशस्वी", + "cannotDeleteTheOnlyWorkspace": "फक्त एकच कार्यक्षेत्र असल्यास ते हटवता येत नाही", + "fetchWorkspacesFailed": "कार्यक्षेत्रे मिळवण्यात अयशस्वी", + "leaveCurrentWorkspace": "कार्यक्षेत्र सोडा", + "leaveCurrentWorkspacePrompt": "तुम्हाला हे कार्यक्षेत्र सोडायचे आहे का?" + }, + "shareAction": { + "buttonText": "शेअर करा", + "workInProgress": "लवकरच येत आहे", + "markdown": "Markdown", + "html": "HTML", + "clipboard": "क्लिपबोर्डवर कॉपी करा", + "csv": "CSV", + "copyLink": "लिंक कॉपी करा", + "publishToTheWeb": "वेबवर प्रकाशित करा", + "publishToTheWebHint": "AppFlowy सह वेबसाइट तयार करा", + "publish": "प्रकाशित करा", + "unPublish": "अप्रकाशित करा", + "visitSite": "साइटला भेट द्या", + "exportAsTab": "या स्वरूपात निर्यात करा", + "publishTab": "प्रकाशित करा", + "shareTab": "शेअर करा", + "publishOnAppFlowy": "AppFlowy वर प्रकाशित करा", + "shareTabTitle": "सहकार्य करण्यासाठी आमंत्रित करा", + "shareTabDescription": "कोणासही सहज सहकार्य करण्यासाठी", + "copyLinkSuccess": "लिंक क्लिपबोर्डवर कॉपी केली", + "copyShareLink": "शेअर लिंक कॉपी करा", + "copyLinkFailed": "लिंक क्लिपबोर्डवर कॉपी करण्यात अयशस्वी", + "copyLinkToBlockSuccess": "ब्लॉकची लिंक क्लिपबोर्डवर कॉपी केली", + "copyLinkToBlockFailed": "ब्लॉकची लिंक क्लिपबोर्डवर कॉपी करण्यात अयशस्वी", + "manageAllSites": "सर्व साइट्स व्यवस्थापित करा", + "updatePathName": "पथाचे नाव अपडेट करा" + }, + "moreAction": { + "small": "लहान", + "medium": "मध्यम", + "large": "मोठा", + "fontSize": "फॉन्ट आकार", + "import": "Import", + "moreOptions": "अधिक पर्याय", + "wordCount": "शब्द संख्या: {}", + "charCount": "अक्षर संख्या: {}", + "createdAt": "निर्मिती: {}", + "deleteView": "हटवा", + "duplicateView": "प्रत बनवा", + "wordCountLabel": "शब्द संख्या: ", + "charCountLabel": "अक्षर संख्या: ", + "createdAtLabel": "निर्मिती: ", + "syncedAtLabel": "सिंक केले: ", + "saveAsNewPage": "संदेश पृष्ठात जोडा", + "saveAsNewPageDisabled": "उपलब्ध संदेश नाहीत" + }, + "importPanel": { + "textAndMarkdown": "मजकूर आणि Markdown", + "documentFromV010": "v0.1.0 पासून दस्तऐवज", + "databaseFromV010": "v0.1.0 पासून डेटाबेस", + "notionZip": "Notion निर्यात केलेली Zip फाईल", + "csv": "CSV", + "database": "डेटाबेस" + }, + "emojiIconPicker": { + "iconUploader": { + "placeholderLeft": "फाईल ड्रॅग आणि ड्रॉप करा, क्लिक करा ", + "placeholderUpload": "अपलोड", + "placeholderRight": ", किंवा इमेज लिंक पेस्ट करा.", + "dropToUpload": "अपलोडसाठी फाईल ड्रॉप करा", + "change": "बदला" + } + }, + "disclosureAction": { + "rename": "नाव बदला", + "delete": "हटवा", + "duplicate": "प्रत बनवा", + "unfavorite": "आवडतीतून काढा", + "favorite": "आवडतीत जोडा", + "openNewTab": "नवीन टॅबमध्ये उघडा", + "moveTo": "या ठिकाणी हलवा", + "addToFavorites": "आवडतीत जोडा", + "copyLink": "लिंक कॉपी करा", + "changeIcon": "आयकॉन बदला", + "collapseAllPages": "सर्व उपपृष्ठे संकुचित करा", + "movePageTo": "पृष्ठ हलवा", + "move": "हलवा", + "lockPage": "पृष्ठ लॉक करा" + }, + "blankPageTitle": "रिक्त पृष्ठ", + "newPageText": "नवीन पृष्ठ", + "newDocumentText": "नवीन दस्तऐवज", + "newGridText": "नवीन ग्रिड", + "newCalendarText": "नवीन कॅलेंडर", + "newBoardText": "नवीन बोर्ड", + "chat": { + "newChat": "AI गप्पा", + "inputMessageHint": "@:appName AI ला विचार करा", + "inputLocalAIMessageHint": "@:appName लोकल AI ला विचार करा", + "unsupportedCloudPrompt": "ही सुविधा फक्त @:appName Cloud वापरताना उपलब्ध आहे", + "relatedQuestion": "सूचवलेले", + "serverUnavailable": "कनेक्शन गमावले. कृपया तुमचे इंटरनेट तपासा आणि पुन्हा प्रयत्न करा", + "aiServerUnavailable": "AI सेवा सध्या अनुपलब्ध आहे. कृपया नंतर पुन्हा प्रयत्न करा.", + "retry": "पुन्हा प्रयत्न करा", + "clickToRetry": "पुन्हा प्रयत्न करण्यासाठी क्लिक करा", + "regenerateAnswer": "उत्तर पुन्हा तयार करा", + "question1": "Kanban वापरून कामे कशी व्यवस्थापित करायची", + "question2": "GTD पद्धत समजावून सांगा", + "question3": "Rust का वापरावा", + "question4": "माझ्या स्वयंपाकघरात असलेल्या वस्तूंनी रेसिपी", + "question5": "माझ्या पृष्ठासाठी एक चित्र तयार करा", + "question6": "या आठवड्याची माझी कामांची यादी तयार करा", + "aiMistakePrompt": "AI चुकू शकतो. महत्त्वाची माहिती तपासा.", + "chatWithFilePrompt": "तुम्हाला फाइलसोबत गप्पा मारायच्या आहेत का?", + "indexFileSuccess": "फाईल यशस्वीरित्या अनुक्रमित केली गेली", + "inputActionNoPages": "काहीही पृष्ठे सापडली नाहीत", + "referenceSource": { + "zero": "0 स्रोत सापडले", + "one": "{count} स्रोत सापडला", + "other": "{count} स्रोत सापडले" + } + }, + "clickToMention": "पृष्ठाचा उल्लेख करा", + "uploadFile": "PDFs, मजकूर किंवा markdown फाइल्स जोडा", + "questionDetail": "नमस्कार {}! मी तुम्हाला कशी मदत करू शकतो?", + "indexingFile": "{} अनुक्रमित करत आहे", + "generatingResponse": "उत्तर तयार होत आहे", + "selectSources": "स्रोत निवडा", + "currentPage": "सध्याचे पृष्ठ", + "sourcesLimitReached": "तुम्ही फक्त ३ मुख्य दस्तऐवज आणि त्यांची उपदस्तऐवज निवडू शकता", + "sourceUnsupported": "सध्या डेटाबेससह चॅटिंगसाठी आम्ही समर्थन करत नाही", + "regenerate": "पुन्हा प्रयत्न करा", + "addToPageButton": "संदेश पृष्ठावर जोडा", + "addToPageTitle": "या पृष्ठात संदेश जोडा...", + "addToNewPage": "नवीन पृष्ठ तयार करा", + "addToNewPageName": "\"{}\" मधून काढलेले संदेश", + "addToNewPageSuccessToast": "संदेश जोडण्यात आला", + "openPagePreviewFailedToast": "पृष्ठ उघडण्यात अयशस्वी", + "changeFormat": { + "actionButton": "फॉरमॅट बदला", + "confirmButton": "या फॉरमॅटसह पुन्हा तयार करा", + "textOnly": "मजकूर", + "imageOnly": "फक्त प्रतिमा", + "textAndImage": "मजकूर आणि प्रतिमा", + "text": "परिच्छेद", + "bullet": "बुलेट यादी", + "number": "क्रमांकित यादी", + "table": "सारणी", + "blankDescription": "उत्तराचे फॉरमॅट ठरवा", + "defaultDescription": "स्वयंचलित उत्तर फॉरमॅट", + "textWithImageDescription": "@:chat.changeFormat.text प्रतिमेसह", + "numberWithImageDescription": "@:chat.changeFormat.number प्रतिमेसह", + "bulletWithImageDescription": "@:chat.changeFormat.bullet प्रतिमेसह", + " tableWithImageDescription": "@:chat.changeFormat.table प्रतिमेसह" + }, + "switchModel": { + "label": "मॉडेल बदला", + "localModel": "स्थानिक मॉडेल", + "cloudModel": "क्लाऊड मॉडेल", + "autoModel": "स्वयंचलित" + }, + "selectBanner": { + "saveButton": "… मध्ये जोडा", + "selectMessages": "संदेश निवडा", + "nSelected": "{} निवडले गेले", + "allSelected": "सर्व निवडले गेले" + }, + "stopTooltip": "उत्पन्न करणे थांबवा", + "trash": { + "text": "कचरा", + "restoreAll": "सर्व पुनर्संचयित करा", + "restore": "पुनर्संचयित करा", + "deleteAll": "सर्व हटवा", + "pageHeader": { + "fileName": "फाईलचे नाव", + "lastModified": "शेवटचा बदल", + "created": "निर्मिती" + } + }, + "confirmDeleteAll": { + "title": "कचरापेटीतील सर्व पृष्ठे", + "caption": "तुम्हाला कचरापेटीतील सर्व काही हटवायचे आहे का? ही कृती पूर्ववत केली जाऊ शकत नाही." + }, + "confirmRestoreAll": { + "title": "कचरापेटीतील सर्व पृष्ठे पुनर्संचयित करा", + "caption": "ही कृती पूर्ववत केली जाऊ शकत नाही." + }, + "restorePage": { + "title": "पुनर्संचयित करा: {}", + "caption": "तुम्हाला हे पृष्ठ पुनर्संचयित करायचे आहे का?" + }, + "mobile": { + "actions": "कचरा क्रिया", + "empty": "कचरापेटीत कोणतीही पृष्ठे किंवा जागा नाहीत", + "emptyDescription": "जे आवश्यक नाही ते कचरापेटीत हलवा.", + "isDeleted": "हटवले गेले आहे", + "isRestored": "पुनर्संचयित केले गेले आहे" + }, + "confirmDeleteTitle": "तुम्हाला हे पृष्ठ कायमचे हटवायचे आहे का?", + "deletePagePrompt": { + "text": "हे पृष्ठ कचरापेटीत आहे", + "restore": "पृष्ठ पुनर्संचयित करा", + "deletePermanent": "कायमचे हटवा", + "deletePermanentDescription": "तुम्हाला हे पृष्ठ कायमचे हटवायचे आहे का? ही कृती पूर्ववत केली जाऊ शकत नाही." + }, + "dialogCreatePageNameHint": "पृष्ठाचे नाव", + "questionBubble": { + "shortcuts": "शॉर्टकट्स", + "whatsNew": "नवीन काय आहे?", + "help": "मदत आणि समर्थन", + "markdown": "Markdown", + "debug": { + "name": "डीबग माहिती", + "success": "डीबग माहिती क्लिपबोर्डवर कॉपी केली!", + "fail": "डीबग माहिती कॉपी करता आली नाही" + }, + "feedback": "अभिप्राय" + }, + "menuAppHeader": { + "moreButtonToolTip": "हटवा, नाव बदला आणि अधिक...", + "addPageTooltip": "तत्काळ एक पृष्ठ जोडा", + "defaultNewPageName": "शीर्षक नसलेले", + "renameDialog": "नाव बदला", + "pageNameSuffix": "प्रत" + }, + "noPagesInside": "अंदर कोणतीही पृष्ठे नाहीत", + "toolbar": { + "undo": "पूर्ववत करा", + "redo": "पुन्हा करा", + "bold": "ठळक", + "italic": "तिरकस", + "underline": "अधोरेखित", + "strike": "मागे ओढलेले", + "numList": "क्रमांकित यादी", + "bulletList": "बुलेट यादी", + "checkList": "चेक यादी", + "inlineCode": "इनलाइन कोड", + "quote": "उद्धरण ब्लॉक", + "header": "शीर्षक", + "highlight": "हायलाइट", + "color": "रंग", + "addLink": "लिंक जोडा" + }, + "tooltip": { + "lightMode": "लाइट मोडमध्ये स्विच करा", + "darkMode": "डार्क मोडमध्ये स्विच करा", + "openAsPage": "पृष्ठ म्हणून उघडा", + "addNewRow": "नवीन पंक्ती जोडा", + "openMenu": "मेनू उघडण्यासाठी क्लिक करा", + "dragRow": "पंक्तीचे स्थान बदलण्यासाठी ड्रॅग करा", + "viewDataBase": "डेटाबेस पहा", + "referencePage": "हे {name} संदर्भित आहे", + "addBlockBelow": "खाली एक ब्लॉक जोडा", + "aiGenerate": "निर्मिती करा" + }, + "sideBar": { + "closeSidebar": "साइडबार बंद करा", + "openSidebar": "साइडबार उघडा", + "expandSidebar": "पूर्ण पृष्ठावर विस्तारित करा", + "personal": "वैयक्तिक", + "private": "खाजगी", + "workspace": "कार्यक्षेत्र", + "favorites": "आवडती", + "clickToHidePrivate": "खाजगी जागा लपवण्यासाठी क्लिक करा\nयेथे तयार केलेली पाने फक्त तुम्हाला दिसतील", + "clickToHideWorkspace": "कार्यक्षेत्र लपवण्यासाठी क्लिक करा\nयेथे तयार केलेली पाने सर्व सदस्यांना दिसतील", + "clickToHidePersonal": "वैयक्तिक जागा लपवण्यासाठी क्लिक करा", + "clickToHideFavorites": "आवडती जागा लपवण्यासाठी क्लिक करा", + "addAPage": "नवीन पृष्ठ जोडा", + "addAPageToPrivate": "खाजगी जागेत पृष्ठ जोडा", + "addAPageToWorkspace": "कार्यक्षेत्रात पृष्ठ जोडा", + "recent": "अलीकडील", + "today": "आज", + "thisWeek": "या आठवड्यात", + "others": "पूर्वीच्या आवडती", + "earlier": "पूर्वीचे", + "justNow": "आत्ताच", + "minutesAgo": "{count} मिनिटांपूर्वी", + "lastViewed": "शेवटी पाहिलेले", + "favoriteAt": "आवडते म्हणून चिन्हांकित", + "emptyRecent": "अलीकडील पृष्ठे नाहीत", + "emptyRecentDescription": "तुम्ही पाहिलेली पृष्ठे येथे सहज पुन्हा मिळवण्यासाठी दिसतील.", + "emptyFavorite": "आवडती पृष्ठे नाहीत", + "emptyFavoriteDescription": "पृष्ठांना आवडते म्हणून चिन्हांकित करा—ते येथे झपाट्याने प्रवेशासाठी दिसतील!", + "removePageFromRecent": "हे पृष्ठ अलीकडील यादीतून काढायचे?", + "removeSuccess": "यशस्वीरित्या काढले गेले", + "favoriteSpace": "आवडती", + "RecentSpace": "अलीकडील", + "Spaces": "जागा", + "upgradeToPro": "Pro मध्ये अपग्रेड करा", + "upgradeToAIMax": "अमर्यादित AI अनलॉक करा", + "storageLimitDialogTitle": "तुमचा मोफत स्टोरेज संपला आहे. अमर्यादित स्टोरेजसाठी अपग्रेड करा", + "storageLimitDialogTitleIOS": "तुमचा मोफत स्टोरेज संपला आहे.", + "aiResponseLimitTitle": "तुमचे मोफत AI प्रतिसाद संपले आहेत. कृपया Pro प्लॅनला अपग्रेड करा किंवा AI add-on खरेदी करा", + "aiResponseLimitDialogTitle": "AI प्रतिसाद मर्यादा गाठली आहे", + "aiResponseLimit": "तुमचे मोफत AI प्रतिसाद संपले आहेत.\n\nसेटिंग्ज -> प्लॅन -> AI Max किंवा Pro प्लॅन क्लिक करा", + "askOwnerToUpgradeToPro": "तुमच्या कार्यक्षेत्राचे मोफत स्टोरेज संपत चालले आहे. कृपया कार्यक्षेत्र मालकाला Pro प्लॅनमध्ये अपग्रेड करण्यास सांगा", + "askOwnerToUpgradeToProIOS": "तुमच्या कार्यक्षेत्राचे मोफत स्टोरेज संपत चालले आहे.", + "askOwnerToUpgradeToAIMax": "तुमच्या कार्यक्षेत्राचे मोफत AI प्रतिसाद संपले आहेत. कृपया कार्यक्षेत्र मालकाला प्लॅन अपग्रेड किंवा AI add-ons खरेदी करण्यास सांगा", + "askOwnerToUpgradeToAIMaxIOS": "तुमच्या कार्यक्षेत्राचे मोफत AI प्रतिसाद संपले आहेत.", + "purchaseAIMax": "तुमच्या कार्यक्षेत्राचे AI प्रतिमा प्रतिसाद संपले आहेत. कृपया कार्यक्षेत्र मालकाला AI Max खरेदी करण्यास सांगा", + "aiImageResponseLimit": "तुमचे AI प्रतिमा प्रतिसाद संपले आहेत.\n\nसेटिंग्ज -> प्लॅन -> AI Max क्लिक करा", + "purchaseStorageSpace": "स्टोरेज स्पेस खरेदी करा", + "singleFileProPlanLimitationDescription": "तुम्ही मोफत प्लॅनमध्ये परवानगी दिलेल्या फाइल अपलोड आकाराची मर्यादा ओलांडली आहे. कृपया Pro प्लॅनमध्ये अपग्रेड करा", + "purchaseAIResponse": "AI प्रतिसाद खरेदी करा", + "askOwnerToUpgradeToLocalAI": "कार्यक्षेत्र मालकाला ऑन-डिव्हाइस AI सक्षम करण्यास सांगा", + "upgradeToAILocal": "अत्याधिक गोपनीयतेसाठी तुमच्या डिव्हाइसवर लोकल मॉडेल चालवा", + "upgradeToAILocalDesc": "PDFs सोबत गप्पा मारा, तुमचे लेखन सुधारा आणि लोकल AI वापरून टेबल्स आपोआप भरा" +}, + "notifications": { + "export": { + "markdown": "टीप Markdown मध्ये निर्यात केली", + "path": "Documents/flowy" + } + }, + "contactsPage": { + "title": "संपर्क", + "whatsHappening": "या आठवड्यात काय घडत आहे?", + "addContact": "संपर्क जोडा", + "editContact": "संपर्क संपादित करा" + }, + "button": { + "ok": "ठीक आहे", + "confirm": "खात्री करा", + "done": "पूर्ण", + "cancel": "रद्द करा", + "signIn": "साइन इन", + "signOut": "साइन आउट", + "complete": "पूर्ण करा", + "save": "जतन करा", + "generate": "निर्माण करा", + "esc": "ESC", + "keep": "ठेवा", + "tryAgain": "पुन्हा प्रयत्न करा", + "discard": "टाका", + "replace": "बदला", + "insertBelow": "खाली घाला", + "insertAbove": "वर घाला", + "upload": "अपलोड करा", + "edit": "संपादित करा", + "delete": "हटवा", + "copy": "कॉपी करा", + "duplicate": "प्रत बनवा", + "putback": "परत ठेवा", + "update": "अद्यतनित करा", + "share": "शेअर करा", + "removeFromFavorites": "आवडतीतून काढा", + "removeFromRecent": "अलीकडील यादीतून काढा", + "addToFavorites": "आवडतीत जोडा", + "favoriteSuccessfully": "आवडतीत यशस्वीरित्या जोडले", + "unfavoriteSuccessfully": "आवडतीतून यशस्वीरित्या काढले", + "duplicateSuccessfully": "प्रत यशस्वीरित्या तयार झाली", + "rename": "नाव बदला", + "helpCenter": "मदत केंद्र", + "add": "जोड़ा", + "yes": "होय", + "no": "नाही", + "clear": "साफ करा", + "remove": "काढा", + "dontRemove": "काढू नका", + "copyLink": "लिंक कॉपी करा", + "align": "जुळवा", + "login": "लॉगिन", + "logout": "लॉगआउट", + "deleteAccount": "खाते हटवा", + "back": "मागे", + "signInGoogle": "Google सह पुढे जा", + "signInGithub": "GitHub सह पुढे जा", + "signInDiscord": "Discord सह पुढे जा", + "more": "अधिक", + "create": "तयार करा", + "close": "बंद करा", + "next": "पुढे", + "previous": "मागील", + "submit": "सबमिट करा", + "download": "डाउनलोड करा", + "backToHome": "मुख्यपृष्ठावर परत जा", + "viewing": "पाहत आहात", + "editing": "संपादन करत आहात", + "gotIt": "समजले", + "retry": "पुन्हा प्रयत्न करा", + "uploadFailed": "अपलोड अयशस्वी.", + "copyLinkOriginal": "मूळ दुव्याची कॉपी करा" + }, + "label": { + "welcome": "स्वागत आहे!", + "firstName": "पहिले नाव", + "middleName": "मधले नाव", + "lastName": "आडनाव", + "stepX": "पायरी {X}" + }, + "oAuth": { + "err": { + "failedTitle": "तुमच्या खात्याशी कनेक्ट होता आले नाही.", + "failedMsg": "कृपया खात्री करा की तुम्ही ब्राउझरमध्ये साइन-इन प्रक्रिया पूर्ण केली आहे." + }, + "google": { + "title": "GOOGLE साइन-इन", + "instruction1": "तुमचे Google Contacts आयात करण्यासाठी, तुम्हाला तुमच्या वेब ब्राउझरचा वापर करून या अ‍ॅप्लिकेशनला अधिकृत करणे आवश्यक आहे.", + "instruction2": "ही कोड आयकॉनवर क्लिक करून किंवा मजकूर निवडून क्लिपबोर्डवर कॉपी करा:", + "instruction3": "तुमच्या वेब ब्राउझरमध्ये खालील दुव्यावर जा आणि वरील कोड टाका:", + "instruction4": "साइनअप पूर्ण झाल्यावर खालील बटण क्लिक करा:" + } + }, + "settings": { + "title": "सेटिंग्ज", + "popupMenuItem": { + "settings": "सेटिंग्ज", + "members": "सदस्य", + "trash": "कचरा", + "helpAndSupport": "मदत आणि समर्थन" + }, + "sites": { + "title": "साइट्स", + "namespaceTitle": "नेमस्पेस", + "namespaceDescription": "तुमचा नेमस्पेस आणि मुख्यपृष्ठ व्यवस्थापित करा", + "namespaceHeader": "नेमस्पेस", + "homepageHeader": "मुख्यपृष्ठ", + "updateNamespace": "नेमस्पेस अद्यतनित करा", + "removeHomepage": "मुख्यपृष्ठ हटवा", + "selectHomePage": "एक पृष्ठ निवडा", + "clearHomePage": "या नेमस्पेससाठी मुख्यपृष्ठ साफ करा", + "customUrl": "स्वतःची URL", + "namespace": { + "description": "हे बदल सर्व प्रकाशित पृष्ठांवर लागू होतील जे या नेमस्पेसवर चालू आहेत", + "tooltip": "कोणताही अनुचित नेमस्पेस आम्ही काढून टाकण्याचा अधिकार राखून ठेवतो", + "updateExistingNamespace": "विद्यमान नेमस्पेस अद्यतनित करा", + "upgradeToPro": "मुख्यपृष्ठ सेट करण्यासाठी Pro प्लॅनमध्ये अपग्रेड करा", + "redirectToPayment": "पेमेंट पृष्ठावर वळवत आहे...", + "onlyWorkspaceOwnerCanSetHomePage": "फक्त कार्यक्षेत्र मालकच मुख्यपृष्ठ सेट करू शकतो", + "pleaseAskOwnerToSetHomePage": "कृपया कार्यक्षेत्र मालकाला Pro प्लॅनमध्ये अपग्रेड करण्यास सांगा" + }, + "publishedPage": { + "title": "सर्व प्रकाशित पृष्ठे", + "description": "तुमची प्रकाशित पृष्ठे व्यवस्थापित करा", + "page": "पृष्ठ", + "pathName": "पथाचे नाव", + "date": "प्रकाशन तारीख", + "emptyHinText": "या कार्यक्षेत्रात तुमच्याकडे कोणतीही प्रकाशित पृष्ठे नाहीत", + "noPublishedPages": "प्रकाशित पृष्ठे नाहीत", + "settings": "प्रकाशन सेटिंग्ज", + "clickToOpenPageInApp": "पृष्ठ अ‍ॅपमध्ये उघडा", + "clickToOpenPageInBrowser": "पृष्ठ ब्राउझरमध्ये उघडा" + } + } + }, + "error": { + "failedToGeneratePaymentLink": "Pro प्लॅनसाठी पेमेंट लिंक तयार करण्यात अयशस्वी", + "failedToUpdateNamespace": "नेमस्पेस अद्यतनित करण्यात अयशस्वी", + "proPlanLimitation": "नेमस्पेस अद्यतनित करण्यासाठी तुम्हाला Pro प्लॅनमध्ये अपग्रेड करणे आवश्यक आहे", + "namespaceAlreadyInUse": "नेमस्पेस आधीच वापरात आहे, कृपया दुसरे प्रयत्न करा", + "invalidNamespace": "अवैध नेमस्पेस, कृपया दुसरे प्रयत्न करा", + "namespaceLengthAtLeast2Characters": "नेमस्पेस किमान २ अक्षरे लांब असावे", + "onlyWorkspaceOwnerCanUpdateNamespace": "फक्त कार्यक्षेत्र मालकच नेमस्पेस अद्यतनित करू शकतो", + "onlyWorkspaceOwnerCanRemoveHomepage": "फक्त कार्यक्षेत्र मालकच मुख्यपृष्ठ हटवू शकतो", + "setHomepageFailed": "मुख्यपृष्ठ सेट करण्यात अयशस्वी", + "namespaceTooLong": "नेमस्पेस खूप लांब आहे, कृपया दुसरे प्रयत्न करा", + "namespaceTooShort": "नेमस्पेस खूप लहान आहे, कृपया दुसरे प्रयत्न करा", + "namespaceIsReserved": "हा नेमस्पेस राखीव आहे, कृपया दुसरे प्रयत्न करा", + "updatePathNameFailed": "पथाचे नाव अद्यतनित करण्यात अयशस्वी", + "removeHomePageFailed": "मुख्यपृष्ठ हटवण्यात अयशस्वी", + "publishNameContainsInvalidCharacters": "पथाच्या नावामध्ये अवैध अक्षरे आहेत, कृपया दुसरे प्रयत्न करा", + "publishNameTooShort": "पथाचे नाव खूप लहान आहे, कृपया दुसरे प्रयत्न करा", + "publishNameTooLong": "पथाचे नाव खूप लांब आहे, कृपया दुसरे प्रयत्न करा", + "publishNameAlreadyInUse": "हे पथाचे नाव आधीच वापरले गेले आहे, कृपया दुसरे प्रयत्न करा", + "namespaceContainsInvalidCharacters": "नेमस्पेसमध्ये अवैध अक्षरे आहेत, कृपया दुसरे प्रयत्न करा", + "publishPermissionDenied": "फक्त कार्यक्षेत्र मालक किंवा पृष्ठ प्रकाशकच प्रकाशन सेटिंग्ज व्यवस्थापित करू शकतो", + "publishNameCannotBeEmpty": "पथाचे नाव रिकामे असू शकत नाही, कृपया दुसरे प्रयत्न करा" + }, + "success": { + "namespaceUpdated": "नेमस्पेस यशस्वीरित्या अद्यतनित केला", + "setHomepageSuccess": "मुख्यपृष्ठ यशस्वीरित्या सेट केले", + "updatePathNameSuccess": "पथाचे नाव यशस्वीरित्या अद्यतनित केले", + "removeHomePageSuccess": "मुख्यपृष्ठ यशस्वीरित्या हटवले" + }, + "accountPage": { + "menuLabel": "खाते आणि अ‍ॅप", + "title": "माझे खाते", + "general": { + "title": "खात्याचे नाव आणि प्रोफाइल प्रतिमा", + "changeProfilePicture": "प्रोफाइल प्रतिमा बदला" + }, + "email": { + "title": "ईमेल", + "actions": { + "change": "ईमेल बदला" + } + }, + "login": { + "title": "खाते लॉगिन", + "loginLabel": "लॉगिन", + "logoutLabel": "लॉगआउट" + }, + "isUpToDate": "@:appName अद्ययावत आहे!", + "officialVersion": "आवृत्ती {version} (अधिकृत बिल्ड)" +}, + "workspacePage": { + "menuLabel": "कार्यक्षेत्र", + "title": "कार्यक्षेत्र", + "description": "तुमचे कार्यक्षेत्र स्वरूप, थीम, फॉन्ट, मजकूर रचना, दिनांक/वेळ फॉरमॅट आणि भाषा सानुकूलित करा.", + "workspaceName": { + "title": "कार्यक्षेत्राचे नाव" + }, + "workspaceIcon": { + "title": "कार्यक्षेत्राचे चिन्ह", + "description": "तुमच्या कार्यक्षेत्रासाठी प्रतिमा अपलोड करा किंवा इमोजी वापरा. हे चिन्ह साइडबार आणि सूचना मध्ये दिसेल." + }, + "appearance": { + "title": "दृश्यरूप", + "description": "कार्यक्षेत्राचे दृश्यरूप, थीम, फॉन्ट, मजकूर रचना, दिनांक, वेळ आणि भाषा सानुकूलित करा.", + "options": { + "system": "स्वयंचलित", + "light": "लाइट", + "dark": "डार्क" + } + } + }, + "resetCursorColor": { + "title": "दस्तऐवज कर्सरचा रंग रीसेट करा", + "description": "तुम्हाला कर्सरचा रंग रीसेट करायचा आहे का?" + }, + "resetSelectionColor": { + "title": "दस्तऐवज निवडीचा रंग रीसेट करा", + "description": "तुम्हाला निवडीचा रंग रीसेट करायचा आहे का?" + }, + "resetWidth": { + "resetSuccess": "दस्तऐवजाची रुंदी यशस्वीरित्या रीसेट केली" + }, + "theme": { + "title": "थीम", + "description": "पूर्व-निर्धारित थीम निवडा किंवा तुमची स्वतःची थीम अपलोड करा.", + "uploadCustomThemeTooltip": "स्वतःची थीम अपलोड करा" + }, + "workspaceFont": { + "title": "कार्यक्षेत्र फॉन्ट", + "noFontHint": "कोणताही फॉन्ट सापडला नाही, कृपया दुसरा शब्द वापरून पहा." + }, + "textDirection": { + "title": "मजकूर दिशा", + "leftToRight": "डावीकडून उजवीकडे", + "rightToLeft": "उजवीकडून डावीकडे", + "auto": "स्वयंचलित", + "enableRTLItems": "RTL टूलबार घटक सक्षम करा" + }, + "layoutDirection": { + "title": "लेआउट दिशा", + "leftToRight": "डावीकडून उजवीकडे", + "rightToLeft": "उजवीकडून डावीकडे" + }, + "dateTime": { + "title": "दिनांक आणि वेळ", + "example": "{} वाजता {} ({})", + "24HourTime": "२४-तास वेळ", + "dateFormat": { + "label": "दिनांक फॉरमॅट", + "local": "स्थानिक", + "us": "US", + "iso": "ISO", + "friendly": "सुलभ", + "dmy": "D/M/Y" + } + }, + "language": { + "title": "भाषा" + }, + "deleteWorkspacePrompt": { + "title": "कार्यक्षेत्र हटवा", + "content": "तुम्हाला हे कार्यक्षेत्र हटवायचे आहे का? ही कृती पूर्ववत केली जाऊ शकत नाही, आणि तुम्ही प्रकाशित केलेली कोणतीही पृष्ठे अप्रकाशित होतील." + }, + "leaveWorkspacePrompt": { + "title": "कार्यक्षेत्र सोडा", + "content": "तुम्हाला हे कार्यक्षेत्र सोडायचे आहे का? यानंतर तुम्हाला यामधील सर्व पृष्ठे आणि डेटावर प्रवेश मिळणार नाही.", + "success": "तुम्ही कार्यक्षेत्र यशस्वीरित्या सोडले.", + "fail": "कार्यक्षेत्र सोडण्यात अयशस्वी." + }, + "manageWorkspace": { + "title": "कार्यक्षेत्र व्यवस्थापित करा", + "leaveWorkspace": "कार्यक्षेत्र सोडा", + "deleteWorkspace": "कार्यक्षेत्र हटवा" + }, + "manageDataPage": { + "menuLabel": "डेटा व्यवस्थापित करा", + "title": "डेटा व्यवस्थापन", + "description": "@:appName मध्ये स्थानिक डेटा संचयन व्यवस्थापित करा किंवा तुमचा विद्यमान डेटा आयात करा.", + "dataStorage": { + "title": "फाइल संचयन स्थान", + "tooltip": "जिथे तुमच्या फाइल्स संग्रहित आहेत ते स्थान", + "actions": { + "change": "मार्ग बदला", + "open": "फोल्डर उघडा", + "openTooltip": "सध्याच्या डेटा फोल्डरचे स्थान उघडा", + "copy": "मार्ग कॉपी करा", + "copiedHint": "मार्ग कॉपी केला!", + "resetTooltip": "मूलभूत स्थानावर रीसेट करा" + }, + "resetDialog": { + "title": "तुम्हाला खात्री आहे का?", + "description": "पथ मूलभूत डेटा स्थानावर रीसेट केल्याने तुमचा डेटा हटवला जाणार नाही. तुम्हाला सध्याचा डेटा पुन्हा आयात करायचा असल्यास, कृपया आधी त्याचा पथ कॉपी करा." + } + }, + "importData": { + "title": "डेटा आयात करा", + "tooltip": "@:appName बॅकअप/डेटा फोल्डरमधून डेटा आयात करा", + "description": "बाह्य @:appName डेटा फोल्डरमधून डेटा कॉपी करा", + "action": "फाइल निवडा" + }, + "encryption": { + "title": "एनक्रिप्शन", + "tooltip": "तुमचा डेटा कसा संग्रहित आणि एनक्रिप्ट केला जातो ते व्यवस्थापित करा", + "descriptionNoEncryption": "एनक्रिप्शन चालू केल्याने सर्व डेटा एनक्रिप्ट केला जाईल. ही क्रिया पूर्ववत केली जाऊ शकत नाही.", + "descriptionEncrypted": "तुमचा डेटा एनक्रिप्टेड आहे.", + "action": "डेटा एनक्रिप्ट करा", + "dialog": { + "title": "संपूर्ण डेटा एनक्रिप्ट करायचा?", + "description": "तुमचा संपूर्ण डेटा एनक्रिप्ट केल्याने तो सुरक्षित राहील. ही क्रिया पूर्ववत केली जाऊ शकत नाही. तुम्हाला पुढे जायचे आहे का?" + } + }, + "cache": { + "title": "कॅशे साफ करा", + "description": "प्रतिमा न दिसणे, पृष्ठे हरवणे, फॉन्ट लोड न होणे अशा समस्यांचे निराकरण करण्यासाठी मदत करा. याचा तुमच्या डेटावर परिणाम होणार नाही.", + "dialog": { + "title": "कॅशे साफ करा", + "description": "प्रतिमा न दिसणे, पृष्ठे हरवणे, फॉन्ट लोड न होणे अशा समस्यांचे निराकरण करण्यासाठी मदत करा. याचा तुमच्या डेटावर परिणाम होणार नाही.", + "successHint": "कॅशे साफ झाली!" + } + }, + "data": { + "fixYourData": "तुमचा डेटा सुधारा", + "fixButton": "सुधारा", + "fixYourDataDescription": "तुमच्या डेटामध्ये काही अडचण येत असल्यास, तुम्ही येथे ती सुधारू शकता." + } + }, + "shortcutsPage": { + "menuLabel": "शॉर्टकट्स", + "title": "शॉर्टकट्स", + "editBindingHint": "नवीन बाइंडिंग टाका", + "searchHint": "शोधा", + "actions": { + "resetDefault": "मूलभूत रीसेट करा" + }, + "errorPage": { + "message": "शॉर्टकट्स लोड करण्यात अयशस्वी: {}", + "howToFix": "कृपया पुन्हा प्रयत्न करा. समस्या कायम राहिल्यास GitHub वर संपर्क साधा." + }, + "resetDialog": { + "title": "शॉर्टकट्स रीसेट करा", + "description": "हे सर्व कीबाइंडिंग्जना मूळ स्थितीत रीसेट करेल. ही क्रिया पूर्ववत करता येणार नाही. तुम्हाला खात्री आहे का?", + "buttonLabel": "रीसेट करा" + }, + "conflictDialog": { + "title": "{} आधीच वापरले जात आहे", + "descriptionPrefix": "हे कीबाइंडिंग सध्या ", + "descriptionSuffix": " यामध्ये वापरले जात आहे. तुम्ही हे कीबाइंडिंग बदलल्यास, ते {} मधून काढले जाईल.", + "confirmLabel": "पुढे जा" + }, + "editTooltip": "कीबाइंडिंग संपादित करण्यासाठी क्लिक करा", + "keybindings": { + "toggleToDoList": "टू-डू सूची चालू/बंद करा", + "insertNewParagraphInCodeblock": "नवीन परिच्छेद टाका", + "pasteInCodeblock": "कोडब्लॉकमध्ये पेस्ट करा", + "selectAllCodeblock": "सर्व निवडा", + "indentLineCodeblock": "ओळीच्या सुरुवातीला दोन स्पेस टाका", + "outdentLineCodeblock": "ओळीच्या सुरुवातीची दोन स्पेस काढा", + "twoSpacesCursorCodeblock": "कर्सरवर दोन स्पेस टाका", + "copy": "निवड कॉपी करा", + "paste": "मजकुरात पेस्ट करा", + "cut": "निवड कट करा", + "alignLeft": "मजकूर डावीकडे संरेखित करा", + "alignCenter": "मजकूर मधोमध संरेखित करा", + "alignRight": "मजकूर उजवीकडे संरेखित करा", + "insertInlineMathEquation": "इनलाइन गणितीय सूत्र टाका", + "undo": "पूर्ववत करा", + "redo": "पुन्हा करा", + "convertToParagraph": "ब्लॉक परिच्छेदात रूपांतरित करा", + "backspace": "हटवा", + "deleteLeftWord": "डावीकडील शब्द हटवा", + "deleteLeftSentence": "डावीकडील वाक्य हटवा", + "delete": "उजवीकडील अक्षर हटवा", + "deleteMacOS": "डावीकडील अक्षर हटवा", + "deleteRightWord": "उजवीकडील शब्द हटवा", + "moveCursorLeft": "कर्सर डावीकडे हलवा", + "moveCursorBeginning": "कर्सर सुरुवातीला हलवा", + "moveCursorLeftWord": "कर्सर एक शब्द डावीकडे हलवा", + "moveCursorLeftSelect": "निवडा आणि कर्सर डावीकडे हलवा", + "moveCursorBeginSelect": "निवडा आणि कर्सर सुरुवातीला हलवा", + "moveCursorLeftWordSelect": "निवडा आणि कर्सर एक शब्द डावीकडे हलवा", + "moveCursorRight": "कर्सर उजवीकडे हलवा", + "moveCursorEnd": "कर्सर शेवटी हलवा", + "moveCursorRightWord": "कर्सर एक शब्द उजवीकडे हलवा", + "moveCursorRightSelect": "निवडा आणि कर्सर उजवीकडे हलवा", + "moveCursorEndSelect": "निवडा आणि कर्सर शेवटी हलवा", + "moveCursorRightWordSelect": "निवडा आणि कर्सर एक शब्द उजवीकडे हलवा", + "moveCursorUp": "कर्सर वर हलवा", + "moveCursorTopSelect": "निवडा आणि कर्सर वर हलवा", + "moveCursorTop": "कर्सर वर हलवा", + "moveCursorUpSelect": "निवडा आणि कर्सर वर हलवा", + "moveCursorBottomSelect": "निवडा आणि कर्सर खाली हलवा", + "moveCursorBottom": "कर्सर खाली हलवा", + "moveCursorDown": "कर्सर खाली हलवा", + "moveCursorDownSelect": "निवडा आणि कर्सर खाली हलवा", + "home": "वर स्क्रोल करा", + "end": "खाली स्क्रोल करा", + "toggleBold": "बोल्ड चालू/बंद करा", + "toggleItalic": "इटालिक चालू/बंद करा", + "toggleUnderline": "अधोरेखित चालू/बंद करा", + "toggleStrikethrough": "स्ट्राईकथ्रू चालू/बंद करा", + "toggleCode": "इनलाइन कोड चालू/बंद करा", + "toggleHighlight": "हायलाईट चालू/बंद करा", + "showLinkMenu": "लिंक मेनू दाखवा", + "openInlineLink": "इनलाइन लिंक उघडा", + "openLinks": "सर्व निवडलेले लिंक उघडा", + "indent": "इंडेंट", + "outdent": "आउटडेंट", + "exit": "संपादनातून बाहेर पडा", + "pageUp": "एक पृष्ठ वर स्क्रोल करा", + "pageDown": "एक पृष्ठ खाली स्क्रोल करा", + "selectAll": "सर्व निवडा", + "pasteWithoutFormatting": "फॉरमॅटिंगशिवाय पेस्ट करा", + "showEmojiPicker": "इमोजी निवडकर्ता दाखवा", + "enterInTableCell": "टेबलमध्ये नवीन ओळ जोडा", + "leftInTableCell": "टेबलमध्ये डावीकडील सेलमध्ये जा", + "rightInTableCell": "टेबलमध्ये उजवीकडील सेलमध्ये जा", + "upInTableCell": "टेबलमध्ये वरील सेलमध्ये जा", + "downInTableCell": "टेबलमध्ये खालील सेलमध्ये जा", + "tabInTableCell": "टेबलमध्ये पुढील सेलमध्ये जा", + "shiftTabInTableCell": "टेबलमध्ये मागील सेलमध्ये जा", + "backSpaceInTableCell": "सेलच्या सुरुवातीला थांबा" + }, + "commands": { + "codeBlockNewParagraph": "कोडब्लॉकच्या शेजारी नवीन परिच्छेद टाका", + "codeBlockIndentLines": "कोडब्लॉकमध्ये ओळीच्या सुरुवातीला दोन स्पेस टाका", + "codeBlockOutdentLines": "कोडब्लॉकमध्ये ओळीच्या सुरुवातीची दोन स्पेस काढा", + "codeBlockAddTwoSpaces": "कोडब्लॉकमध्ये कर्सरच्या ठिकाणी दोन स्पेस टाका", + "codeBlockSelectAll": "कोडब्लॉकमधील सर्व मजकूर निवडा", + "codeBlockPasteText": "कोडब्लॉकमध्ये मजकूर पेस्ट करा", + "textAlignLeft": "मजकूर डावीकडे संरेखित करा", + "textAlignCenter": "मजकूर मधोमध संरेखित करा", + "textAlignRight": "मजकूर उजवीकडे संरेखित करा" + }, + "couldNotLoadErrorMsg": "शॉर्टकट लोड करता आले नाहीत. पुन्हा प्रयत्न करा", + "couldNotSaveErrorMsg": "शॉर्टकट सेव्ह करता आले नाहीत. पुन्हा प्रयत्न करा" +}, + "aiPage": { + "title": "AI सेटिंग्ज", + "menuLabel": "AI सेटिंग्ज", + "keys": { + "enableAISearchTitle": "AI शोध", + "aiSettingsDescription": "AppFlowy AI चालवण्यासाठी तुमचा पसंतीचा मॉडेल निवडा. आता GPT-4o, GPT-o3-mini, DeepSeek R1, Claude 3.5 Sonnet आणि Ollama मधील मॉडेल्सचा समावेश आहे.", + "loginToEnableAIFeature": "AI वैशिष्ट्ये फक्त @:appName Cloud मध्ये लॉगिन केल्यानंतरच सक्षम होतात. तुमच्याकडे @:appName खाते नसेल तर 'माय अकाउंट' मध्ये जाऊन साइन अप करा.", + "llmModel": "भाषा मॉडेल", + "llmModelType": "भाषा मॉडेल प्रकार", + "downloadLLMPrompt": "{} डाउनलोड करा", + "downloadAppFlowyOfflineAI": "AI ऑफलाइन पॅकेज डाउनलोड केल्याने तुमच्या डिव्हाइसवर AI चालवता येईल. तुम्हाला पुढे जायचे आहे का?", + "downloadLLMPromptDetail": "{} स्थानिक मॉडेल डाउनलोड करण्यासाठी सुमारे {} संचयन लागेल. तुम्हाला पुढे जायचे आहे का?", + "downloadBigFilePrompt": "डाउनलोड पूर्ण होण्यासाठी सुमारे १० मिनिटे लागू शकतात", + "downloadAIModelButton": "डाउनलोड करा", + "downloadingModel": "डाउनलोड करत आहे", + "localAILoaded": "स्थानिक AI मॉडेल यशस्वीरित्या जोडले गेले आणि वापरण्यास सज्ज आहे", + "localAIStart": "स्थानिक AI सुरू होत आहे. जर हळू वाटत असेल, तर ते बंद करून पुन्हा सुरू करून पहा", + "localAILoading": "स्थानिक AI Chat मॉडेल लोड होत आहे...", + "localAIStopped": "स्थानिक AI थांबले आहे", + "localAIRunning": "स्थानिक AI चालू आहे", + "localAINotReadyRetryLater": "स्थानिक AI सुरू होत आहे, कृपया नंतर पुन्हा प्रयत्न करा", + "localAIDisabled": "तुम्ही स्थानिक AI वापरत आहात, पण ते अकार्यक्षम आहे. कृपया सेटिंग्जमध्ये जाऊन ते सक्षम करा किंवा दुसरे मॉडेल वापरून पहा", + "localAIInitializing": "स्थानिक AI लोड होत आहे. तुमच्या डिव्हाइसवर अवलंबून याला काही मिनिटे लागू शकतात", + "localAINotReadyTextFieldPrompt": "स्थानिक AI लोड होत असताना संपादन करता येणार नाही", + "failToLoadLocalAI": "स्थानिक AI सुरू करण्यात अयशस्वी.", + "restartLocalAI": "पुन्हा सुरू करा", + "disableLocalAITitle": "स्थानिक AI अकार्यक्षम करा", + "disableLocalAIDescription": "तुम्हाला स्थानिक AI अकार्यक्षम करायचा आहे का?", + "localAIToggleTitle": "AppFlowy स्थानिक AI (LAI)", + "localAIToggleSubTitle": "AppFlowy मध्ये अत्याधुनिक स्थानिक AI मॉडेल्स वापरून गोपनीयता आणि सुरक्षा सुनिश्चित करा", + "offlineAIInstruction1": "हे अनुसरा", + "offlineAIInstruction2": "सूचना", + "offlineAIInstruction3": "ऑफलाइन AI सक्षम करण्यासाठी.", + "offlineAIDownload1": "जर तुम्ही AppFlowy AI डाउनलोड केले नसेल, तर कृपया", + "offlineAIDownload2": "डाउनलोड", + "offlineAIDownload3": "करा", + "activeOfflineAI": "सक्रिय", + "downloadOfflineAI": "डाउनलोड करा", + "openModelDirectory": "फोल्डर उघडा", + "laiNotReady": "स्थानिक AI अ‍ॅप योग्य प्रकारे इन्स्टॉल झालेले नाही.", + "ollamaNotReady": "Ollama सर्व्हर तयार नाही.", + "pleaseFollowThese": "कृपया हे अनुसरा", + "instructions": "सूचना", + "installOllamaLai": "Ollama आणि AppFlowy स्थानिक AI सेटअप करण्यासाठी.", + "modelsMissing": "आवश्यक मॉडेल्स सापडत नाहीत.", + "downloadModel": "त्यांना डाउनलोड करण्यासाठी." + } +}, + "planPage": { + "menuLabel": "योजना", + "title": "दर योजना", + "planUsage": { + "title": "योजनेचा वापर सारांश", + "storageLabel": "स्टोरेज", + "storageUsage": "{} पैकी {} GB", + "unlimitedStorageLabel": "अमर्यादित स्टोरेज", + "collaboratorsLabel": "सदस्य", + "collaboratorsUsage": "{} पैकी {}", + "aiResponseLabel": "AI प्रतिसाद", + "aiResponseUsage": "{} पैकी {}", + "unlimitedAILabel": "अमर्यादित AI प्रतिसाद", + "proBadge": "प्रो", + "aiMaxBadge": "AI Max", + "aiOnDeviceBadge": "मॅकसाठी ऑन-डिव्हाइस AI", + "memberProToggle": "अधिक सदस्य आणि अमर्यादित AI", + "aiMaxToggle": "अमर्यादित AI आणि प्रगत मॉडेल्सचा प्रवेश", + "aiOnDeviceToggle": "जास्त गोपनीयतेसाठी स्थानिक AI", + "aiCredit": { + "title": "@:appName AI क्रेडिट जोडा", + "price": "{}", + "priceDescription": "1,000 क्रेडिट्ससाठी", + "purchase": "AI खरेदी करा", + "info": "प्रत्येक कार्यक्षेत्रासाठी 1,000 AI क्रेडिट्स जोडा आणि आपल्या कामाच्या प्रवाहात सानुकूलनीय AI सहजपणे एकत्र करा — अधिक स्मार्ट आणि जलद निकालांसाठी:", + "infoItemOne": "प्रत्येक डेटाबेससाठी 10,000 प्रतिसाद", + "infoItemTwo": "प्रत्येक कार्यक्षेत्रासाठी 1,000 प्रतिसाद" + }, + "currentPlan": { + "bannerLabel": "सद्य योजना", + "freeTitle": "फ्री", + "proTitle": "प्रो", + "teamTitle": "टीम", + "freeInfo": "2 सदस्यांपर्यंत वैयक्तिक वापरासाठी उत्तम", + "proInfo": "10 सदस्यांपर्यंत लहान आणि मध्यम टीमसाठी योग्य", + "teamInfo": "सर्व उत्पादनक्षम आणि सुव्यवस्थित टीमसाठी योग्य", + "upgrade": "योजना बदला", + "canceledInfo": "तुमची योजना रद्द करण्यात आली आहे, {} रोजी तुम्हाला फ्री योजनेवर डाऊनग्रेड केले जाईल." + }, + "addons": { + "title": "ऍड-ऑन्स", + "addLabel": "जोडा", + "activeLabel": "जोडले गेले", + "aiMax": { + "title": "AI Max", + "description": "प्रगत AI मॉडेल्ससह अमर्यादित AI प्रतिसाद आणि दरमहा 50 AI प्रतिमा", + "price": "{}", + "priceInfo": "प्रति वापरकर्ता प्रति महिना (वार्षिक बिलिंग)" + }, + "aiOnDevice": { + "title": "मॅकसाठी ऑन-डिव्हाइस AI", + "description": "तुमच्या डिव्हाइसवर Mistral 7B, LLAMA 3 आणि अधिक स्थानिक मॉडेल्स चालवा", + "price": "{}", + "priceInfo": "प्रति वापरकर्ता प्रति महिना (वार्षिक बिलिंग)", + "recommend": "M1 किंवा नवीनतम शिफारस केली जाते" + } + }, + "deal": { + "bannerLabel": "नववर्षाचे विशेष ऑफर!", + "title": "तुमची टीम वाढवा!", + "info": "Pro आणि Team योजनांवर 10% सूट मिळवा! @:appName AI सह शक्तिशाली नवीन वैशिष्ट्यांसह तुमचे कार्यक्षेत्र अधिक कार्यक्षम बनवा.", + "viewPlans": "योजना पहा" + } + } +}, + "billingPage": { + "menuLabel": "बिलिंग", + "title": "बिलिंग", + "plan": { + "title": "योजना", + "freeLabel": "फ्री", + "proLabel": "प्रो", + "planButtonLabel": "योजना बदला", + "billingPeriod": "बिलिंग कालावधी", + "periodButtonLabel": "कालावधी संपादित करा" + }, + "paymentDetails": { + "title": "पेमेंट तपशील", + "methodLabel": "पेमेंट पद्धत", + "methodButtonLabel": "पद्धत संपादित करा" + }, + "addons": { + "title": "ऍड-ऑन्स", + "addLabel": "जोडा", + "removeLabel": "काढा", + "renewLabel": "नवीन करा", + "aiMax": { + "label": "AI Max", + "description": "अमर्यादित AI आणि प्रगत मॉडेल्स अनलॉक करा", + "activeDescription": "पुढील बिलिंग तारीख {} आहे", + "canceledDescription": "AI Max {} पर्यंत उपलब्ध असेल" + }, + "aiOnDevice": { + "label": "मॅकसाठी ऑन-डिव्हाइस AI", + "description": "तुमच्या डिव्हाइसवर अमर्यादित ऑन-डिव्हाइस AI अनलॉक करा", + "activeDescription": "पुढील बिलिंग तारीख {} आहे", + "canceledDescription": "मॅकसाठी ऑन-डिव्हाइस AI {} पर्यंत उपलब्ध असेल" + }, + "removeDialog": { + "title": "{} काढा", + "description": "तुम्हाला {plan} काढायचे आहे का? तुम्हाला तत्काळ {plan} चे सर्व फिचर्स आणि फायदे वापरण्याचा अधिकार गमावावा लागेल." + } + }, + "currentPeriodBadge": "सद्य कालावधी", + "changePeriod": "कालावधी बदला", + "planPeriod": "{} कालावधी", + "monthlyInterval": "मासिक", + "monthlyPriceInfo": "प्रति सदस्य, मासिक बिलिंग", + "annualInterval": "वार्षिक", + "annualPriceInfo": "प्रति सदस्य, वार्षिक बिलिंग" +}, + "comparePlanDialog": { + "title": "योजना तुलना आणि निवड", + "planFeatures": "योजनेची\nवैशिष्ट्ये", + "current": "सध्याची", + "actions": { + "upgrade": "अपग्रेड करा", + "downgrade": "डाऊनग्रेड करा", + "current": "सध्याची" + }, + "freePlan": { + "title": "फ्री", + "description": "२ सदस्यांपर्यंत व्यक्तींसाठी सर्व काही व्यवस्थित करण्यासाठी", + "price": "{}", + "priceInfo": "सदैव फ्री" + }, + "proPlan": { + "title": "प्रो", + "description": "लहान टीम्ससाठी प्रोजेक्ट्स व ज्ञान व्यवस्थापनासाठी", + "price": "{}", + "priceInfo": "प्रति सदस्य प्रति महिना\nवार्षिक बिलिंग\n\n{} मासिक बिलिंगसाठी" + }, + "planLabels": { + "itemOne": "वर्कस्पेसेस", + "itemTwo": "सदस्य", + "itemThree": "स्टोरेज", + "itemFour": "रिअल-टाइम सहकार्य", + "itemFive": "मोबाईल अ‍ॅप", + "itemSix": "AI प्रतिसाद", + "itemSeven": "AI प्रतिमा", + "itemFileUpload": "फाइल अपलोड", + "customNamespace": "सानुकूल नेमस्पेस", + "tooltipSix": "‘Lifetime’ म्हणजे या प्रतिसादांची मर्यादा कधीही रीसेट केली जात नाही", + "intelligentSearch": "स्मार्ट शोध", + "tooltipSeven": "तुमच्या कार्यक्षेत्रासाठी URL चा भाग सानुकूलित करू देते", + "customNamespaceTooltip": "सानुकूल प्रकाशित साइट URL" + }, + "freeLabels": { + "itemOne": "प्रत्येक वर्कस्पेसवर शुल्क", + "itemTwo": "२ पर्यंत", + "itemThree": "५ GB", + "itemFour": "होय", + "itemFive": "होय", + "itemSix": "१० कायमस्वरूपी", + "itemSeven": "२ कायमस्वरूपी", + "itemFileUpload": "७ MB पर्यंत", + "intelligentSearch": "स्मार्ट शोध" + }, + "proLabels": { + "itemOne": "प्रत्येक वर्कस्पेसवर शुल्क", + "itemTwo": "१० पर्यंत", + "itemThree": "अमर्यादित", + "itemFour": "होय", + "itemFive": "होय", + "itemSix": "अमर्यादित", + "itemSeven": "दर महिन्याला १० प्रतिमा", + "itemFileUpload": "अमर्यादित", + "intelligentSearch": "स्मार्ट शोध" + }, + "paymentSuccess": { + "title": "तुम्ही आता {} योजनेवर आहात!", + "description": "तुमचे पेमेंट यशस्वीरित्या पूर्ण झाले असून तुमची योजना @:appName {} मध्ये अपग्रेड झाली आहे. तुम्ही 'योजना' पृष्ठावर तपशील पाहू शकता." + }, + "downgradeDialog": { + "title": "तुम्हाला योजना डाऊनग्रेड करायची आहे का?", + "description": "तुमची योजना डाऊनग्रेड केल्यास तुम्ही फ्री योजनेवर परत जाल. काही सदस्यांना या कार्यक्षेत्राचा प्रवेश मिळणार नाही आणि तुम्हाला स्टोरेज मर्यादेप्रमाणे जागा रिकामी करावी लागू शकते.", + "downgradeLabel": "योजना डाऊनग्रेड करा" + } +}, + "cancelSurveyDialog": { + "title": "तुम्ही जात आहात याचे दुःख आहे", + "description": "तुम्ही जात आहात याचे आम्हाला खरोखरच दुःख वाटते. @:appName सुधारण्यासाठी तुमचा अभिप्राय आम्हाला महत्त्वाचा वाटतो. कृपया काही क्षण घेऊन काही प्रश्नांची उत्तरे द्या.", + "commonOther": "इतर", + "otherHint": "तुमचे उत्तर येथे लिहा", + "questionOne": { + "question": "तुम्ही तुमची @:appName Pro सदस्यता का रद्द केली?", + "answerOne": "खर्च खूप जास्त आहे", + "answerTwo": "वैशिष्ट्ये अपेक्षांनुसार नव्हती", + "answerThree": "यापेक्षा चांगला पर्याय सापडला", + "answerFour": "वापर फारसा केला नाही, त्यामुळे खर्च योग्य वाटला नाही", + "answerFive": "सेवा समस्या किंवा तांत्रिक अडचणी" + }, + "questionTwo": { + "question": "भविष्यात @:appName Pro सदस्यता पुन्हा घेण्याची शक्यता किती आहे?", + "answerOne": "खूप शक्यता आहे", + "answerTwo": "काहीशी शक्यता आहे", + "answerThree": "निश्चित नाही", + "answerFour": "अल्प शक्यता", + "answerFive": "एकदम कमी शक्यता" + }, + "questionThree": { + "question": "तुमच्या सदस्यत्वादरम्यान कोणते Pro वैशिष्ट्य सर्वात उपयुक्त वाटले?", + "answerOne": "अनेक वापरकर्त्यांशी सहकार्य", + "answerTwo": "लांब कालावधीची आवृत्ती इतिहास", + "answerThree": "अमर्यादित AI प्रतिसाद", + "answerFour": "स्थानिक AI मॉडेल्सचा प्रवेश" + }, + "questionFour": { + "question": "@:appName वापरण्याचा तुमचा एकूण अनुभव कसा होता?", + "answerOne": "खूप छान", + "answerTwo": "चांगला", + "answerThree": "सरासरी", + "answerFour": "सरासरीपेक्षा कमी", + "answerFive": "असंतोषजनक" + } +}, + "common": { + "uploadingFile": "फाईल अपलोड होत आहे. कृपया अ‍ॅप बंद करू नका", + "uploadNotionSuccess": "तुमची Notion zip फाईल यशस्वीरित्या अपलोड झाली आहे. आयात पूर्ण झाल्यानंतर तुम्हाला पुष्टीकरण ईमेल मिळेल", + "reset": "रीसेट करा" +}, + "menu": { + "appearance": "दृश्यरूप", + "language": "भाषा", + "user": "वापरकर्ता", + "files": "फाईल्स", + "notifications": "सूचना", + "open": "सेटिंग्ज उघडा", + "logout": "लॉगआउट", + "logoutPrompt": "तुम्हाला नक्की लॉगआउट करायचे आहे का?", + "selfEncryptionLogoutPrompt": "तुम्हाला खात्रीने लॉगआउट करायचे आहे का? कृपया खात्री करा की तुम्ही एनक्रिप्शन गुप्तकी कॉपी केली आहे", + "syncSetting": "सिंक्रोनायझेशन सेटिंग", + "cloudSettings": "क्लाऊड सेटिंग्ज", + "enableSync": "सिंक्रोनायझेशन सक्षम करा", + "enableSyncLog": "सिंक लॉगिंग सक्षम करा", + "enableSyncLogWarning": "सिंक समस्यांचे निदान करण्यात मदतीसाठी धन्यवाद. हे तुमचे डॉक्युमेंट संपादन स्थानिक फाईलमध्ये लॉग करेल. कृपया सक्षम केल्यानंतर अ‍ॅप बंद करून पुन्हा उघडा", + "enableEncrypt": "डेटा एन्क्रिप्ट करा", + "cloudURL": "बेस URL", + "webURL": "वेब URL", + "invalidCloudURLScheme": "अवैध स्कीम", + "cloudServerType": "क्लाऊड सर्व्हर", + "cloudServerTypeTip": "कृपया लक्षात घ्या की क्लाऊड सर्व्हर बदलल्यास तुम्ही सध्या लॉगिन केलेले खाते लॉगआउट होऊ शकते", + "cloudLocal": "स्थानिक", + "cloudAppFlowy": "@:appName Cloud", + "cloudAppFlowySelfHost": "@:appName क्लाऊड सेल्फ-होस्टेड", + "appFlowyCloudUrlCanNotBeEmpty": "क्लाऊड URL रिकामा असू शकत नाही", + "clickToCopy": "क्लिपबोर्डवर कॉपी करा", + "selfHostStart": "जर तुमच्याकडे सर्व्हर नसेल, तर कृपया हे पाहा", + "selfHostContent": "दस्तऐवज", + "selfHostEnd": "तुमचा स्वतःचा सर्व्हर कसा होस्ट करावा यासाठी मार्गदर्शनासाठी", + "pleaseInputValidURL": "कृपया वैध URL टाका", + "changeUrl": "सेल्फ-होस्टेड URL {} मध्ये बदला", + "cloudURLHint": "तुमच्या सर्व्हरचा बेस URL टाका", + "webURLHint": "तुमच्या वेब सर्व्हरचा बेस URL टाका", + "cloudWSURL": "वेबसॉकेट URL", + "cloudWSURLHint": "तुमच्या सर्व्हरचा वेबसॉकेट पत्ता टाका", + "restartApp": "अ‍ॅप रीस्टार्ट करा", + "restartAppTip": "बदल प्रभावी होण्यासाठी अ‍ॅप रीस्टार्ट करा. कृपया लक्षात घ्या की यामुळे सध्याचे खाते लॉगआउट होऊ शकते.", + "changeServerTip": "सर्व्हर बदलल्यानंतर, बदल लागू करण्यासाठी तुम्हाला 'रीस्टार्ट' बटणावर क्लिक करणे आवश्यक आहे", + "enableEncryptPrompt": "तुमचा डेटा सुरक्षित करण्यासाठी एन्क्रिप्शन सक्रिय करा. ही गुप्तकी सुरक्षित ठेवा; एकदा सक्षम केल्यावर ती बंद करता येणार नाही. जर हरवली तर तुमचा डेटा पुन्हा मिळवता येणार नाही. कॉपी करण्यासाठी क्लिक करा", + "inputEncryptPrompt": "कृपया खालीलसाठी तुमची एनक्रिप्शन गुप्तकी टाका:", + "clickToCopySecret": "गुप्तकी कॉपी करण्यासाठी क्लिक करा", + "configServerSetting": "तुमच्या सर्व्हर सेटिंग्ज कॉन्फिगर करा", + "configServerGuide": "`Quick Start` निवडल्यानंतर, `Settings` → \"Cloud Settings\" मध्ये जा आणि तुमचा सेल्फ-होस्टेड सर्व्हर कॉन्फिगर करा.", + "inputTextFieldHint": "तुमची गुप्तकी", + "historicalUserList": "वापरकर्ता लॉगिन इतिहास", + "historicalUserListTooltip": "ही यादी तुमची अनामिक खाती दर्शवते. तपशील पाहण्यासाठी खात्यावर क्लिक करा. अनामिक खाती 'सुरुवात करा' बटणावर क्लिक करून तयार केली जातात", + "openHistoricalUser": "अनामिक खाते उघडण्यासाठी क्लिक करा", + "customPathPrompt": "@:appName डेटा फोल्डर Google Drive सारख्या क्लाऊड-सिंक फोल्डरमध्ये साठवणे धोकादायक ठरू शकते. फोल्डरमधील डेटाबेस अनेक ठिकाणांवरून एकाच वेळी ऍक्सेस/संपादित केल्यास डेटा सिंक समस्या किंवा भ्रष्ट होण्याची शक्यता असते.", + "importAppFlowyData": "बाह्य @:appName फोल्डरमधून डेटा आयात करा", + "importingAppFlowyDataTip": "डेटा आयात सुरू आहे. कृपया अ‍ॅप बंद करू नका", + "importAppFlowyDataDescription": "बाह्य @:appName फोल्डरमधून डेटा कॉपी करून सध्याच्या AppFlowy डेटा फोल्डरमध्ये आयात करा", + "importSuccess": "@:appName डेटा फोल्डर यशस्वीरित्या आयात झाला", + "importFailed": "@:appName डेटा फोल्डर आयात करण्यात अयशस्वी", + "importGuide": "अधिक माहितीसाठी, कृपया संदर्भित दस्तऐवज पहा" +}, + "notifications": { + "enableNotifications": { + "label": "सूचना सक्षम करा", + "hint": "स्थानिक सूचना दिसू नयेत यासाठी बंद करा." + }, + "showNotificationsIcon": { + "label": "सूचना चिन्ह दाखवा", + "hint": "साइडबारमध्ये सूचना चिन्ह लपवण्यासाठी बंद करा." + }, + "archiveNotifications": { + "allSuccess": "सर्व सूचना यशस्वीरित्या संग्रहित केल्या", + "success": "सूचना यशस्वीरित्या संग्रहित केली" + }, + "markAsReadNotifications": { + "allSuccess": "सर्व वाचलेल्या म्हणून चिन्हांकित केल्या", + "success": "वाचलेले म्हणून चिन्हांकित केले" + }, + "action": { + "markAsRead": "वाचलेले म्हणून चिन्हांकित करा", + "multipleChoice": "अधिक निवडा", + "archive": "संग्रहित करा" + }, + "settings": { + "settings": "सेटिंग्ज", + "markAllAsRead": "सर्व वाचलेले म्हणून चिन्हांकित करा", + "archiveAll": "सर्व संग्रहित करा" + }, + "emptyInbox": { + "title": "इनबॉक्स झिरो!", + "description": "इथे सूचना मिळवण्यासाठी रिमाइंडर सेट करा." + }, + "emptyUnread": { + "title": "कोणतीही न वाचलेली सूचना नाही", + "description": "तुम्ही सर्व वाचले आहे!" + }, + "emptyArchived": { + "title": "कोणतीही संग्रहित सूचना नाही", + "description": "संग्रहित सूचना इथे दिसतील." + }, + "tabs": { + "inbox": "इनबॉक्स", + "unread": "न वाचलेले", + "archived": "संग्रहित" + }, + "refreshSuccess": "सूचना यशस्वीरित्या रीफ्रेश केल्या", + "titles": { + "notifications": "सूचना", + "reminder": "रिमाइंडर" + } +}, + "appearance": { + "resetSetting": "रीसेट", + "fontFamily": { + "label": "फॉन्ट फॅमिली", + "search": "शोध", + "defaultFont": "सिस्टम" + }, + "themeMode": { + "label": "थीम मोड", + "light": "लाइट मोड", + "dark": "डार्क मोड", + "system": "सिस्टमशी जुळवा" + }, + "fontScaleFactor": "फॉन्ट स्केल घटक", + "displaySize": "डिस्प्ले आकार", + "documentSettings": { + "cursorColor": "डॉक्युमेंट कर्सरचा रंग", + "selectionColor": "डॉक्युमेंट निवडीचा रंग", + "width": "डॉक्युमेंटची रुंदी", + "changeWidth": "बदला", + "pickColor": "रंग निवडा", + "colorShade": "रंगाची छटा", + "opacity": "अपारदर्शकता", + "hexEmptyError": "Hex रंग रिकामा असू शकत नाही", + "hexLengthError": "Hex व्हॅल्यू 6 अंकांची असावी", + "hexInvalidError": "अवैध Hex व्हॅल्यू", + "opacityEmptyError": "अपारदर्शकता रिकामी असू शकत नाही", + "opacityRangeError": "अपारदर्शकता 1 ते 100 दरम्यान असावी", + "app": "अ‍ॅप", + "flowy": "Flowy", + "apply": "लागू करा" + }, + "layoutDirection": { + "label": "लेआउट दिशा", + "hint": "तुमच्या स्क्रीनवरील कंटेंटचा प्रवाह नियंत्रित करा — डावीकडून उजवीकडे किंवा उजवीकडून डावीकडे.", + "ltr": "LTR", + "rtl": "RTL" + }, + "textDirection": { + "label": "मूलभूत मजकूर दिशा", + "hint": "मजकूर डावीकडून सुरू व्हावा की उजवीकडून याचे पूर्वनिर्धारित निर्धारण करा.", + "ltr": "LTR", + "rtl": "RTL", + "auto": "स्वयं", + "fallback": "लेआउट दिशेशी जुळवा" + }, + "themeUpload": { + "button": "अपलोड", + "uploadTheme": "थीम अपलोड करा", + "description": "खालील बटण वापरून तुमची स्वतःची @:appName थीम अपलोड करा.", + "loading": "कृपया प्रतीक्षा करा. आम्ही तुमची थीम पडताळत आहोत आणि अपलोड करत आहोत...", + "uploadSuccess": "तुमची थीम यशस्वीरित्या अपलोड झाली आहे", + "deletionFailure": "थीम हटवण्यात अयशस्वी. कृपया ती मॅन्युअली हटवून पाहा.", + "filePickerDialogTitle": ".flowy_plugin फाईल निवडा", + "urlUploadFailure": "URL उघडण्यात अयशस्वी: {}" + }, + "theme": "थीम", + "builtInsLabel": "अंतर्गत थीम्स", + "pluginsLabel": "प्लगइन्स", + "dateFormat": { + "label": "दिनांक फॉरमॅट", + "local": "स्थानिक", + "us": "US", + "iso": "ISO", + "friendly": "अनौपचारिक", + "dmy": "D/M/Y" + }, + "timeFormat": { + "label": "वेळ फॉरमॅट", + "twelveHour": "१२ तास", + "twentyFourHour": "२४ तास" + }, + "showNamingDialogWhenCreatingPage": "पृष्ठ तयार करताना नाव विचारणारा डायलॉग दाखवा", + "enableRTLToolbarItems": "RTL टूलबार आयटम्स सक्षम करा", + "members": { + "title": "सदस्य सेटिंग्ज", + "inviteMembers": "सदस्यांना आमंत्रण द्या", + "inviteHint": "ईमेलद्वारे आमंत्रण द्या", + "sendInvite": "आमंत्रण पाठवा", + "copyInviteLink": "आमंत्रण दुवा कॉपी करा", + "label": "सदस्य", + "user": "वापरकर्ता", + "role": "भूमिका", + "removeFromWorkspace": "वर्कस्पेसमधून काढा", + "removeFromWorkspaceSuccess": "वर्कस्पेसमधून यशस्वीरित्या काढले", + "removeFromWorkspaceFailed": "वर्कस्पेसमधून काढण्यात अयशस्वी", + "owner": "मालक", + "guest": "अतिथी", + "member": "सदस्य", + "memberHintText": "सदस्य पृष्ठे वाचू व संपादित करू शकतो", + "guestHintText": "अतिथी पृष्ठे वाचू शकतो, प्रतिक्रिया देऊ शकतो, टिप्पणी करू शकतो, आणि परवानगी असल्यास काही पृष्ठे संपादित करू शकतो.", + "emailInvalidError": "अवैध ईमेल, कृपया तपासा व पुन्हा प्रयत्न करा", + "emailSent": "ईमेल पाठवला गेला आहे, कृपया इनबॉक्स तपासा", + "members": "सदस्य", + "membersCount": { + "zero": "{} सदस्य", + "one": "{} सदस्य", + "other": "{} सदस्य" + }, + "inviteFailedDialogTitle": "आमंत्रण पाठवण्यात अयशस्वी", + "inviteFailedMemberLimit": "सदस्य मर्यादा गाठली आहे, अधिक सदस्यांसाठी कृपया अपग्रेड करा.", + "inviteFailedMemberLimitMobile": "तुमच्या वर्कस्पेसने सदस्य मर्यादा गाठली आहे.", + "memberLimitExceeded": "सदस्य मर्यादा गाठली आहे, अधिक सदस्य आमंत्रित करण्यासाठी कृपया ", + "memberLimitExceededUpgrade": "अपग्रेड करा", + "memberLimitExceededPro": "सदस्य मर्यादा गाठली आहे, अधिक सदस्य आवश्यक असल्यास संपर्क करा", + "memberLimitExceededProContact": "support@appflowy.io", + "failedToAddMember": "सदस्य जोडण्यात अयशस्वी", + "addMemberSuccess": "सदस्य यशस्वीरित्या जोडला गेला", + "removeMember": "सदस्य काढा", + "areYouSureToRemoveMember": "तुम्हाला हा सदस्य काढायचा आहे का?", + "inviteMemberSuccess": "आमंत्रण यशस्वीरित्या पाठवले गेले", + "failedToInviteMember": "सदस्य आमंत्रित करण्यात अयशस्वी", + "workspaceMembersError": "अरे! काहीतरी चूक झाली आहे", + "workspaceMembersErrorDescription": "आम्ही सध्या सदस्यांची यादी लोड करू शकत नाही. कृपया नंतर पुन्हा प्रयत्न करा" + } +}, + "files": { + "copy": "कॉपी करा", + "defaultLocation": "फाईल्स आणि डेटाचा संचय स्थान", + "exportData": "तुमचा डेटा निर्यात करा", + "doubleTapToCopy": "पथ कॉपी करण्यासाठी दोनदा टॅप करा", + "restoreLocation": "@:appName चे मूळ स्थान पुनर्संचयित करा", + "customizeLocation": "इतर फोल्डर उघडा", + "restartApp": "बदल लागू करण्यासाठी कृपया अ‍ॅप रीस्टार्ट करा.", + "exportDatabase": "डेटाबेस निर्यात करा", + "selectFiles": "निर्यात करण्यासाठी फाईल्स निवडा", + "selectAll": "सर्व निवडा", + "deselectAll": "सर्व निवड रद्द करा", + "createNewFolder": "नवीन फोल्डर तयार करा", + "createNewFolderDesc": "तुमचा डेटा कुठे साठवायचा हे सांगा", + "defineWhereYourDataIsStored": "तुमचा डेटा कुठे साठवला जातो हे ठरवा", + "open": "उघडा", + "openFolder": "आधीक फोल्डर उघडा", + "openFolderDesc": "तुमच्या विद्यमान @:appName फोल्डरमध्ये वाचन व लेखन करा", + "folderHintText": "फोल्डरचे नाव", + "location": "नवीन फोल्डर तयार करत आहे", + "locationDesc": "तुमच्या @:appName डेटासाठी नाव निवडा", + "browser": "ब्राउझ करा", + "create": "तयार करा", + "set": "सेट करा", + "folderPath": "फोल्डर साठवण्याचा मार्ग", + "locationCannotBeEmpty": "मार्ग रिकामा असू शकत नाही", + "pathCopiedSnackbar": "फाईल संचय मार्ग क्लिपबोर्डवर कॉपी केला!", + "changeLocationTooltips": "डेटा डिरेक्टरी बदला", + "change": "बदला", + "openLocationTooltips": "इतर डेटा डिरेक्टरी उघडा", + "openCurrentDataFolder": "सध्याची डेटा डिरेक्टरी उघडा", + "recoverLocationTooltips": "@:appName च्या मूळ डेटा डिरेक्टरीवर रीसेट करा", + "exportFileSuccess": "फाईल यशस्वीरित्या निर्यात झाली!", + "exportFileFail": "फाईल निर्यात करण्यात अयशस्वी!", + "export": "निर्यात करा", + "clearCache": "कॅशे साफ करा", + "clearCacheDesc": "प्रतिमा लोड होत नाहीत किंवा फॉन्ट अयोग्यरित्या दिसत असल्यास, कॅशे साफ करून पाहा. ही क्रिया तुमचा वापरकर्ता डेटा हटवणार नाही.", + "areYouSureToClearCache": "तुम्हाला नक्की कॅशे साफ करायची आहे का?", + "clearCacheSuccess": "कॅशे यशस्वीरित्या साफ झाली!" +}, + "user": { + "name": "नाव", + "email": "ईमेल", + "tooltipSelectIcon": "चिन्ह निवडा", + "selectAnIcon": "चिन्ह निवडा", + "pleaseInputYourOpenAIKey": "कृपया तुमची AI की टाका", + "clickToLogout": "सध्याचा वापरकर्ता लॉगआउट करण्यासाठी क्लिक करा" +}, + "mobile": { + "personalInfo": "वैयक्तिक माहिती", + "username": "वापरकर्तानाव", + "usernameEmptyError": "वापरकर्तानाव रिकामे असू शकत नाही", + "about": "विषयी", + "pushNotifications": "पुश सूचना", + "support": "सपोर्ट", + "joinDiscord": "Discord मध्ये सहभागी व्हा", + "privacyPolicy": "गोपनीयता धोरण", + "userAgreement": "वापरकर्ता करार", + "termsAndConditions": "अटी व शर्ती", + "userprofileError": "वापरकर्ता प्रोफाइल लोड करण्यात अयशस्वी", + "userprofileErrorDescription": "कृपया लॉगआउट करून पुन्हा लॉगिन करा आणि त्रुटी अजूनही येते का ते पहा.", + "selectLayout": "लेआउट निवडा", + "selectStartingDay": "सप्ताहाचा प्रारंभ दिवस निवडा", + "version": "आवृत्ती" +}, + "grid": { + "deleteView": "तुम्हाला हे दृश्य हटवायचे आहे का?", + "createView": "नवीन", + "title": { + "placeholder": "नाव नाही" + }, + "settings": { + "filter": "फिल्टर", + "sort": "क्रमवारी", + "sortBy": "यावरून क्रमवारी लावा", + "properties": "गुणधर्म", + "reorderPropertiesTooltip": "गुणधर्मांचे स्थान बदला", + "group": "समूह", + "addFilter": "फिल्टर जोडा", + "deleteFilter": "फिल्टर हटवा", + "filterBy": "यावरून फिल्टर करा", + "typeAValue": "मूल्य लिहा...", + "layout": "लेआउट", + "compactMode": "कॉम्पॅक्ट मोड", + "databaseLayout": "लेआउट", + "viewList": { + "zero": "० दृश्ये", + "one": "{count} दृश्य", + "other": "{count} दृश्ये" + }, + "editView": "दृश्य संपादित करा", + "boardSettings": "बोर्ड सेटिंग", + "calendarSettings": "कॅलेंडर सेटिंग", + "createView": "नवीन दृश्य", + "duplicateView": "दृश्याची प्रत बनवा", + "deleteView": "दृश्य हटवा", + "numberOfVisibleFields": "{} दर्शविले" + }, + "filter": { + "empty": "कोणतेही सक्रिय फिल्टर नाहीत", + "addFilter": "फिल्टर जोडा", + "cannotFindCreatableField": "फिल्टर करण्यासाठी योग्य फील्ड सापडले नाही", + "conditon": "अट", + "where": "जिथे" + }, + "textFilter": { + "contains": "अंतर्भूत आहे", + "doesNotContain": "अंतर्भूत नाही", + "endsWith": "याने समाप्त होते", + "startWith": "याने सुरू होते", + "is": "आहे", + "isNot": "नाही", + "isEmpty": "रिकामे आहे", + "isNotEmpty": "रिकामे नाही", + "choicechipPrefix": { + "isNot": "नाही", + "startWith": "याने सुरू होते", + "endWith": "याने समाप्त होते", + "isEmpty": "रिकामे आहे", + "isNotEmpty": "रिकामे नाही" + } + }, + "checkboxFilter": { + "isChecked": "निवडलेले आहे", + "isUnchecked": "निवडलेले नाही", + "choicechipPrefix": { + "is": "आहे" + } + }, + "checklistFilter": { + "isComplete": "पूर्ण झाले आहे", + "isIncomplted": "अपूर्ण आहे" + }, + "selectOptionFilter": { + "is": "आहे", + "isNot": "नाही", + "contains": "अंतर्भूत आहे", + "doesNotContain": "अंतर्भूत नाही", + "isEmpty": "रिकामे आहे", + "isNotEmpty": "रिकामे नाही" +}, +"dateFilter": { + "is": "या दिवशी आहे", + "before": "पूर्वी आहे", + "after": "नंतर आहे", + "onOrBefore": "या दिवशी किंवा त्याआधी आहे", + "onOrAfter": "या दिवशी किंवा त्यानंतर आहे", + "between": "दरम्यान आहे", + "empty": "रिकामे आहे", + "notEmpty": "रिकामे नाही", + "startDate": "सुरुवातीची तारीख", + "endDate": "शेवटची तारीख", + "choicechipPrefix": { + "before": "पूर्वी", + "after": "नंतर", + "between": "दरम्यान", + "onOrBefore": "या दिवशी किंवा त्याआधी", + "onOrAfter": "या दिवशी किंवा त्यानंतर", + "isEmpty": "रिकामे आहे", + "isNotEmpty": "रिकामे नाही" + } +}, +"numberFilter": { + "equal": "बरोबर आहे", + "notEqual": "बरोबर नाही", + "lessThan": "पेक्षा कमी आहे", + "greaterThan": "पेक्षा जास्त आहे", + "lessThanOrEqualTo": "किंवा कमी आहे", + "greaterThanOrEqualTo": "किंवा जास्त आहे", + "isEmpty": "रिकामे आहे", + "isNotEmpty": "रिकामे नाही" +}, +"field": { + "label": "गुणधर्म", + "hide": "गुणधर्म लपवा", + "show": "गुणधर्म दर्शवा", + "insertLeft": "डावीकडे जोडा", + "insertRight": "उजवीकडे जोडा", + "duplicate": "प्रत बनवा", + "delete": "हटवा", + "wrapCellContent": "पाठ लपेटा", + "clear": "सेल्स रिकामे करा", + "switchPrimaryFieldTooltip": "प्राथमिक फील्डचा प्रकार बदलू शकत नाही", + "textFieldName": "मजकूर", + "checkboxFieldName": "चेकबॉक्स", + "dateFieldName": "तारीख", + "updatedAtFieldName": "शेवटचे अपडेट", + "createdAtFieldName": "तयार झाले", + "numberFieldName": "संख्या", + "singleSelectFieldName": "सिंगल सिलेक्ट", + "multiSelectFieldName": "मल्टीसिलेक्ट", + "urlFieldName": "URL", + "checklistFieldName": "चेकलिस्ट", + "relationFieldName": "संबंध", + "summaryFieldName": "AI सारांश", + "timeFieldName": "वेळ", + "mediaFieldName": "फाईल्स आणि मीडिया", + "translateFieldName": "AI भाषांतर", + "translateTo": "मध्ये भाषांतर करा", + "numberFormat": "संख्या स्वरूप", + "dateFormat": "तारीख स्वरूप", + "includeTime": "वेळ जोडा", + "isRange": "शेवटची तारीख", + "dateFormatFriendly": "महिना दिवस, वर्ष", + "dateFormatISO": "वर्ष-महिना-दिनांक", + "dateFormatLocal": "महिना/दिवस/वर्ष", + "dateFormatUS": "वर्ष/महिना/दिवस", + "dateFormatDayMonthYear": "दिवस/महिना/वर्ष", + "timeFormat": "वेळ स्वरूप", + "invalidTimeFormat": "अवैध स्वरूप", + "timeFormatTwelveHour": "१२ तास", + "timeFormatTwentyFourHour": "२४ तास", + "clearDate": "तारीख हटवा", + "dateTime": "तारीख व वेळ", + "startDateTime": "सुरुवातीची तारीख व वेळ", + "endDateTime": "शेवटची तारीख व वेळ", + "failedToLoadDate": "तारीख मूल्य लोड करण्यात अयशस्वी", + "selectTime": "वेळ निवडा", + "selectDate": "तारीख निवडा", + "visibility": "दृश्यता", + "propertyType": "गुणधर्माचा प्रकार", + "addSelectOption": "पर्याय जोडा", + "typeANewOption": "नवीन पर्याय लिहा", + "optionTitle": "पर्याय", + "addOption": "पर्याय जोडा", + "editProperty": "गुणधर्म संपादित करा", + "newProperty": "नवीन गुणधर्म", + "openRowDocument": "पृष्ठ म्हणून उघडा", + "deleteFieldPromptMessage": "तुम्हाला खात्री आहे का? हा गुणधर्म आणि त्याचा डेटा हटवला जाईल", + "clearFieldPromptMessage": "तुम्हाला खात्री आहे का? या कॉलममधील सर्व सेल्स रिकामे होतील", + "newColumn": "नवीन कॉलम", + "format": "स्वरूप", + "reminderOnDateTooltip": "या सेलमध्ये अनुस्मारक शेड्यूल केले आहे", + "optionAlreadyExist": "पर्याय आधीच अस्तित्वात आहे" +}, + "rowPage": { + "newField": "नवीन फील्ड जोडा", + "fieldDragElementTooltip": "मेनू उघडण्यासाठी क्लिक करा", + "showHiddenFields": { + "one": "{count} लपलेले फील्ड दाखवा", + "many": "{count} लपलेली फील्ड दाखवा", + "other": "{count} लपलेली फील्ड दाखवा" + }, + "hideHiddenFields": { + "one": "{count} लपलेले फील्ड लपवा", + "many": "{count} लपलेली फील्ड लपवा", + "other": "{count} लपलेली फील्ड लपवा" + }, + "openAsFullPage": "पूर्ण पृष्ठ म्हणून उघडा", + "moreRowActions": "अधिक पंक्ती क्रिया" +}, +"sort": { + "ascending": "चढत्या क्रमाने", + "descending": "उतरत्या क्रमाने", + "by": "द्वारे", + "empty": "सक्रिय सॉर्ट्स नाहीत", + "cannotFindCreatableField": "सॉर्टसाठी योग्य फील्ड सापडले नाही", + "deleteAllSorts": "सर्व सॉर्ट्स हटवा", + "addSort": "सॉर्ट जोडा", + "sortsActive": "सॉर्टिंग करत असताना {intention} करू शकत नाही", + "removeSorting": "या दृश्यातील सर्व सॉर्ट्स हटवायचे आहेत का?", + "fieldInUse": "या फील्डवर आधीच सॉर्टिंग चालू आहे" +}, +"row": { + "label": "पंक्ती", + "duplicate": "प्रत बनवा", + "delete": "हटवा", + "titlePlaceholder": "शीर्षक नाही", + "textPlaceholder": "रिक्त", + "copyProperty": "गुणधर्म क्लिपबोर्डवर कॉपी केला", + "count": "संख्या", + "newRow": "नवीन पंक्ती", + "loadMore": "अधिक लोड करा", + "action": "क्रिया", + "add": "खाली जोडा वर क्लिक करा", + "drag": "हलवण्यासाठी ड्रॅग करा", + "deleteRowPrompt": "तुम्हाला खात्री आहे का की ही पंक्ती हटवायची आहे? ही क्रिया पूर्ववत करता येणार नाही.", + "deleteCardPrompt": "तुम्हाला खात्री आहे का की हे कार्ड हटवायचे आहे? ही क्रिया पूर्ववत करता येणार नाही.", + "dragAndClick": "हलवण्यासाठी ड्रॅग करा, मेनू उघडण्यासाठी क्लिक करा", + "insertRecordAbove": "वर रेकॉर्ड जोडा", + "insertRecordBelow": "खाली रेकॉर्ड जोडा", + "noContent": "माहिती नाही", + "reorderRowDescription": "पंक्तीचे पुन्हा क्रमांकन", + "createRowAboveDescription": "वर पंक्ती तयार करा", + "createRowBelowDescription": "खाली पंक्ती जोडा" +}, +"selectOption": { + "create": "तयार करा", + "purpleColor": "जांभळा", + "pinkColor": "गुलाबी", + "lightPinkColor": "फिकट गुलाबी", + "orangeColor": "नारंगी", + "yellowColor": "पिवळा", + "limeColor": "लिंबू", + "greenColor": "हिरवा", + "aquaColor": "आक्वा", + "blueColor": "निळा", + "deleteTag": "टॅग हटवा", + "colorPanelTitle": "रंग", + "panelTitle": "पर्याय निवडा किंवा नवीन तयार करा", + "searchOption": "पर्याय शोधा", + "searchOrCreateOption": "पर्याय शोधा किंवा तयार करा", + "createNew": "नवीन तयार करा", + "orSelectOne": "किंवा पर्याय निवडा", + "typeANewOption": "नवीन पर्याय टाइप करा", + "tagName": "टॅग नाव" +}, +"checklist": { + "taskHint": "कार्याचे वर्णन", + "addNew": "नवीन कार्य जोडा", + "submitNewTask": "तयार करा", + "hideComplete": "पूर्ण कार्ये लपवा", + "showComplete": "सर्व कार्ये दाखवा" +}, +"url": { + "launch": "बाह्य ब्राउझरमध्ये लिंक उघडा", + "copy": "लिंक क्लिपबोर्डवर कॉपी करा", + "textFieldHint": "URL टाका", + "copiedNotification": "क्लिपबोर्डवर कॉपी केले!" +}, +"relation": { + "relatedDatabasePlaceLabel": "संबंधित डेटाबेस", + "relatedDatabasePlaceholder": "काही नाही", + "inRelatedDatabase": "या मध्ये", + "rowSearchTextFieldPlaceholder": "शोध", + "noDatabaseSelected": "कोणताही डेटाबेस निवडलेला नाही, कृपया खालील यादीतून एक निवडा:", + "emptySearchResult": "कोणतीही नोंद सापडली नाही", + "linkedRowListLabel": "{count} लिंक केलेल्या पंक्ती", + "unlinkedRowListLabel": "आणखी एक पंक्ती लिंक करा" +}, +"menuName": "ग्रिड", +"referencedGridPrefix": "दृश्य", +"calculate": "गणना करा", +"calculationTypeLabel": { + "none": "काही नाही", + "average": "सरासरी", + "max": "कमाल", + "median": "मध्यम", + "min": "किमान", + "sum": "बेरीज", + "count": "मोजणी", + "countEmpty": "रिकाम्यांची मोजणी", + "countEmptyShort": "रिक्त", + "countNonEmpty": "रिक्त नसलेल्यांची मोजणी", + "countNonEmptyShort": "भरलेले" +}, +"media": { + "rename": "पुन्हा नाव द्या", + "download": "डाउनलोड करा", + "expand": "मोठे करा", + "delete": "हटवा", + "moreFilesHint": "+{}", + "addFileOrImage": "फाईल किंवा लिंक जोडा", + "attachmentsHint": "{}", + "addFileMobile": "फाईल जोडा", + "extraCount": "+{}", + "deleteFileDescription": "तुम्हाला खात्री आहे का की ही फाईल हटवायची आहे? ही क्रिया पूर्ववत करता येणार नाही.", + "showFileNames": "फाईलचे नाव दाखवा", + "downloadSuccess": "फाईल डाउनलोड झाली", + "downloadFailedToken": "फाईल डाउनलोड अयशस्वी, वापरकर्ता टोकन अनुपलब्ध", + "setAsCover": "कव्हर म्हणून सेट करा", + "openInBrowser": "ब्राउझरमध्ये उघडा", + "embedLink": "फाईल लिंक एम्बेड करा" + } +}, + "document": { + "menuName": "दस्तऐवज", + "date": { + "timeHintTextInTwelveHour": "01:00 PM", + "timeHintTextInTwentyFourHour": "13:00" + }, + "creating": "तयार करत आहे...", + "slashMenu": { + "board": { + "selectABoardToLinkTo": "लिंक करण्यासाठी बोर्ड निवडा", + "createANewBoard": "नवीन बोर्ड तयार करा" + }, + "grid": { + "selectAGridToLinkTo": "लिंक करण्यासाठी ग्रिड निवडा", + "createANewGrid": "नवीन ग्रिड तयार करा" + }, + "calendar": { + "selectACalendarToLinkTo": "लिंक करण्यासाठी दिनदर्शिका निवडा", + "createANewCalendar": "नवीन दिनदर्शिका तयार करा" + }, + "document": { + "selectADocumentToLinkTo": "लिंक करण्यासाठी दस्तऐवज निवडा" + }, + "name": { + "textStyle": "मजकुराची शैली", + "list": "यादी", + "toggle": "टॉगल", + "fileAndMedia": "फाईल व मीडिया", + "simpleTable": "सोपे टेबल", + "visuals": "दृश्य घटक", + "document": "दस्तऐवज", + "advanced": "प्रगत", + "text": "मजकूर", + "heading1": "शीर्षक 1", + "heading2": "शीर्षक 2", + "heading3": "शीर्षक 3", + "image": "प्रतिमा", + "bulletedList": "बुलेट यादी", + "numberedList": "क्रमांकित यादी", + "todoList": "करण्याची यादी", + "doc": "दस्तऐवज", + "linkedDoc": "पृष्ठाशी लिंक करा", + "grid": "ग्रिड", + "linkedGrid": "लिंक केलेला ग्रिड", + "kanban": "कानबन", + "linkedKanban": "लिंक केलेला कानबन", + "calendar": "दिनदर्शिका", + "linkedCalendar": "लिंक केलेली दिनदर्शिका", + "quote": "उद्धरण", + "divider": "विभाजक", + "table": "टेबल", + "callout": "महत्त्वाचा मजकूर", + "outline": "रूपरेषा", + "mathEquation": "गणिती समीकरण", + "code": "कोड", + "toggleList": "टॉगल यादी", + "toggleHeading1": "टॉगल शीर्षक 1", + "toggleHeading2": "टॉगल शीर्षक 2", + "toggleHeading3": "टॉगल शीर्षक 3", + "emoji": "इमोजी", + "aiWriter": "AI ला काहीही विचारा", + "dateOrReminder": "दिनांक किंवा स्मरणपत्र", + "photoGallery": "फोटो गॅलरी", + "file": "फाईल", + "twoColumns": "२ स्तंभ", + "threeColumns": "३ स्तंभ", + "fourColumns": "४ स्तंभ" + }, + "subPage": { + "name": "दस्तऐवज", + "keyword1": "उपपृष्ठ", + "keyword2": "पृष्ठ", + "keyword3": "चाइल्ड पृष्ठ", + "keyword4": "पृष्ठ जोडा", + "keyword5": "एम्बेड पृष्ठ", + "keyword6": "नवीन पृष्ठ", + "keyword7": "पृष्ठ तयार करा", + "keyword8": "दस्तऐवज" + } + }, + "selectionMenu": { + "outline": "रूपरेषा", + "codeBlock": "कोड ब्लॉक" + }, + "plugins": { + "referencedBoard": "संदर्भित बोर्ड", + "referencedGrid": "संदर्भित ग्रिड", + "referencedCalendar": "संदर्भित दिनदर्शिका", + "referencedDocument": "संदर्भित दस्तऐवज", + "aiWriter": { + "userQuestion": "AI ला काहीही विचारा", + "continueWriting": "लेखन सुरू ठेवा", + "fixSpelling": "स्पेलिंग व व्याकरण सुधारणा", + "improveWriting": "लेखन सुधारित करा", + "summarize": "सारांश द्या", + "explain": "स्पष्टीकरण द्या", + "makeShorter": "लहान करा", + "makeLonger": "मोठे करा" + }, + "autoGeneratorMenuItemName": "AI लेखक", +"autoGeneratorTitleName": "AI: काहीही लिहिण्यासाठी AI ला विचारा...", +"autoGeneratorLearnMore": "अधिक जाणून घ्या", +"autoGeneratorGenerate": "उत्पन्न करा", +"autoGeneratorHintText": "AI ला विचारा...", +"autoGeneratorCantGetOpenAIKey": "AI की मिळवू शकलो नाही", +"autoGeneratorRewrite": "पुन्हा लिहा", +"smartEdit": "AI ला विचारा", +"aI": "AI", +"smartEditFixSpelling": "स्पेलिंग आणि व्याकरण सुधारा", +"warning": "⚠️ AI उत्तरं चुकीची किंवा दिशाभूल करणारी असू शकतात.", +"smartEditSummarize": "सारांश द्या", +"smartEditImproveWriting": "लेखन सुधारित करा", +"smartEditMakeLonger": "लांब करा", +"smartEditCouldNotFetchResult": "AI कडून उत्तर मिळवता आले नाही", +"smartEditCouldNotFetchKey": "AI की मिळवता आली नाही", +"smartEditDisabled": "सेटिंग्जमध्ये AI कनेक्ट करा", +"appflowyAIEditDisabled": "AI वैशिष्ट्ये सक्षम करण्यासाठी साइन इन करा", +"discardResponse": "AI उत्तर फेकून द्यायचं आहे का?", +"createInlineMathEquation": "समीकरण तयार करा", +"fonts": "फॉन्ट्स", +"insertDate": "तारीख जोडा", +"emoji": "इमोजी", +"toggleList": "टॉगल यादी", +"emptyToggleHeading": "रिकामे टॉगल h{}. मजकूर जोडण्यासाठी क्लिक करा.", +"emptyToggleList": "रिकामी टॉगल यादी. मजकूर जोडण्यासाठी क्लिक करा.", +"emptyToggleHeadingWeb": "रिकामे टॉगल h{level}. मजकूर जोडण्यासाठी क्लिक करा", +"quoteList": "उद्धरण यादी", +"numberedList": "क्रमांकित यादी", +"bulletedList": "बुलेट यादी", +"todoList": "करण्याची यादी", +"callout": "ठळक मजकूर", +"simpleTable": { + "moreActions": { + "color": "रंग", + "align": "पंक्तिबद्ध करा", + "delete": "हटा", + "duplicate": "डुप्लिकेट करा", + "insertLeft": "डावीकडे घाला", + "insertRight": "उजवीकडे घाला", + "insertAbove": "वर घाला", + "insertBelow": "खाली घाला", + "headerColumn": "हेडर स्तंभ", + "headerRow": "हेडर ओळ", + "clearContents": "सामग्री साफ करा", + "setToPageWidth": "पृष्ठाच्या रुंदीप्रमाणे सेट करा", + "distributeColumnsWidth": "स्तंभ समान करा", + "duplicateRow": "ओळ डुप्लिकेट करा", + "duplicateColumn": "स्तंभ डुप्लिकेट करा", + "textColor": "मजकूराचा रंग", + "cellBackgroundColor": "सेलचा पार्श्वभूमी रंग", + "duplicateTable": "टेबल डुप्लिकेट करा" + }, + "clickToAddNewRow": "नवीन ओळ जोडण्यासाठी क्लिक करा", + "clickToAddNewColumn": "नवीन स्तंभ जोडण्यासाठी क्लिक करा", + "clickToAddNewRowAndColumn": "नवीन ओळ आणि स्तंभ जोडण्यासाठी क्लिक करा", + "headerName": { + "table": "टेबल", + "alignText": "मजकूर पंक्तिबद्ध करा" + } +}, +"cover": { + "changeCover": "कव्हर बदला", + "colors": "रंग", + "images": "प्रतिमा", + "clearAll": "सर्व साफ करा", + "abstract": "ऍबस्ट्रॅक्ट", + "addCover": "कव्हर जोडा", + "addLocalImage": "स्थानिक प्रतिमा जोडा", + "invalidImageUrl": "अवैध प्रतिमा URL", + "failedToAddImageToGallery": "प्रतिमा गॅलरीत जोडता आली नाही", + "enterImageUrl": "प्रतिमा URL लिहा", + "add": "जोडा", + "back": "मागे", + "saveToGallery": "गॅलरीत जतन करा", + "removeIcon": "आयकॉन काढा", + "removeCover": "कव्हर काढा", + "pasteImageUrl": "प्रतिमा URL पेस्ट करा", + "or": "किंवा", + "pickFromFiles": "फाईल्समधून निवडा", + "couldNotFetchImage": "प्रतिमा मिळवता आली नाही", + "imageSavingFailed": "प्रतिमा जतन करणे अयशस्वी", + "addIcon": "आयकॉन जोडा", + "changeIcon": "आयकॉन बदला", + "coverRemoveAlert": "हे हटवल्यानंतर कव्हरमधून काढले जाईल.", + "alertDialogConfirmation": "तुम्हाला खात्री आहे का? तुम्हाला पुढे जायचे आहे?" +}, +"mathEquation": { + "name": "गणिती समीकरण", + "addMathEquation": "TeX समीकरण जोडा", + "editMathEquation": "गणिती समीकरण संपादित करा" +}, +"optionAction": { + "click": "क्लिक", + "toOpenMenu": "मेनू उघडण्यासाठी", + "drag": "ओढा", + "toMove": "हलवण्यासाठी", + "delete": "हटा", + "duplicate": "डुप्लिकेट करा", + "turnInto": "मध्ये बदला", + "moveUp": "वर हलवा", + "moveDown": "खाली हलवा", + "color": "रंग", + "align": "पंक्तिबद्ध करा", + "left": "डावीकडे", + "center": "मध्यभागी", + "right": "उजवीकडे", + "defaultColor": "डिफॉल्ट", + "depth": "खोली", + "copyLinkToBlock": "ब्लॉकसाठी लिंक कॉपी करा" +}, + "image": { + "addAnImage": "प्रतिमा जोडा", + "copiedToPasteBoard": "प्रतिमेची लिंक क्लिपबोर्डवर कॉपी केली गेली आहे", + "addAnImageDesktop": "प्रतिमा जोडा", + "addAnImageMobile": "एक किंवा अधिक प्रतिमा जोडण्यासाठी क्लिक करा", + "dropImageToInsert": "प्रतिमा ड्रॉप करून जोडा", + "imageUploadFailed": "प्रतिमा अपलोड करण्यात अयशस्वी", + "imageDownloadFailed": "प्रतिमा डाउनलोड करण्यात अयशस्वी, कृपया पुन्हा प्रयत्न करा", + "imageDownloadFailedToken": "युजर टोकन नसल्यामुळे प्रतिमा डाउनलोड करण्यात अयशस्वी, कृपया पुन्हा प्रयत्न करा", + "errorCode": "त्रुटी कोड" +}, +"photoGallery": { + "name": "फोटो गॅलरी", + "imageKeyword": "प्रतिमा", + "imageGalleryKeyword": "प्रतिमा गॅलरी", + "photoKeyword": "फोटो", + "photoBrowserKeyword": "फोटो ब्राउझर", + "galleryKeyword": "गॅलरी", + "addImageTooltip": "प्रतिमा जोडा", + "changeLayoutTooltip": "लेआउट बदला", + "browserLayout": "ब्राउझर", + "gridLayout": "ग्रिड", + "deleteBlockTooltip": "संपूर्ण गॅलरी हटवा" +}, +"math": { + "copiedToPasteBoard": "समीकरण क्लिपबोर्डवर कॉपी केले गेले आहे" +}, +"urlPreview": { + "copiedToPasteBoard": "लिंक क्लिपबोर्डवर कॉपी केली गेली आहे", + "convertToLink": "एंबेड लिंकमध्ये रूपांतर करा" +}, +"outline": { + "addHeadingToCreateOutline": "सामग्री यादी तयार करण्यासाठी शीर्षके जोडा.", + "noMatchHeadings": "जुळणारी शीर्षके आढळली नाहीत." +}, +"table": { + "addAfter": "नंतर जोडा", + "addBefore": "आधी जोडा", + "delete": "हटा", + "clear": "सामग्री साफ करा", + "duplicate": "डुप्लिकेट करा", + "bgColor": "पार्श्वभूमीचा रंग" +}, +"contextMenu": { + "copy": "कॉपी करा", + "cut": "कापा", + "paste": "पेस्ट करा", + "pasteAsPlainText": "साध्या मजकूराच्या स्वरूपात पेस्ट करा" +}, +"action": "कृती", +"database": { + "selectDataSource": "डेटा स्रोत निवडा", + "noDataSource": "डेटा स्रोत नाही", + "selectADataSource": "डेटा स्रोत निवडा", + "toContinue": "पुढे जाण्यासाठी", + "newDatabase": "नवीन डेटाबेस", + "linkToDatabase": "डेटाबेसशी लिंक करा" +}, +"date": "तारीख", +"video": { + "label": "व्हिडिओ", + "emptyLabel": "व्हिडिओ जोडा", + "placeholder": "व्हिडिओ लिंक पेस्ट करा", + "copiedToPasteBoard": "व्हिडिओ लिंक क्लिपबोर्डवर कॉपी केली गेली आहे", + "insertVideo": "व्हिडिओ जोडा", + "invalidVideoUrl": "ही URL सध्या समर्थित नाही.", + "invalidVideoUrlYouTube": "YouTube सध्या समर्थित नाही.", + "supportedFormats": "समर्थित स्वरूप: MP4, WebM, MOV, AVI, FLV, MPEG/M4V, H.264" +}, +"file": { + "name": "फाईल", + "uploadTab": "अपलोड", + "uploadMobile": "फाईल निवडा", + "uploadMobileGallery": "फोटो गॅलरीमधून", + "networkTab": "लिंक एम्बेड करा", + "placeholderText": "फाईल अपलोड किंवा एम्बेड करा", + "placeholderDragging": "फाईल ड्रॉप करून अपलोड करा", + "dropFileToUpload": "फाईल ड्रॉप करून अपलोड करा", + "fileUploadHint": "फाईल ड्रॅग आणि ड्रॉप करा किंवा क्लिक करा ", + "fileUploadHintSuffix": "ब्राउझ करा", + "networkHint": "फाईल लिंक पेस्ट करा", + "networkUrlInvalid": "अवैध URL. कृपया तपासा आणि पुन्हा प्रयत्न करा.", + "networkAction": "एम्बेड", + "fileTooBigError": "फाईल खूप मोठी आहे, कृपया 10MB पेक्षा कमी फाईल अपलोड करा", + "renameFile": { + "title": "फाईलचे नाव बदला", + "description": "या फाईलसाठी नवीन नाव लिहा", + "nameEmptyError": "फाईलचे नाव रिकामे असू शकत नाही." + }, + "uploadedAt": "{} रोजी अपलोड केले", + "linkedAt": "{} रोजी लिंक जोडली", + "failedToOpenMsg": "उघडण्यात अयशस्वी, फाईल सापडली नाही" +}, +"subPage": { + "handlingPasteHint": " - (पेस्ट प्रक्रिया करत आहे)", + "errors": { + "failedDeletePage": "पृष्ठ हटवण्यात अयशस्वी", + "failedCreatePage": "पृष्ठ तयार करण्यात अयशस्वी", + "failedMovePage": "हे पृष्ठ हलवण्यात अयशस्वी", + "failedDuplicatePage": "पृष्ठ डुप्लिकेट करण्यात अयशस्वी", + "failedDuplicateFindView": "पृष्ठ डुप्लिकेट करण्यात अयशस्वी - मूळ दृश्य सापडले नाही" + } +}, + "cannotMoveToItsChildren": "मुलांमध्ये हलवू शकत नाही" +}, +"outlineBlock": { + "placeholder": "सामग्री सूची" +}, +"textBlock": { + "placeholder": "कमांडसाठी '/' टाइप करा" +}, +"title": { + "placeholder": "शीर्षक नाही" +}, +"imageBlock": { + "placeholder": "प्रतिमा जोडण्यासाठी क्लिक करा", + "upload": { + "label": "अपलोड", + "placeholder": "प्रतिमा अपलोड करण्यासाठी क्लिक करा" + }, + "url": { + "label": "प्रतिमेची URL", + "placeholder": "प्रतिमेची URL टाका" + }, + "ai": { + "label": "AI द्वारे प्रतिमा तयार करा", + "placeholder": "AI द्वारे प्रतिमा तयार करण्यासाठी कृपया संकेत द्या" + }, + "stability_ai": { + "label": "Stability AI द्वारे प्रतिमा तयार करा", + "placeholder": "Stability AI द्वारे प्रतिमा तयार करण्यासाठी कृपया संकेत द्या" + }, + "support": "प्रतिमेचा कमाल आकार 5MB आहे. समर्थित स्वरूप: JPEG, PNG, GIF, SVG", + "error": { + "invalidImage": "अवैध प्रतिमा", + "invalidImageSize": "प्रतिमेचा आकार 5MB पेक्षा कमी असावा", + "invalidImageFormat": "प्रतिमेचे स्वरूप समर्थित नाही. समर्थित स्वरूप: JPEG, PNG, JPG, GIF, SVG, WEBP", + "invalidImageUrl": "अवैध प्रतिमेची URL", + "noImage": "अशी फाईल किंवा निर्देशिका नाही", + "multipleImagesFailed": "एक किंवा अधिक प्रतिमा अपलोड करण्यात अयशस्वी, कृपया पुन्हा प्रयत्न करा" + }, + "embedLink": { + "label": "लिंक एम्बेड करा", + "placeholder": "प्रतिमेची लिंक पेस्ट करा किंवा टाका" + }, + "unsplash": { + "label": "Unsplash" + }, + "searchForAnImage": "प्रतिमा शोधा", + "pleaseInputYourOpenAIKey": "कृपया सेटिंग्ज पृष्ठात आपला AI की प्रविष्ट करा", + "saveImageToGallery": "प्रतिमा जतन करा", + "failedToAddImageToGallery": "प्रतिमा जतन करण्यात अयशस्वी", + "successToAddImageToGallery": "प्रतिमा 'Photos' मध्ये जतन केली", + "unableToLoadImage": "प्रतिमा लोड करण्यात अयशस्वी", + "maximumImageSize": "कमाल प्रतिमा अपलोड आकार 10MB आहे", + "uploadImageErrorImageSizeTooBig": "प्रतिमेचा आकार 10MB पेक्षा कमी असावा", + "imageIsUploading": "प्रतिमा अपलोड होत आहे", + "openFullScreen": "पूर्ण स्क्रीनमध्ये उघडा", + "interactiveViewer": { + "toolbar": { + "previousImageTooltip": "मागील प्रतिमा", + "nextImageTooltip": "पुढील प्रतिमा", + "zoomOutTooltip": "लहान करा", + "zoomInTooltip": "मोठी करा", + "changeZoomLevelTooltip": "झूम पातळी बदला", + "openLocalImage": "प्रतिमा उघडा", + "downloadImage": "प्रतिमा डाउनलोड करा", + "closeViewer": "इंटरअॅक्टिव्ह व्ह्युअर बंद करा", + "scalePercentage": "{}%", + "deleteImageTooltip": "प्रतिमा हटवा" + } + } +}, + "codeBlock": { + "language": { + "label": "भाषा", + "placeholder": "भाषा निवडा", + "auto": "स्वयंचलित" + }, + "copyTooltip": "कॉपी करा", + "searchLanguageHint": "भाषा शोधा", + "codeCopiedSnackbar": "कोड क्लिपबोर्डवर कॉपी झाला!" +}, +"inlineLink": { + "placeholder": "लिंक पेस्ट करा किंवा टाका", + "openInNewTab": "नवीन टॅबमध्ये उघडा", + "copyLink": "लिंक कॉपी करा", + "removeLink": "लिंक काढा", + "url": { + "label": "लिंक URL", + "placeholder": "लिंक URL टाका" + }, + "title": { + "label": "लिंक शीर्षक", + "placeholder": "लिंक शीर्षक टाका" + } +}, +"mention": { + "placeholder": "कोणीतरी, पृष्ठ किंवा तारीख नमूद करा...", + "page": { + "label": "पृष्ठाला लिंक करा", + "tooltip": "पृष्ठ उघडण्यासाठी क्लिक करा" + }, + "deleted": "हटवले गेले", + "deletedContent": "ही सामग्री अस्तित्वात नाही किंवा हटवण्यात आली आहे", + "noAccess": "प्रवेश नाही", + "deletedPage": "हटवलेले पृष्ठ", + "trashHint": " - ट्रॅशमध्ये", + "morePages": "अजून पृष्ठे" +}, +"toolbar": { + "resetToDefaultFont": "डीफॉल्ट फॉन्टवर परत जा", + "textSize": "मजकूराचा आकार", + "textColor": "मजकूराचा रंग", + "h1": "मथळा 1", + "h2": "मथळा 2", + "h3": "मथळा 3", + "alignLeft": "डावीकडे संरेखित करा", + "alignRight": "उजवीकडे संरेखित करा", + "alignCenter": "मध्यभागी संरेखित करा", + "link": "लिंक", + "textAlign": "मजकूर संरेखन", + "moreOptions": "अधिक पर्याय", + "font": "फॉन्ट", + "inlineCode": "इनलाइन कोड", + "suggestions": "सूचना", + "turnInto": "मध्ये रूपांतरित करा", + "equation": "समीकरण", + "insert": "घाला", + "linkInputHint": "लिंक पेस्ट करा किंवा पृष्ठे शोधा", + "pageOrURL": "पृष्ठ किंवा URL", + "linkName": "लिंकचे नाव", + "linkNameHint": "लिंकचे नाव प्रविष्ट करा" +}, +"errorBlock": { + "theBlockIsNotSupported": "ब्लॉक सामग्री पार्स करण्यात अक्षम", + "clickToCopyTheBlockContent": "ब्लॉक सामग्री कॉपी करण्यासाठी क्लिक करा", + "blockContentHasBeenCopied": "ब्लॉक सामग्री कॉपी केली आहे.", + "parseError": "{} ब्लॉक पार्स करताना त्रुटी आली.", + "copyBlockContent": "ब्लॉक सामग्री कॉपी करा" +}, +"mobilePageSelector": { + "title": "पृष्ठ निवडा", + "failedToLoad": "पृष्ठ यादी लोड करण्यात अयशस्वी", + "noPagesFound": "कोणतीही पृष्ठे सापडली नाहीत" +}, +"attachmentMenu": { + "choosePhoto": "फोटो निवडा", + "takePicture": "फोटो काढा", + "chooseFile": "फाईल निवडा" + } + }, + "board": { + "column": { + "label": "स्तंभ", + "createNewCard": "नवीन", + "renameGroupTooltip": "गटाचे नाव बदलण्यासाठी क्लिक करा", + "createNewColumn": "नवीन गट जोडा", + "addToColumnTopTooltip": "वर नवीन कार्ड जोडा", + "addToColumnBottomTooltip": "खाली नवीन कार्ड जोडा", + "renameColumn": "स्तंभाचे नाव बदला", + "hideColumn": "लपवा", + "newGroup": "नवीन गट", + "deleteColumn": "हटवा", + "deleteColumnConfirmation": "हा गट आणि त्यामधील सर्व कार्ड्स हटवले जातील. तुम्हाला खात्री आहे का?" + }, + "hiddenGroupSection": { + "sectionTitle": "लपवलेले गट", + "collapseTooltip": "लपवलेले गट लपवा", + "expandTooltip": "लपवलेले गट पाहा" + }, + "cardDetail": "कार्ड तपशील", + "cardActions": "कार्ड क्रिया", + "cardDuplicated": "कार्डची प्रत तयार झाली", + "cardDeleted": "कार्ड हटवले गेले", + "showOnCard": "कार्ड तपशिलावर दाखवा", + "setting": "सेटिंग", + "propertyName": "गुणधर्माचे नाव", + "menuName": "बोर्ड", + "showUngrouped": "गटात नसलेली कार्ड्स दाखवा", + "ungroupedButtonText": "गट नसलेली", + "ungroupedButtonTooltip": "ज्या कार्ड्स कोणत्याही गटात नाहीत", + "ungroupedItemsTitle": "बोर्डमध्ये जोडण्यासाठी क्लिक करा", + "groupBy": "या आधारावर गट करा", + "groupCondition": "गट स्थिती", + "referencedBoardPrefix": "याचे दृश्य", + "notesTooltip": "नोट्स आहेत", + "mobile": { + "editURL": "URL संपादित करा", + "showGroup": "गट दाखवा", + "showGroupContent": "हा गट बोर्डवर दाखवायचा आहे का?", + "failedToLoad": "बोर्ड दृश्य लोड होण्यात अयशस्वी" + }, + "dateCondition": { + "weekOf": "{} - {} ची आठवडा", + "today": "आज", + "yesterday": "काल", + "tomorrow": "उद्या", + "lastSevenDays": "शेवटचे ७ दिवस", + "nextSevenDays": "पुढील ७ दिवस", + "lastThirtyDays": "शेवटचे ३० दिवस", + "nextThirtyDays": "पुढील ३० दिवस" + }, + "noGroup": "गटासाठी कोणताही गुणधर्म निवडलेला नाही", + "noGroupDesc": "बोर्ड दृश्य दाखवण्यासाठी गट करण्याचा एक गुणधर्म आवश्यक आहे", + "media": { + "cardText": "{} {}", + "fallbackName": "फायली" + } +}, + "calendar": { + "menuName": "कॅलेंडर", + "defaultNewCalendarTitle": "नाव नाही", + "newEventButtonTooltip": "नवीन इव्हेंट जोडा", + "navigation": { + "today": "आज", + "jumpToday": "आजवर जा", + "previousMonth": "मागील महिना", + "nextMonth": "पुढील महिना", + "views": { + "day": "दिवस", + "week": "आठवडा", + "month": "महिना", + "year": "वर्ष" + } + }, + "mobileEventScreen": { + "emptyTitle": "सध्या कोणतेही इव्हेंट नाहीत", + "emptyBody": "या दिवशी इव्हेंट तयार करण्यासाठी प्लस बटणावर क्लिक करा." + }, + "settings": { + "showWeekNumbers": "आठवड्याचे क्रमांक दाखवा", + "showWeekends": "सप्ताहांत दाखवा", + "firstDayOfWeek": "आठवड्याची सुरुवात", + "layoutDateField": "कॅलेंडर मांडणी दिनांकानुसार", + "changeLayoutDateField": "मांडणी फील्ड बदला", + "noDateTitle": "तारीख नाही", + "noDateHint": { + "zero": "नियोजित नसलेली इव्हेंट्स येथे दिसतील", + "one": "{count} नियोजित नसलेली इव्हेंट", + "other": "{count} नियोजित नसलेल्या इव्हेंट्स" + }, + "unscheduledEventsTitle": "नियोजित नसलेल्या इव्हेंट्स", + "clickToAdd": "कॅलेंडरमध्ये जोडण्यासाठी क्लिक करा", + "name": "कॅलेंडर सेटिंग्ज", + "clickToOpen": "रेकॉर्ड उघडण्यासाठी क्लिक करा" + }, + "referencedCalendarPrefix": "याचे दृश्य", + "quickJumpYear": "या वर्षावर जा", + "duplicateEvent": "इव्हेंट डुप्लिकेट करा" +}, + "errorDialog": { + "title": "@:appName त्रुटी", + "howToFixFallback": "या गैरसोयीबद्दल आम्ही दिलगीर आहोत! कृपया GitHub पेजवर त्रुटीबद्दल माहिती देणारे एक issue सबमिट करा.", + "howToFixFallbackHint1": "या गैरसोयीबद्दल आम्ही दिलगीर आहोत! कृपया ", + "howToFixFallbackHint2": " पेजवर त्रुटीचे वर्णन करणारे issue सबमिट करा.", + "github": "GitHub वर पहा" +}, +"search": { + "label": "शोध", + "sidebarSearchIcon": "पृष्ठ शोधा आणि पटकन जा", + "placeholder": { + "actions": "कृती शोधा..." + } +}, +"message": { + "copy": { + "success": "कॉपी झाले!", + "fail": "कॉपी करू शकत नाही" + } +}, +"unSupportBlock": "सध्याचा व्हर्जन या ब्लॉकला समर्थन देत नाही.", +"views": { + "deleteContentTitle": "तुम्हाला हे {pageType} हटवायचे आहे का?", + "deleteContentCaption": "हे {pageType} हटवल्यास, तुम्ही ते trash मधून पुनर्संचयित करू शकता." +}, + "colors": { + "custom": "सानुकूल", + "default": "डीफॉल्ट", + "red": "लाल", + "orange": "संत्रा", + "yellow": "पिवळा", + "green": "हिरवा", + "blue": "निळा", + "purple": "जांभळा", + "pink": "गुलाबी", + "brown": "तपकिरी", + "gray": "करड्या रंगाचा" +}, + "emoji": { + "emojiTab": "इमोजी", + "search": "इमोजी शोधा", + "noRecent": "अलीकडील कोणतेही इमोजी नाहीत", + "noEmojiFound": "कोणतेही इमोजी सापडले नाहीत", + "filter": "फिल्टर", + "random": "योगायोगाने", + "selectSkinTone": "त्वचेचा टोन निवडा", + "remove": "इमोजी काढा", + "categories": { + "smileys": "स्मायली आणि भावना", + "people": "लोक", + "animals": "प्राणी आणि निसर्ग", + "food": "अन्न", + "activities": "क्रिया", + "places": "स्थळे", + "objects": "वस्तू", + "symbols": "चिन्हे", + "flags": "ध्वज", + "nature": "निसर्ग", + "frequentlyUsed": "नेहमी वापरलेले" + }, + "skinTone": { + "default": "डीफॉल्ट", + "light": "हलका", + "mediumLight": "मध्यम-हलका", + "medium": "मध्यम", + "mediumDark": "मध्यम-गडद", + "dark": "गडद" + }, + "openSourceIconsFrom": "खुल्या स्रोताचे आयकॉन्स" +}, + "inlineActions": { + "noResults": "निकाल नाही", + "recentPages": "अलीकडील पृष्ठे", + "pageReference": "पृष्ठ संदर्भ", + "docReference": "दस्तऐवज संदर्भ", + "boardReference": "बोर्ड संदर्भ", + "calReference": "कॅलेंडर संदर्भ", + "gridReference": "ग्रिड संदर्भ", + "date": "तारीख", + "reminder": { + "groupTitle": "स्मरणपत्र", + "shortKeyword": "remind" + }, + "createPage": "\"{}\" उप-पृष्ठ तयार करा" +}, + "datePicker": { + "dateTimeFormatTooltip": "सेटिंग्जमध्ये तारीख आणि वेळ फॉरमॅट बदला", + "dateFormat": "तारीख फॉरमॅट", + "includeTime": "वेळ समाविष्ट करा", + "isRange": "शेवटची तारीख", + "timeFormat": "वेळ फॉरमॅट", + "clearDate": "तारीख साफ करा", + "reminderLabel": "स्मरणपत्र", + "selectReminder": "स्मरणपत्र निवडा", + "reminderOptions": { + "none": "काहीही नाही", + "atTimeOfEvent": "इव्हेंटच्या वेळी", + "fiveMinsBefore": "५ मिनिटे आधी", + "tenMinsBefore": "१० मिनिटे आधी", + "fifteenMinsBefore": "१५ मिनिटे आधी", + "thirtyMinsBefore": "३० मिनिटे आधी", + "oneHourBefore": "१ तास आधी", + "twoHoursBefore": "२ तास आधी", + "onDayOfEvent": "इव्हेंटच्या दिवशी", + "oneDayBefore": "१ दिवस आधी", + "twoDaysBefore": "२ दिवस आधी", + "oneWeekBefore": "१ आठवडा आधी", + "custom": "सानुकूल" + } +}, + "relativeDates": { + "yesterday": "काल", + "today": "आज", + "tomorrow": "उद्या", + "oneWeek": "१ आठवडा" +}, + "notificationHub": { + "title": "सूचना", + "mobile": { + "title": "अपडेट्स" + }, + "emptyTitle": "सर्व पूर्ण झाले!", + "emptyBody": "कोणतीही प्रलंबित सूचना किंवा कृती नाहीत. शांततेचा आनंद घ्या.", + "tabs": { + "inbox": "इनबॉक्स", + "upcoming": "आगामी" + }, + "actions": { + "markAllRead": "सर्व वाचलेल्या म्हणून चिन्हित करा", + "showAll": "सर्व", + "showUnreads": "न वाचलेल्या" + }, + "filters": { + "ascending": "आरोही", + "descending": "अवरोही", + "groupByDate": "तारीखेनुसार गटबद्ध करा", + "showUnreadsOnly": "फक्त न वाचलेल्या दाखवा", + "resetToDefault": "डीफॉल्टवर रीसेट करा" + } +}, + "reminderNotification": { + "title": "स्मरणपत्र", + "message": "तुम्ही विसरण्याआधी हे तपासण्याचे लक्षात ठेवा!", + "tooltipDelete": "हटवा", + "tooltipMarkRead": "वाचले म्हणून चिन्हित करा", + "tooltipMarkUnread": "न वाचले म्हणून चिन्हित करा" +}, + "findAndReplace": { + "find": "शोधा", + "previousMatch": "मागील जुळणारे", + "nextMatch": "पुढील जुळणारे", + "close": "बंद करा", + "replace": "बदला", + "replaceAll": "सर्व बदला", + "noResult": "कोणतेही निकाल नाहीत", + "caseSensitive": "केस सेंसिटिव्ह", + "searchMore": "अधिक निकालांसाठी शोधा" +}, + "error": { + "weAreSorry": "आम्ही क्षमस्व आहोत", + "loadingViewError": "हे दृश्य लोड करण्यात अडचण येत आहे. कृपया तुमचे इंटरनेट कनेक्शन तपासा, अ‍ॅप रीफ्रेश करा, आणि समस्या कायम असल्यास आमच्याशी संपर्क साधा.", + "syncError": "इतर डिव्हाइसमधून डेटा सिंक झाला नाही", + "syncErrorHint": "कृपया हे पृष्ठ शेवटचे जिथे संपादित केले होते त्या डिव्हाइसवर उघडा, आणि मग सध्याच्या डिव्हाइसवर पुन्हा उघडा.", + "clickToCopy": "एरर कोड कॉपी करण्यासाठी क्लिक करा" +}, + "editor": { + "bold": "जाड", + "bulletedList": "बुलेट यादी", + "bulletedListShortForm": "बुलेट", + "checkbox": "चेकबॉक्स", + "embedCode": "कोड एम्बेड करा", + "heading1": "H1", + "heading2": "H2", + "heading3": "H3", + "highlight": "हायलाइट", + "color": "रंग", + "image": "प्रतिमा", + "date": "तारीख", + "page": "पृष्ठ", + "italic": "तिरका", + "link": "लिंक", + "numberedList": "क्रमांकित यादी", + "numberedListShortForm": "क्रमांकित", + "toggleHeading1ShortForm": "Toggle H1", + "toggleHeading2ShortForm": "Toggle H2", + "toggleHeading3ShortForm": "Toggle H3", + "quote": "कोट", + "strikethrough": "ओढून टाका", + "text": "मजकूर", + "underline": "अधोरेखित", + "fontColorDefault": "डीफॉल्ट", + "fontColorGray": "धूसर", + "fontColorBrown": "तपकिरी", + "fontColorOrange": "केशरी", + "fontColorYellow": "पिवळा", + "fontColorGreen": "हिरवा", + "fontColorBlue": "निळा", + "fontColorPurple": "जांभळा", + "fontColorPink": "पिंग", + "fontColorRed": "लाल", + "backgroundColorDefault": "डीफॉल्ट पार्श्वभूमी", + "backgroundColorGray": "धूसर पार्श्वभूमी", + "backgroundColorBrown": "तपकिरी पार्श्वभूमी", + "backgroundColorOrange": "केशरी पार्श्वभूमी", + "backgroundColorYellow": "पिवळी पार्श्वभूमी", + "backgroundColorGreen": "हिरवी पार्श्वभूमी", + "backgroundColorBlue": "निळी पार्श्वभूमी", + "backgroundColorPurple": "जांभळी पार्श्वभूमी", + "backgroundColorPink": "पिंग पार्श्वभूमी", + "backgroundColorRed": "लाल पार्श्वभूमी", + "backgroundColorLime": "लिंबू पार्श्वभूमी", + "backgroundColorAqua": "पाण्याचा पार्श्वभूमी", + "done": "पूर्ण", + "cancel": "रद्द करा", + "tint1": "टिंट 1", + "tint2": "टिंट 2", + "tint3": "टिंट 3", + "tint4": "टिंट 4", + "tint5": "टिंट 5", + "tint6": "टिंट 6", + "tint7": "टिंट 7", + "tint8": "टिंट 8", + "tint9": "टिंट 9", + "lightLightTint1": "जांभळा", + "lightLightTint2": "पिंग", + "lightLightTint3": "फिकट पिंग", + "lightLightTint4": "केशरी", + "lightLightTint5": "पिवळा", + "lightLightTint6": "लिंबू", + "lightLightTint7": "हिरवा", + "lightLightTint8": "पाणी", + "lightLightTint9": "निळा", + "urlHint": "URL", + "mobileHeading1": "Heading 1", + "mobileHeading2": "Heading 2", + "mobileHeading3": "Heading 3", + "mobileHeading4": "Heading 4", + "mobileHeading5": "Heading 5", + "mobileHeading6": "Heading 6", + "textColor": "मजकूराचा रंग", + "backgroundColor": "पार्श्वभूमीचा रंग", + "addYourLink": "तुमची लिंक जोडा", + "openLink": "लिंक उघडा", + "copyLink": "लिंक कॉपी करा", + "removeLink": "लिंक काढा", + "editLink": "लिंक संपादित करा", + "linkText": "मजकूर", + "linkTextHint": "कृपया मजकूर प्रविष्ट करा", + "linkAddressHint": "कृपया URL प्रविष्ट करा", + "highlightColor": "हायलाइट रंग", + "clearHighlightColor": "हायलाइट काढा", + "customColor": "स्वतःचा रंग", + "hexValue": "Hex मूल्य", + "opacity": "अपारदर्शकता", + "resetToDefaultColor": "डीफॉल्ट रंगावर रीसेट करा", + "ltr": "LTR", + "rtl": "RTL", + "auto": "स्वयंचलित", + "cut": "कट", + "copy": "कॉपी", + "paste": "पेस्ट", + "find": "शोधा", + "select": "निवडा", + "selectAll": "सर्व निवडा", + "previousMatch": "मागील जुळणारे", + "nextMatch": "पुढील जुळणारे", + "closeFind": "बंद करा", + "replace": "बदला", + "replaceAll": "सर्व बदला", + "regex": "Regex", + "caseSensitive": "केस सेंसिटिव्ह", + "uploadImage": "प्रतिमा अपलोड करा", + "urlImage": "URL प्रतिमा", + "incorrectLink": "चुकीची लिंक", + "upload": "अपलोड", + "chooseImage": "प्रतिमा निवडा", + "loading": "लोड करत आहे", + "imageLoadFailed": "प्रतिमा लोड करण्यात अयशस्वी", + "divider": "विभाजक", + "table": "तक्त्याचे स्वरूप", + "colAddBefore": "यापूर्वी स्तंभ जोडा", + "rowAddBefore": "यापूर्वी पंक्ती जोडा", + "colAddAfter": "यानंतर स्तंभ जोडा", + "rowAddAfter": "यानंतर पंक्ती जोडा", + "colRemove": "स्तंभ काढा", + "rowRemove": "पंक्ती काढा", + "colDuplicate": "स्तंभ डुप्लिकेट", + "rowDuplicate": "पंक्ती डुप्लिकेट", + "colClear": "सामग्री साफ करा", + "rowClear": "सामग्री साफ करा", + "slashPlaceHolder": "'/' टाइप करा आणि घटक जोडा किंवा टाइप सुरू करा", + "typeSomething": "काहीतरी लिहा...", + "toggleListShortForm": "टॉगल", + "quoteListShortForm": "कोट", + "mathEquationShortForm": "सूत्र", + "codeBlockShortForm": "कोड" +}, + "favorite": { + "noFavorite": "कोणतेही आवडते पृष्ठ नाही", + "noFavoriteHintText": "पृष्ठाला डावीकडे स्वाइप करा आणि ते आवडत्या यादीत जोडा", + "removeFromSidebar": "साइडबारमधून काढा", + "addToSidebar": "साइडबारमध्ये पिन करा" +}, +"cardDetails": { + "notesPlaceholder": "/ टाइप करा ब्लॉक घालण्यासाठी, किंवा टाइप करायला सुरुवात करा" +}, +"blockPlaceholders": { + "todoList": "करण्याची यादी", + "bulletList": "यादी", + "numberList": "क्रमांकित यादी", + "quote": "कोट", + "heading": "मथळा {}" +}, +"titleBar": { + "pageIcon": "पृष्ठ चिन्ह", + "language": "भाषा", + "font": "फॉन्ट", + "actions": "क्रिया", + "date": "तारीख", + "addField": "फील्ड जोडा", + "userIcon": "वापरकर्त्याचे चिन्ह" +}, +"noLogFiles": "कोणतीही लॉग फाइल्स नाहीत", +"newSettings": { + "myAccount": { + "title": "माझे खाते", + "subtitle": "तुमचा प्रोफाइल सानुकूल करा, खाते सुरक्षा व्यवस्थापित करा, AI कीज पहा किंवा लॉगिन करा.", + "profileLabel": "खाते नाव आणि प्रोफाइल चित्र", + "profileNamePlaceholder": "तुमचे नाव प्रविष्ट करा", + "accountSecurity": "खाते सुरक्षा", + "2FA": "2-स्टेप प्रमाणीकरण", + "aiKeys": "AI कीज", + "accountLogin": "खाते लॉगिन", + "updateNameError": "नाव अपडेट करण्यात अयशस्वी", + "updateIconError": "चिन्ह अपडेट करण्यात अयशस्वी", + "aboutAppFlowy": "@:appName विषयी", + "deleteAccount": { + "title": "खाते हटवा", + "subtitle": "तुमचे खाते आणि सर्व डेटा कायमचे हटवा.", + "description": "तुमचे खाते कायमचे हटवले जाईल आणि सर्व वर्कस्पेसमधून प्रवेश काढून टाकला जाईल.", + "deleteMyAccount": "माझे खाते हटवा", + "dialogTitle": "खाते हटवा", + "dialogContent1": "तुम्हाला खात्री आहे की तुम्ही तुमचे खाते कायमचे हटवू इच्छिता?", + "dialogContent2": "ही क्रिया पूर्ववत केली जाऊ शकत नाही. हे सर्व वर्कस्पेसमधून प्रवेश हटवेल, खाजगी वर्कस्पेस मिटवेल, आणि सर्व शेअर्ड वर्कस्पेसमधून काढून टाकेल.", + "confirmHint1": "कृपया पुष्टीसाठी \"@:newSettings.myAccount.deleteAccount.confirmHint3\" टाइप करा.", + "confirmHint2": "मला समजले आहे की ही क्रिया अपरिवर्तनीय आहे आणि माझे खाते व सर्व संबंधित डेटा कायमचा हटवला जाईल.", + "confirmHint3": "DELETE MY ACCOUNT", + "checkToConfirmError": "हटवण्यासाठी पुष्टी बॉक्स निवडणे आवश्यक आहे", + "failedToGetCurrentUser": "वर्तमान वापरकर्त्याचा ईमेल मिळवण्यात अयशस्वी", + "confirmTextValidationFailed": "तुमचा पुष्टी मजकूर \"@:newSettings.myAccount.deleteAccount.confirmHint3\" शी जुळत नाही", + "deleteAccountSuccess": "खाते यशस्वीरित्या हटवले गेले" + } + }, + "workplace": { + "name": "वर्कस्पेस", + "title": "वर्कस्पेस सेटिंग्स", + "subtitle": "तुमचा वर्कस्पेस लुक, थीम, फॉन्ट, मजकूर लेआउट, तारीख, वेळ आणि भाषा सानुकूल करा.", + "workplaceName": "वर्कस्पेसचे नाव", + "workplaceNamePlaceholder": "वर्कस्पेसचे नाव टाका", + "workplaceIcon": "वर्कस्पेस चिन्ह", + "workplaceIconSubtitle": "एक प्रतिमा अपलोड करा किंवा इमोजी वापरा. हे साइडबार आणि सूचना मध्ये दर्शवले जाईल.", + "renameError": "वर्कस्पेसचे नाव बदलण्यात अयशस्वी", + "updateIconError": "चिन्ह अपडेट करण्यात अयशस्वी", + "chooseAnIcon": "चिन्ह निवडा", + "appearance": { + "name": "दृश्यरूप", + "themeMode": { + "auto": "स्वयंचलित", + "light": "प्रकाश मोड", + "dark": "गडद मोड" + }, + "language": "भाषा" + } + }, + "syncState": { + "syncing": "सिंक्रोनायझ करत आहे", + "synced": "सिंक्रोनायझ झाले", + "noNetworkConnected": "नेटवर्क कनेक्ट केलेले नाही" + } +}, + "pageStyle": { + "title": "पृष्ठ शैली", + "layout": "लेआउट", + "coverImage": "मुखपृष्ठ प्रतिमा", + "pageIcon": "पृष्ठ चिन्ह", + "colors": "रंग", + "gradient": "ग्रेडियंट", + "backgroundImage": "पार्श्वभूमी प्रतिमा", + "presets": "पूर्वनियोजित", + "photo": "फोटो", + "unsplash": "Unsplash", + "pageCover": "पृष्ठ कव्हर", + "none": "काही नाही", + "openSettings": "सेटिंग्स उघडा", + "photoPermissionTitle": "@:appName तुमच्या फोटो लायब्ररीमध्ये प्रवेश करू इच्छित आहे", + "photoPermissionDescription": "तुमच्या कागदपत्रांमध्ये प्रतिमा जोडण्यासाठी @:appName ला तुमच्या फोटोंमध्ये प्रवेश आवश्यक आहे", + "cameraPermissionTitle": "@:appName तुमच्या कॅमेऱ्याला प्रवेश करू इच्छित आहे", + "cameraPermissionDescription": "कॅमेऱ्यातून प्रतिमा जोडण्यासाठी @:appName ला तुमच्या कॅमेऱ्याचा प्रवेश आवश्यक आहे", + "doNotAllow": "परवानगी देऊ नका", + "image": "प्रतिमा" +}, +"commandPalette": { + "placeholder": "शोधा किंवा प्रश्न विचारा...", + "bestMatches": "सर्वोत्तम जुळवणी", + "recentHistory": "अलीकडील इतिहास", + "navigateHint": "नेव्हिगेट करण्यासाठी", + "loadingTooltip": "आम्ही निकाल शोधत आहोत...", + "betaLabel": "बेटा", + "betaTooltip": "सध्या आम्ही फक्त दस्तऐवज आणि पृष्ठ शोध समर्थन करतो", + "fromTrashHint": "कचरापेटीतून", + "noResultsHint": "आपण जे शोधत आहात ते सापडले नाही, कृपया दुसरा शब्द वापरून शोधा.", + "clearSearchTooltip": "शोध फील्ड साफ करा" +}, +"space": { + "delete": "हटवा", + "deleteConfirmation": "हटवा: ", + "deleteConfirmationDescription": "या स्पेसमधील सर्व पृष्ठे हटवली जातील आणि कचरापेटीत टाकली जातील, आणि प्रकाशित पृष्ठे अनपब्लिश केली जातील.", + "rename": "स्पेसचे नाव बदला", + "changeIcon": "चिन्ह बदला", + "manage": "स्पेस व्यवस्थापित करा", + "addNewSpace": "स्पेस तयार करा", + "collapseAllSubPages": "सर्व उपपृष्ठे संकुचित करा", + "createNewSpace": "नवीन स्पेस तयार करा", + "createSpaceDescription": "तुमचे कार्य अधिक चांगल्या प्रकारे आयोजित करण्यासाठी अनेक सार्वजनिक व खाजगी स्पेस तयार करा.", + "spaceName": "स्पेसचे नाव", + "spaceNamePlaceholder": "उदा. मार्केटिंग, अभियांत्रिकी, HR", + "permission": "स्पेस परवानगी", + "publicPermission": "सार्वजनिक", + "publicPermissionDescription": "पूर्ण प्रवेशासह सर्व वर्कस्पेस सदस्य", + "privatePermission": "खाजगी", + "privatePermissionDescription": "फक्त तुम्हाला या स्पेसमध्ये प्रवेश आहे", + "spaceIconBackground": "पार्श्वभूमीचा रंग", + "spaceIcon": "चिन्ह", + "dangerZone": "धोकादायक क्षेत्र", + "unableToDeleteLastSpace": "शेवटची स्पेस हटवता येणार नाही", + "unableToDeleteSpaceNotCreatedByYou": "तुमच्याद्वारे तयार न केलेली स्पेस हटवता येणार नाही", + "enableSpacesForYourWorkspace": "तुमच्या वर्कस्पेससाठी स्पेस सक्षम करा", + "title": "स्पेसेस", + "defaultSpaceName": "सामान्य", + "upgradeSpaceTitle": "स्पेस सक्षम करा", + "upgradeSpaceDescription": "तुमच्या वर्कस्पेसचे अधिक चांगल्या प्रकारे व्यवस्थापन करण्यासाठी अनेक सार्वजनिक आणि खाजगी स्पेस तयार करा.", + "upgrade": "अपग्रेड", + "upgradeYourSpace": "अनेक स्पेस तयार करा", + "quicklySwitch": "पुढील स्पेसवर पटकन स्विच करा", + "duplicate": "स्पेस डुप्लिकेट करा", + "movePageToSpace": "पृष्ठ स्पेसमध्ये हलवा", + "cannotMovePageToDatabase": "पृष्ठ डेटाबेसमध्ये हलवता येणार नाही", + "switchSpace": "स्पेस स्विच करा", + "spaceNameCannotBeEmpty": "स्पेसचे नाव रिकामे असू शकत नाही", + "success": { + "deleteSpace": "स्पेस यशस्वीरित्या हटवली", + "renameSpace": "स्पेसचे नाव यशस्वीरित्या बदलले", + "duplicateSpace": "स्पेस यशस्वीरित्या डुप्लिकेट केली", + "updateSpace": "स्पेस यशस्वीरित्या अपडेट केली" + }, + "error": { + "deleteSpace": "स्पेस हटवण्यात अयशस्वी", + "renameSpace": "स्पेसचे नाव बदलण्यात अयशस्वी", + "duplicateSpace": "स्पेस डुप्लिकेट करण्यात अयशस्वी", + "updateSpace": "स्पेस अपडेट करण्यात अयशस्वी" + }, + "createSpace": "स्पेस तयार करा", + "manageSpace": "स्पेस व्यवस्थापित करा", + "renameSpace": "स्पेसचे नाव बदला", + "mSpaceIconColor": "स्पेस चिन्हाचा रंग", + "mSpaceIcon": "स्पेस चिन्ह" +}, + "publish": { + "hasNotBeenPublished": "हे पृष्ठ अजून प्रकाशित केलेले नाही", + "spaceHasNotBeenPublished": "स्पेस प्रकाशित करण्यासाठी समर्थन नाही", + "reportPage": "पृष्ठाची तक्रार करा", + "databaseHasNotBeenPublished": "डेटाबेस प्रकाशित करण्यास समर्थन नाही.", + "createdWith": "यांनी तयार केले", + "downloadApp": "AppFlowy डाउनलोड करा", + "copy": { + "codeBlock": "कोड ब्लॉकची सामग्री क्लिपबोर्डवर कॉपी केली गेली आहे", + "imageBlock": "प्रतिमा लिंक क्लिपबोर्डवर कॉपी केली गेली आहे", + "mathBlock": "गणितीय समीकरण क्लिपबोर्डवर कॉपी केले गेले आहे", + "fileBlock": "फाइल लिंक क्लिपबोर्डवर कॉपी केली गेली आहे" + }, + "containsPublishedPage": "या पृष्ठात एक किंवा अधिक प्रकाशित पृष्ठे आहेत. तुम्ही पुढे गेल्यास ती अनपब्लिश होतील. तुम्हाला हटवणे चालू ठेवायचे आहे का?", + "publishSuccessfully": "यशस्वीरित्या प्रकाशित झाले", + "unpublishSuccessfully": "यशस्वीरित्या अनपब्लिश झाले", + "publishFailed": "प्रकाशित करण्यात अयशस्वी", + "unpublishFailed": "अनपब्लिश करण्यात अयशस्वी", + "noAccessToVisit": "या पृष्ठावर प्रवेश नाही...", + "createWithAppFlowy": "AppFlowy ने वेबसाइट तयार करा", + "fastWithAI": "AI सह जलद आणि सोपे.", + "tryItNow": "आत्ताच वापरून पहा", + "onlyGridViewCanBePublished": "फक्त Grid view प्रकाशित केला जाऊ शकतो", + "database": { + "zero": "{} निवडलेले दृश्य प्रकाशित करा", + "one": "{} निवडलेली दृश्ये प्रकाशित करा", + "many": "{} निवडलेली दृश्ये प्रकाशित करा", + "other": "{} निवडलेली दृश्ये प्रकाशित करा" + }, + "mustSelectPrimaryDatabase": "प्राथमिक दृश्य निवडणे आवश्यक आहे", + "noDatabaseSelected": "कोणताही डेटाबेस निवडलेला नाही, कृपया किमान एक डेटाबेस निवडा.", + "unableToDeselectPrimaryDatabase": "प्राथमिक डेटाबेस अननिवड करता येणार नाही", + "saveThisPage": "या टेम्पलेटपासून सुरू करा", + "duplicateTitle": "तुम्हाला हे कुठे जोडायचे आहे", + "selectWorkspace": "वर्कस्पेस निवडा", + "addTo": "मध्ये जोडा", + "duplicateSuccessfully": "तुमच्या वर्कस्पेसमध्ये जोडले गेले", + "duplicateSuccessfullyDescription": "AppFlowy स्थापित केले नाही? तुम्ही 'डाउनलोड' वर क्लिक केल्यावर डाउनलोड आपोआप सुरू होईल.", + "downloadIt": "डाउनलोड करा", + "openApp": "अ‍ॅपमध्ये उघडा", + "duplicateFailed": "डुप्लिकेट करण्यात अयशस्वी", + "membersCount": { + "zero": "सदस्य नाहीत", + "one": "1 सदस्य", + "many": "{count} सदस्य", + "other": "{count} सदस्य" + }, + "useThisTemplate": "हा टेम्पलेट वापरा" +}, +"web": { + "continue": "पुढे जा", + "or": "किंवा", + "continueWithGoogle": "Google सह पुढे जा", + "continueWithGithub": "GitHub सह पुढे जा", + "continueWithDiscord": "Discord सह पुढे जा", + "continueWithApple": "Apple सह पुढे जा", + "moreOptions": "अधिक पर्याय", + "collapse": "आकुंचन", + "signInAgreement": "\"पुढे जा\" क्लिक करून, तुम्ही AppFlowy च्या अटींना सहमती दिली आहे", + "signInLocalAgreement": "\"सुरुवात करा\" क्लिक करून, तुम्ही AppFlowy च्या अटींना सहमती दिली आहे", + "and": "आणि", + "termOfUse": "वापर अटी", + "privacyPolicy": "गोपनीयता धोरण", + "signInError": "साइन इन त्रुटी", + "login": "साइन अप किंवा लॉग इन करा", + "fileBlock": { + "uploadedAt": "{time} रोजी अपलोड केले", + "linkedAt": "{time} रोजी लिंक जोडली", + "empty": "फाईल अपलोड करा किंवा एम्बेड करा", + "uploadFailed": "अपलोड अयशस्वी, कृपया पुन्हा प्रयत्न करा", + "retry": "पुन्हा प्रयत्न करा" + }, + "importNotion": "Notion वरून आयात करा", + "import": "आयात करा", + "importSuccess": "यशस्वीरित्या अपलोड केले", + "importSuccessMessage": "आम्ही तुम्हाला आयात पूर्ण झाल्यावर सूचित करू. त्यानंतर, तुम्ही साइडबारमध्ये तुमची आयात केलेली पृष्ठे पाहू शकता.", + "importFailed": "आयात अयशस्वी, कृपया फाईल फॉरमॅट तपासा", + "dropNotionFile": "तुमची Notion zip फाईल येथे ड्रॉप करा किंवा ब्राउझ करा", + "error": { + "pageNameIsEmpty": "पृष्ठाचे नाव रिकामे आहे, कृपया दुसरे नाव वापरून पहा" + } +}, + "globalComment": { + "comments": "टिप्पण्या", + "addComment": "टिप्पणी जोडा", + "reactedBy": "यांनी प्रतिक्रिया दिली", + "addReaction": "प्रतिक्रिया जोडा", + "reactedByMore": "आणि {count} इतर", + "showSeconds": { + "one": "1 सेकंदापूर्वी", + "other": "{count} सेकंदांपूर्वी", + "zero": "आत्ताच", + "many": "{count} सेकंदांपूर्वी" + }, + "showMinutes": { + "one": "1 मिनिटापूर्वी", + "other": "{count} मिनिटांपूर्वी", + "many": "{count} मिनिटांपूर्वी" + }, + "showHours": { + "one": "1 तासापूर्वी", + "other": "{count} तासांपूर्वी", + "many": "{count} तासांपूर्वी" + }, + "showDays": { + "one": "1 दिवसापूर्वी", + "other": "{count} दिवसांपूर्वी", + "many": "{count} दिवसांपूर्वी" + }, + "showMonths": { + "one": "1 महिन्यापूर्वी", + "other": "{count} महिन्यांपूर्वी", + "many": "{count} महिन्यांपूर्वी" + }, + "showYears": { + "one": "1 वर्षापूर्वी", + "other": "{count} वर्षांपूर्वी", + "many": "{count} वर्षांपूर्वी" + }, + "reply": "उत्तर द्या", + "deleteComment": "टिप्पणी हटवा", + "youAreNotOwner": "तुम्ही या टिप्पणीचे मालक नाही", + "confirmDeleteDescription": "तुम्हाला ही टिप्पणी हटवायची आहे याची खात्री आहे का?", + "hasBeenDeleted": "हटवले गेले", + "replyingTo": "याला उत्तर देत आहे", + "noAccessDeleteComment": "तुम्हाला ही टिप्पणी हटवण्याची परवानगी नाही", + "collapse": "संकुचित करा", + "readMore": "अधिक वाचा", + "failedToAddComment": "टिप्पणी जोडण्यात अयशस्वी", + "commentAddedSuccessfully": "टिप्पणी यशस्वीरित्या जोडली गेली.", + "commentAddedSuccessTip": "तुम्ही नुकतीच एक टिप्पणी जोडली किंवा उत्तर दिले आहे. वर जाऊन ताजी टिप्पण्या पाहायच्या का?" +}, + "template": { + "asTemplate": "टेम्पलेट म्हणून जतन करा", + "name": "टेम्पलेट नाव", + "description": "टेम्पलेट वर्णन", + "about": "टेम्पलेट माहिती", + "deleteFromTemplate": "टेम्पलेटमधून हटवा", + "preview": "टेम्पलेट पूर्वदृश्य", + "categories": "टेम्पलेट श्रेणी", + "isNewTemplate": "नवीन टेम्पलेटमध्ये पिन करा", + "featured": "वैशिष्ट्यीकृतमध्ये पिन करा", + "relatedTemplates": "संबंधित टेम्पलेट्स", + "requiredField": "{field} आवश्यक आहे", + "addCategory": "\"{category}\" जोडा", + "addNewCategory": "नवीन श्रेणी जोडा", + "addNewCreator": "नवीन निर्माता जोडा", + "deleteCategory": "श्रेणी हटवा", + "editCategory": "श्रेणी संपादित करा", + "editCreator": "निर्माता संपादित करा", + "category": { + "name": "श्रेणीचे नाव", + "icon": "श्रेणी चिन्ह", + "bgColor": "श्रेणी पार्श्वभूमीचा रंग", + "priority": "श्रेणी प्राधान्य", + "desc": "श्रेणीचे वर्णन", + "type": "श्रेणी प्रकार", + "icons": "श्रेणी चिन्हे", + "colors": "श्रेणी रंग", + "byUseCase": "वापराच्या आधारे", + "byFeature": "वैशिष्ट्यांनुसार", + "deleteCategory": "श्रेणी हटवा", + "deleteCategoryDescription": "तुम्हाला ही श्रेणी हटवायची आहे का?", + "typeToSearch": "श्रेणी शोधण्यासाठी टाइप करा..." + }, + "creator": { + "label": "टेम्पलेट निर्माता", + "name": "निर्मात्याचे नाव", + "avatar": "निर्मात्याचा अवतार", + "accountLinks": "निर्मात्याचे खाते दुवे", + "uploadAvatar": "अवतार अपलोड करण्यासाठी क्लिक करा", + "deleteCreator": "निर्माता हटवा", + "deleteCreatorDescription": "तुम्हाला हा निर्माता हटवायचा आहे का?", + "typeToSearch": "निर्माते शोधण्यासाठी टाइप करा..." + }, + "uploadSuccess": "टेम्पलेट यशस्वीरित्या अपलोड झाले", + "uploadSuccessDescription": "तुमचे टेम्पलेट यशस्वीरित्या अपलोड झाले आहे. आता तुम्ही ते टेम्पलेट गॅलरीमध्ये पाहू शकता.", + "viewTemplate": "टेम्पलेट पहा", + "deleteTemplate": "टेम्पलेट हटवा", + "deleteSuccess": "टेम्पलेट यशस्वीरित्या हटवले गेले", + "deleteTemplateDescription": "याचा वर्तमान पृष्ठ किंवा प्रकाशित स्थितीवर परिणाम होणार नाही. तुम्हाला हे टेम्पलेट हटवायचे आहे का?", + "addRelatedTemplate": "संबंधित टेम्पलेट जोडा", + "removeRelatedTemplate": "संबंधित टेम्पलेट हटवा", + "uploadAvatar": "अवतार अपलोड करा", + "searchInCategory": "{category} मध्ये शोधा", + "label": "टेम्पलेट्स" +}, + "fileDropzone": { + "dropFile": "फाइल अपलोड करण्यासाठी येथे क्लिक करा किंवा ड्रॅग करा", + "uploading": "अपलोड करत आहे...", + "uploadFailed": "अपलोड अयशस्वी", + "uploadSuccess": "अपलोड यशस्वी", + "uploadSuccessDescription": "फाइल यशस्वीरित्या अपलोड झाली आहे", + "uploadFailedDescription": "फाइल अपलोड अयशस्वी झाली आहे", + "uploadingDescription": "फाइल अपलोड होत आहे" +}, + "gallery": { + "preview": "पूर्ण स्क्रीनमध्ये उघडा", + "copy": "कॉपी करा", + "download": "डाउनलोड", + "prev": "मागील", + "next": "पुढील", + "resetZoom": "झूम रिसेट करा", + "zoomIn": "झूम इन", + "zoomOut": "झूम आउट" +}, + "invitation": { + "join": "सामील व्हा", + "on": "वर", + "invitedBy": "यांनी आमंत्रित केले", + "membersCount": { + "zero": "{count} सदस्य", + "one": "{count} सदस्य", + "many": "{count} सदस्य", + "other": "{count} सदस्य" + }, + "tip": "तुम्हाला खालील माहितीच्या आधारे या कार्यक्षेत्रात सामील होण्यासाठी आमंत्रित करण्यात आले आहे. ही माहिती चुकीची असल्यास, कृपया प्रशासकाशी संपर्क साधा.", + "joinWorkspace": "वर्कस्पेसमध्ये सामील व्हा", + "success": "तुम्ही यशस्वीरित्या वर्कस्पेसमध्ये सामील झाला आहात", + "successMessage": "आता तुम्ही सर्व पृष्ठे आणि कार्यक्षेत्रे वापरू शकता.", + "openWorkspace": "AppFlowy उघडा", + "alreadyAccepted": "तुम्ही आधीच आमंत्रण स्वीकारले आहे", + "errorModal": { + "title": "काहीतरी चुकले आहे", + "description": "तुमचे सध्याचे खाते {email} कदाचित या वर्कस्पेसमध्ये प्रवेशासाठी पात्र नाही. कृपया योग्य खात्याने लॉग इन करा किंवा वर्कस्पेस मालकाशी संपर्क साधा.", + "contactOwner": "मालकाशी संपर्क करा", + "close": "मुख्यपृष्ठावर परत जा", + "changeAccount": "खाते बदला" + } +}, + "requestAccess": { + "title": "या पृष्ठासाठी प्रवेश नाही", + "subtitle": "तुम्ही या पृष्ठाच्या मालकाकडून प्रवेशासाठी विनंती करू शकता. मंजुरीनंतर तुम्हाला हे पृष्ठ पाहता येईल.", + "requestAccess": "प्रवेशाची विनंती करा", + "backToHome": "मुख्यपृष्ठावर परत जा", + "tip": "तुम्ही सध्या म्हणून लॉग इन आहात.", + "mightBe": "कदाचित तुम्हाला दुसऱ्या खात्याने लॉग इन करणे आवश्यक आहे.", + "successful": "विनंती यशस्वीपणे पाठवली गेली", + "successfulMessage": "मालकाने मंजुरी दिल्यावर तुम्हाला सूचित केले जाईल.", + "requestError": "प्रवेशाची विनंती अयशस्वी", + "repeatRequestError": "तुम्ही यासाठी आधीच विनंती केली आहे" +}, + "approveAccess": { + "title": "वर्कस्पेसमध्ये सामील होण्यासाठी विनंती मंजूर करा", + "requestSummary": " यांनी मध्ये सामील होण्यासाठी आणि पाहण्यासाठी विनंती केली आहे", + "upgrade": "अपग्रेड", + "downloadApp": "AppFlowy डाउनलोड करा", + "approveButton": "मंजूर करा", + "approveSuccess": "मंजूर यशस्वी", + "approveError": "मंजुरी अयशस्वी. कृपया वर्कस्पेस मर्यादा ओलांडलेली नाही याची खात्री करा", + "getRequestInfoError": "विनंतीची माहिती मिळवण्यात अयशस्वी", + "memberCount": { + "zero": "कोणतेही सदस्य नाहीत", + "one": "1 सदस्य", + "many": "{count} सदस्य", + "other": "{count} सदस्य" + }, + "alreadyProTitle": "वर्कस्पेस योजना मर्यादा गाठली आहे", + "alreadyProMessage": "अधिक सदस्यांसाठी शी संपर्क साधा", + "repeatApproveError": "तुम्ही ही विनंती आधीच मंजूर केली आहे", + "ensurePlanLimit": "कृपया खात्री करा की योजना मर्यादा ओलांडलेली नाही. जर ओलांडली असेल तर वर्कस्पेस योजना करा किंवा करा.", + "requestToJoin": "मध्ये सामील होण्यासाठी विनंती केली", + "asMember": "सदस्य म्हणून" +}, + "upgradePlanModal": { + "title": "Pro प्लॅनवर अपग्रेड करा", + "message": "{name} ने फ्री सदस्य मर्यादा गाठली आहे. अधिक सदस्य आमंत्रित करण्यासाठी Pro प्लॅनवर अपग्रेड करा.", + "upgradeSteps": "AppFlowy वर तुमची योजना कशी अपग्रेड करावी:", + "step1": "1. सेटिंग्जमध्ये जा", + "step2": "2. 'योजना' वर क्लिक करा", + "step3": "3. 'योजना बदला' निवडा", + "appNote": "नोंद:", + "actionButton": "अपग्रेड करा", + "downloadLink": "अ‍ॅप डाउनलोड करा", + "laterButton": "नंतर", + "refreshNote": "यशस्वी अपग्रेडनंतर, तुमची नवीन वैशिष्ट्ये सक्रिय करण्यासाठी वर क्लिक करा.", + "refresh": "येथे" +}, + "breadcrumbs": { + "label": "ब्रेडक्रम्स" +}, + "time": { + "justNow": "आत्ताच", + "seconds": { + "one": "1 सेकंद", + "other": "{count} सेकंद" + }, + "minutes": { + "one": "1 मिनिट", + "other": "{count} मिनिटे" + }, + "hours": { + "one": "1 तास", + "other": "{count} तास" + }, + "days": { + "one": "1 दिवस", + "other": "{count} दिवस" + }, + "weeks": { + "one": "1 आठवडा", + "other": "{count} आठवडे" + }, + "months": { + "one": "1 महिना", + "other": "{count} महिने" + }, + "years": { + "one": "1 वर्ष", + "other": "{count} वर्षे" + }, + "ago": "पूर्वी", + "yesterday": "काल", + "today": "आज" +}, + "members": { + "zero": "सदस्य नाहीत", + "one": "1 सदस्य", + "many": "{count} सदस्य", + "other": "{count} सदस्य" +}, + "tabMenu": { + "close": "बंद करा", + "closeDisabledHint": "पिन केलेले टॅब बंद करता येत नाही, कृपया आधी अनपिन करा", + "closeOthers": "इतर टॅब बंद करा", + "closeOthersHint": "हे सर्व अनपिन केलेले टॅब्स बंद करेल या टॅब वगळता", + "closeOthersDisabledHint": "सर्व टॅब पिन केलेले आहेत, बंद करण्यासाठी कोणतेही टॅब सापडले नाहीत", + "favorite": "आवडते", + "unfavorite": "आवडते काढा", + "favoriteDisabledHint": "हे दृश्य आवडते म्हणून जतन करता येत नाही", + "pinTab": "पिन करा", + "unpinTab": "अनपिन करा" +}, + "openFileMessage": { + "success": "फाइल यशस्वीरित्या उघडली", + "fileNotFound": "फाइल सापडली नाही", + "noAppToOpenFile": "ही फाइल उघडण्यासाठी कोणतेही अ‍ॅप उपलब्ध नाही", + "permissionDenied": "ही फाइल उघडण्यासाठी परवानगी नाही", + "unknownError": "फाइल उघडण्यात अयशस्वी" +}, + "inviteMember": { + "requestInviteMembers": "तुमच्या वर्कस्पेसमध्ये आमंत्रित करा", + "inviteFailedMemberLimit": "सदस्य मर्यादा गाठली आहे, कृपया ", + "upgrade": "अपग्रेड करा", + "addEmail": "email@example.com, email2@example.com...", + "requestInvites": "आमंत्रण पाठवा", + "inviteAlready": "तुम्ही या ईमेलला आधीच आमंत्रित केले आहे: {email}", + "inviteSuccess": "आमंत्रण यशस्वीपणे पाठवले", + "description": "खाली ईमेल कॉमा वापरून टाका. शुल्क सदस्य संख्येवर आधारित असते.", + "emails": "ईमेल" +}, + "quickNote": { + "label": "झटपट नोंद", + "quickNotes": "झटपट नोंदी", + "search": "झटपट नोंदी शोधा", + "collapseFullView": "पूर्ण दृश्य लपवा", + "expandFullView": "पूर्ण दृश्य उघडा", + "createFailed": "झटपट नोंद तयार करण्यात अयशस्वी", + "quickNotesEmpty": "कोणत्याही झटपट नोंदी नाहीत", + "emptyNote": "रिकामी नोंद", + "deleteNotePrompt": "निवडलेली नोंद कायमची हटवली जाईल. तुम्हाला नक्की ती हटवायची आहे का?", + "addNote": "नवीन नोंद", + "noAdditionalText": "अधिक माहिती नाही" +}, + "subscribe": { + "upgradePlanTitle": "योजना तुलना करा आणि निवडा", + "yearly": "वार्षिक", + "save": "{discount}% बचत", + "monthly": "मासिक", + "priceIn": "किंमत येथे: ", + "free": "फ्री", + "pro": "प्रो", + "freeDescription": "2 सदस्यांपर्यंत वैयक्तिक वापरकर्त्यांसाठी सर्व काही आयोजित करण्यासाठी", + "proDescription": "प्रकल्प आणि टीम नॉलेज व्यवस्थापित करण्यासाठी लहान टीमसाठी", + "proDuration": { + "monthly": "प्रति सदस्य प्रति महिना\nमासिक बिलिंग", + "yearly": "प्रति सदस्य प्रति महिना\nवार्षिक बिलिंग" + }, + "cancel": "खालच्या योजनेवर जा", + "changePlan": "प्रो योजनेवर अपग्रेड करा", + "everythingInFree": "फ्री योजनेतील सर्व काही +", + "currentPlan": "सध्याची योजना", + "freeDuration": "कायम", + "freePoints": { + "first": "1 सहकार्यात्मक वर्कस्पेस (2 सदस्यांपर्यंत)", + "second": "अमर्यादित पृष्ठे आणि ब्लॉक्स", + "three": "5 GB संचयन", + "four": "बुद्धिमान शोध", + "five": "20 AI प्रतिसाद", + "six": "मोबाईल अ‍ॅप", + "seven": "रिअल-टाइम सहकार्य" + }, + "proPoints": { + "first": "अमर्यादित संचयन", + "second": "10 वर्कस्पेस सदस्यांपर्यंत", + "three": "अमर्यादित AI प्रतिसाद", + "four": "अमर्यादित फाइल अपलोड्स", + "five": "कस्टम नेमस्पेस" + }, + "cancelPlan": { + "title": "आपल्याला जाताना पाहून वाईट वाटते", + "success": "आपली सदस्यता यशस्वीरित्या रद्द झाली आहे", + "description": "आपल्याला जाताना वाईट वाटते. कृपया आम्हाला सुधारण्यासाठी तुमचे अभिप्राय कळवा. खालील काही प्रश्नांना उत्तर द्या.", + "commonOther": "इतर", + "otherHint": "आपले उत्तर येथे लिहा", + "questionOne": { + "question": "तुम्ही AppFlowy Pro सदस्यता का रद्द केली?", + "answerOne": "खर्च खूप जास्त आहे", + "answerTwo": "वैशिष्ट्ये अपेक्षेनुसार नव्हती", + "answerThree": "चांगला पर्याय सापडला", + "answerFour": "खर्च योग्य ठरण्यासाठी वापर पुरेसा नव्हता", + "answerFive": "सेवा समस्या किंवा तांत्रिक अडचणी" + }, + "questionTwo": { + "question": "तुम्ही भविष्यात AppFlowy Pro पुन्हा घेण्याची शक्यता किती आहे?", + "answerOne": "खूप शक्यता आहे", + "answerTwo": "काहीशी शक्यता आहे", + "answerThree": "निश्चित नाही", + "answerFour": "अल्प शक्यता आहे", + "answerFive": "शक्यता नाही" + }, + "questionThree": { + "question": "सदस्यतेदरम्यान कोणते Pro फिचर तुम्हाला सर्वात जास्त उपयोगी वाटले?", + "answerOne": "मल्टी-यूजर सहकार्य", + "answerTwo": "लांब कालावधीसाठी आवृत्ती इतिहास", + "answerThree": "अमर्यादित AI प्रतिसाद", + "answerFour": "स्थानिक AI मॉडेल्सचा प्रवेश" + }, + "questionFour": { + "question": "AppFlowy बाबत तुमचा एकूण अनुभव कसा होता?", + "answerOne": "छान", + "answerTwo": "चांगला", + "answerThree": "सामान्य", + "answerFour": "थोडासा वाईट", + "answerFive": "असंतोषजनक" + } + } +}, + "ai": { + "contentPolicyViolation": "संवेदनशील सामग्रीमुळे प्रतिमा निर्मिती अयशस्वी झाली. कृपया तुमचे इनपुट पुन्हा लिहा आणि पुन्हा प्रयत्न करा.", + "textLimitReachedDescription": "तुमच्या वर्कस्पेसमध्ये मोफत AI प्रतिसाद संपले आहेत. अनलिमिटेड प्रतिसादांसाठी प्रो योजना घेण्याचा किंवा AI अ‍ॅड-ऑन खरेदी करण्याचा विचार करा.", + "imageLimitReachedDescription": "तुमचे मोफत AI प्रतिमा कोटा संपले आहे. कृपया प्रो योजना घ्या किंवा AI अ‍ॅड-ऑन खरेदी करा.", + "limitReachedAction": { + "textDescription": "तुमच्या वर्कस्पेसमध्ये मोफत AI प्रतिसाद संपले आहेत. अधिक प्रतिसाद मिळवण्यासाठी कृपया", + "imageDescription": "तुमचे मोफत AI प्रतिमा कोटा संपले आहे. कृपया", + "upgrade": "अपग्रेड करा", + "toThe": "या योजनेवर", + "proPlan": "प्रो योजना", + "orPurchaseAn": "किंवा खरेदी करा", + "aiAddon": "AI अ‍ॅड-ऑन" + }, + "editing": "संपादन करत आहे", + "analyzing": "विश्लेषण करत आहे", + "continueWritingEmptyDocumentTitle": "लेखन सुरू ठेवता आले नाही", + "continueWritingEmptyDocumentDescription": "तुमच्या दस्तऐवजातील मजकूर वाढवण्यात अडचण येत आहे. कृपया एक छोटं परिचय लिहा, मग आम्ही पुढे नेऊ!", + "more": "अधिक" +}, + "autoUpdate": { + "criticalUpdateTitle": "अद्यतन आवश्यक आहे", + "criticalUpdateDescription": "तुमचा अनुभव सुधारण्यासाठी आम्ही सुधारणा केल्या आहेत! कृपया {currentVersion} वरून {newVersion} वर अद्यतन करा.", + "criticalUpdateButton": "अद्यतन करा", + "bannerUpdateTitle": "नवीन आवृत्ती उपलब्ध!", + "bannerUpdateDescription": "नवीन वैशिष्ट्ये आणि सुधारणांसाठी. अद्यतनासाठी 'Update' क्लिक करा.", + "bannerUpdateButton": "अद्यतन करा", + "settingsUpdateTitle": "नवीन आवृत्ती ({newVersion}) उपलब्ध!", + "settingsUpdateDescription": "सध्याची आवृत्ती: {currentVersion} (अधिकृत बिल्ड) → {newVersion}", + "settingsUpdateButton": "अद्यतन करा", + "settingsUpdateWhatsNew": "काय नवीन आहे" +}, + "lockPage": { + "lockPage": "लॉक केलेले", + "reLockPage": "पुन्हा लॉक करा", + "lockTooltip": "अनवधानाने संपादन टाळण्यासाठी पृष्ठ लॉक केले आहे. अनलॉक करण्यासाठी क्लिक करा.", + "pageLockedToast": "पृष्ठ लॉक केले आहे. कोणी अनलॉक करेपर्यंत संपादन अक्षम आहे.", + "lockedOperationTooltip": "पृष्ठ अनवधानाने संपादनापासून वाचवण्यासाठी लॉक केले आहे." +}, + "suggestion": { + "accept": "स्वीकारा", + "keep": "जसे आहे तसे ठेवा", + "discard": "रद्द करा", + "close": "बंद करा", + "tryAgain": "पुन्हा प्रयत्न करा", + "rewrite": "पुन्हा लिहा", + "insertBelow": "खाली टाका" +} +} diff --git a/frontend/appflowy_flutter/distribute_options.yaml b/frontend/appflowy_flutter/distribute_options.yaml new file mode 100644 index 0000000000..60f603a938 --- /dev/null +++ b/frontend/appflowy_flutter/distribute_options.yaml @@ -0,0 +1,12 @@ +output: dist/ +releases: + - name: dev + jobs: + - name: release-dev-linux-deb + package: + platform: linux + target: deb + - name: release-dev-linux-rpm + package: + platform: linux + target: rpm diff --git a/frontend/appflowy_flutter/dsa_pub.pem b/frontend/appflowy_flutter/dsa_pub.pem new file mode 100644 index 0000000000..6a9d213b8a --- /dev/null +++ b/frontend/appflowy_flutter/dsa_pub.pem @@ -0,0 +1,36 @@ +-----BEGIN PUBLIC KEY----- +MIIGQzCCBDUGByqGSM44BAEwggQoAoICAQDlkozRmUnVH1MJFqOamAmUYu0YruaT +rrt6rCIZ0LFrfNnmHA4LOQEcXwBTTyn5sBmkPq+lb/rjmERKhmvl1rfo6q7tJ8mG +4TWqSu0tOJQ6QxexnNW4yhzK/r9MS5MQus4Al+y2hQLaAMOUIOnaWIrC9OHy7xyw ++sVipECVKyQqipS4shGUSqbcN+ocQuTB+I0MtIjBii0DGSEY3pxQrfNWjHFL7iTV +KiTn3YOWPJQvv3FvEDrN+5xU5JZpD97ZhXaJpLUyOQaNvcPaOELPWcOSJwqHOpf5 +b5N/VZ8SGbHNdxy9d5sSChBgtuAOihEhSp6SjFQ9eVHOf4NyJwSEMmi0gpdpqm4Z +QRJUnM2zIi0p9twR9FRYXzrxOs6yGCQEY+xFG93ShTLTj3zMrIyFqBsqEwFyJiJW +YWe/zp0V7UlLP+jXO9u9eghNmly7QVqD2P0qs/1V0jZFRuLWpsv4inau/qMZ5EhG +G4xCJZXfN1pkehy6e05/h+vs5anK3Wa/H8AtY6cK4CpzAanELvn3AH7VLbAhLswu +6d5CV+DoFgxCWMzGBSdmCYU+2wRLaL8Q9TZHDR+pvQlunEFdfFoGES9WjBPhAsVA +6Mq22U8XSje9yHI3X9Eqe/7a+ajSgcGmB7oQ11+4xf5h2PtubRW/JL0KMjxCxMTp +q1md6Ndx/ptBUwIdAIOyiKb2YcTLWAOt+LAlRXMsY1+W4pTXJfV6RcMCggIAPxbd +0HNj2O/aQhJxNZDMBIcx6+cZ+LKch7qLcaEpVqWHvDSnR2eOJJzWn0RoKK+Vuix/ +4T8texSQkWxAeFFdo6kyrR9XNL7hqEFFq8o9VpmvRzvG6h/bBgh3AHAQE3p/8Wrb +K13IhnlWqd0MjFufSphm63o0gaWl95j+6KeUoKQnioetu9HiMtFKx0d/KYqTQJg7 +hvR6VNCU2oShfXR3ce7RnUYwD37+djrUjUkoAZkZq2KoxBiKyeoSIeqAme19tKcO +s6b17mhALELuJ+NtDwlDunyiCDUYX9lTPijHwKeIFtBs38+OtRk3aIqmWTQdbsCz +Axp+kUMA5ESBME/RBNCSPHuDvtA3wfWvNbA5DXfZLwCgNSxhekq8XntIsRzfJ4v4 +uPzKFcVM3+sUUfSF04HHC9ol+PpLqXUyMnskiizqxFPq7H+6tyFZ7X2HiG6TjcfV +Wthmv+JyfcABjVnk2qFH7GagENbdtYmfUox13LhE59Sh5chaJnCFtCDp8NClWgZn +ixCOFQ9EgTLaH6MovTvWpEgG2MfBCu5SMUHi2qSflorqpRFH+rA7NZSnyz3wm7NB ++fJSOP0IjEkOh7MafU6Z61oK9WY/Fc+F1zIENVv8PUc3p75y/4RAp4xzyKcTilaN +C9U/3MRr3QmWwY7ejtZx6xdOxsvWBRDRSNbDdIkDggIGAAKCAgEAt1DHYZoeXY0r +vYXmxdNO6zfnbz1GGZHXpakzm9h4BrxPDP5J8DQ9ZeVVKg5+cU9AyMO3cZHp7wkx +k6IB+ZDUpqO1D3lWriRl2fI8cS4edI0fzpnW1nyhhFD4MbKmP+v27aH+DhZ4Up3y +GMmJTLmKiYx1EgZp7Sx77PBYDVMsKKd3h9+Hjp2YtUTfD2lleAmC+wcQGZiNtGw/ +eKpsmUVnWrepOdntWTtCQi1OvfcHaF2QmgktCq+68hbDNYWaXmzVIiQqrdv/zzOG +hCFIrRGWemrxL0iFG4Pzc4UfOINsISQLcUxRuF6pQWPxF8O/mWKfzAeqWxmIujUM +EoSEuI3yQ8VjlYpW/8FSK7UhgnHBHOpCJWPWs/vQXAnaUR2PYyzuIzhVEhFs8YA8 +iBIKnixIC2hu0YbEk3TBr/TRcbd7mDw9Mq7NT88xzdU13+Wh+4zhdX3rtBHYzBtI +7GaONGUNyY4h0duoyLpH6dxevaeKN6/bEdzYESjoE58QA88CpnAZGhJVphAba4cb +w6GTDhK3RlPWh6hRqJwLDILGtnJS3UKeBDRmKMqNuqmHqPjyAAvt9JBO8lzjoLgf +1cDsXHNWBVwA2jsX2CukNJPlY1Fa3MWhdaUXmy6QGMSisr1sptvBt1Phry8T2u+P +Y29SB4jvwqls268rP0cWqy4WXwlVwuc= +-----END PUBLIC KEY----- diff --git a/frontend/appflowy_flutter/integration_test/desktop/board/board_hide_groups_test.dart b/frontend/appflowy_flutter/integration_test/desktop/board/board_hide_groups_test.dart index 15da47f0f1..6a012ac763 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/board/board_hide_groups_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/board/board_hide_groups_test.dart @@ -23,24 +23,24 @@ void main() { final expandFinder = find.byFlowySvg(FlowySvgs.hamburger_s_s); // Is expanded by default - expect(collapseFinder, findsOneWidget); - expect(expandFinder, findsNothing); - - // Collapse hidden groups - await tester.tap(collapseFinder); - await tester.pumpAndSettle(); - - // Is collapsed expect(collapseFinder, findsNothing); expect(expandFinder, findsOneWidget); - // Expand hidden groups + // Collapse hidden groups await tester.tap(expandFinder); await tester.pumpAndSettle(); - // Is expanded + // Is collapsed expect(collapseFinder, findsOneWidget); expect(expandFinder, findsNothing); + + // Expand hidden groups + await tester.tap(collapseFinder); + await tester.pumpAndSettle(); + + // Is expanded + expect(collapseFinder, findsNothing); + expect(expandFinder, findsOneWidget); }); testWidgets('hide first group, and show it again', (tester) async { @@ -48,6 +48,9 @@ void main() { await tester.tapAnonymousSignInButton(); await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Board); + final expandFinder = find.byFlowySvg(FlowySvgs.hamburger_s_s); + await tester.tapButton(expandFinder); + // Tap the options of the first group final optionsFinder = find .descendant( diff --git a/frontend/appflowy_flutter/integration_test/desktop/cloud/cloud_runner.dart b/frontend/appflowy_flutter/integration_test/desktop/cloud/cloud_runner.dart index 0b35cffe51..a8c05d5f80 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/cloud/cloud_runner.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/cloud/cloud_runner.dart @@ -1,7 +1,9 @@ import 'data_migration/data_migration_test_runner.dart' as data_migration_test_runner; +import 'database/database_test_runner.dart' as database_test_runner; import 'document/document_test_runner.dart' as document_test_runner; import 'set_env.dart' as preset_af_cloud_env_test; +import 'sidebar/sidebar_icon_test.dart' as sidebar_icon_test; import 'sidebar/sidebar_move_page_test.dart' as sidebar_move_page_test; import 'sidebar/sidebar_rename_untitled_test.dart' as sidebar_rename_untitled_test; @@ -26,4 +28,8 @@ Future main() async { // sidebar sidebar_move_page_test.main(); sidebar_rename_untitled_test.main(); + sidebar_icon_test.main(); + + // database + database_test_runner.main(); } diff --git a/frontend/appflowy_flutter/integration_test/desktop/cloud/data_migration/anon_user_data_migration_test.dart b/frontend/appflowy_flutter/integration_test/desktop/cloud/data_migration/anon_user_data_migration_test.dart index 06c27093f9..e34ac02aab 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/cloud/data_migration/anon_user_data_migration_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/cloud/data_migration/anon_user_data_migration_test.dart @@ -15,7 +15,6 @@ void main() { cloudType: AuthenticatorType.appflowyCloudSelfHost, ); - await tester.tapContinousAnotherWay(); await tester.tapAnonymousSignInButton(); await tester.expectToSeeHomePageWithGetStartedPage(); @@ -31,12 +30,6 @@ void main() { await tester.enterUserName('local_user'); // Scroll to sign-in - await tester.scrollUntilVisible( - find.byType(AccountSignInOutButton), - 100, - scrollable: find.findSettingsScrollable(), - ); - await tester.tapButton(find.byType(AccountSignInOutButton)); // sign up with Google diff --git a/frontend/appflowy_flutter/integration_test/desktop/cloud/database/database_image_test.dart b/frontend/appflowy_flutter/integration_test/desktop/cloud/database/database_image_test.dart new file mode 100644 index 0000000000..5561d40033 --- /dev/null +++ b/frontend/appflowy_flutter/integration_test/desktop/cloud/database/database_image_test.dart @@ -0,0 +1,80 @@ +import 'dart:io'; + +import 'package:appflowy/core/config/kv.dart'; +import 'package:appflowy/core/config/kv_keys.dart'; +import 'package:appflowy/env/cloud_env.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/image/resizeable_image.dart'; +import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pbenum.dart'; +import 'package:appflowy_editor/appflowy_editor.dart' + hide UploadImageMenu, ResizableImage; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; +import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; + +import '../../../shared/constants.dart'; +import '../../../shared/database_test_op.dart'; +import '../../../shared/mock/mock_file_picker.dart'; +import '../../../shared/util.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + // copy link to block + group('database image:', () { + testWidgets('insert image', (tester) async { + await tester.initializeAppFlowy( + cloudType: AuthenticatorType.appflowyCloudSelfHost, + ); + await tester.tapGoogleLoginInButton(); + await tester.expectToSeeHomePageWithGetStartedPage(); + + // open the first row detail page and upload an image + await tester.createNewPageInSpace( + spaceName: Constants.generalSpaceName, + layout: ViewLayoutPB.Grid, + pageName: 'database image', + ); + await tester.openFirstRowDetailPage(); + + // insert an image block + { + await tester.editor.tapLineOfEditorAt(0); + await tester.editor.showSlashMenu(); + await tester.editor.tapSlashMenuItemWithName( + LocaleKeys.document_slashMenu_name_image.tr(), + ); + } + + // upload an image + { + final image = await rootBundle.load('assets/test/images/sample.jpeg'); + final tempDirectory = await getTemporaryDirectory(); + final imagePath = p.join(tempDirectory.path, 'sample.jpeg'); + final file = File(imagePath) + ..writeAsBytesSync(image.buffer.asUint8List()); + + mockPickFilePaths( + paths: [imagePath], + ); + + await getIt().set(KVKeys.kCloudType, '0'); + await tester.tapButtonWithName( + LocaleKeys.document_imageBlock_upload_placeholder.tr(), + ); + await tester.pumpAndSettle(); + expect(find.byType(ResizableImage), findsOneWidget); + final node = tester.editor.getCurrentEditorState().getNodeAtPath([0])!; + expect(node.type, ImageBlockKeys.type); + expect(node.attributes[ImageBlockKeys.url], isNotEmpty); + + // remove the temp file + file.deleteSync(); + } + }); + }); +} diff --git a/frontend/appflowy_flutter/integration_test/desktop/cloud/database/database_test_runner.dart b/frontend/appflowy_flutter/integration_test/desktop/cloud/database/database_test_runner.dart new file mode 100644 index 0000000000..4d1a623f07 --- /dev/null +++ b/frontend/appflowy_flutter/integration_test/desktop/cloud/database/database_test_runner.dart @@ -0,0 +1,9 @@ +import 'package:integration_test/integration_test.dart'; + +import 'database_image_test.dart' as database_image_test; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + database_image_test.main(); +} diff --git a/frontend/appflowy_flutter/integration_test/desktop/cloud/document/document_ai_writer_test.dart b/frontend/appflowy_flutter/integration_test/desktop/cloud/document/document_ai_writer_test.dart index 32737a23d1..f163608ccb 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/cloud/document/document_ai_writer_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/cloud/document/document_ai_writer_test.dart @@ -33,7 +33,7 @@ void main() { await tester.editor.tapSlashMenuItemWithName( LocaleKeys.document_slashMenu_name_aiWriter.tr(), ); - expect(find.byType(AIWriterBlockComponent), findsOneWidget); + expect(find.byType(AiWriterBlockComponent), findsOneWidget); // switch to another page await tester.openPage(Constants.gettingStartedPageName); @@ -41,7 +41,7 @@ void main() { await tester.openPage(pageName); // expect the ai writer block is not in the document - expect(find.byType(AIWriterBlockComponent), findsNothing); + expect(find.byType(AiWriterBlockComponent), findsNothing); }); }); } diff --git a/frontend/appflowy_flutter/integration_test/desktop/cloud/document/document_option_actions_test.dart b/frontend/appflowy_flutter/integration_test/desktop/cloud/document/document_option_actions_test.dart index 0289fbe176..1bc9bd8f92 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/cloud/document/document_option_actions_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/cloud/document/document_option_actions_test.dart @@ -57,7 +57,7 @@ void main() { // move the checkbox to the child of the block at path [9] await tester.editor.dragBlock( [10], - const Offset(80, -30), + const Offset(120, -20), ); // wait for the move animation to complete diff --git a/frontend/appflowy_flutter/integration_test/desktop/cloud/sidebar/sidebar_icon_test.dart b/frontend/appflowy_flutter/integration_test/desktop/cloud/sidebar/sidebar_icon_test.dart new file mode 100644 index 0000000000..5bcca50153 --- /dev/null +++ b/frontend/appflowy_flutter/integration_test/desktop/cloud/sidebar/sidebar_icon_test.dart @@ -0,0 +1,62 @@ +import 'dart:convert'; + +import 'package:appflowy/env/cloud_env.dart'; +import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; +import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart'; +import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart'; +import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/sidebar_space_header.dart'; +import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_action_type.dart'; +import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_more_popup.dart'; +import 'package:flowy_svg/flowy_svg.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../../../shared/emoji.dart'; +import '../../../shared/util.dart'; + +void main() { + setUpAll(() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + RecentIcons.enable = false; + }); + + tearDownAll(() { + RecentIcons.enable = true; + }); + + testWidgets('Change slide bar space icon', (tester) async { + await tester.initializeAppFlowy( + cloudType: AuthenticatorType.appflowyCloudSelfHost, + ); + await tester.tapGoogleLoginInButton(); + await tester.expectToSeeHomePageWithGetStartedPage(); + final emojiIconData = await tester.loadIcon(); + final firstIcon = IconsData.fromJson(jsonDecode(emojiIconData.emoji)); + + await tester.hoverOnWidget( + find.byType(SidebarSpaceHeader), + onHover: () async { + final moreOption = find.byType(SpaceMorePopup); + await tester.tapButton(moreOption); + expect(find.byType(FlowyIconEmojiPicker), findsNothing); + await tester.tapSvgButton(SpaceMoreActionType.changeIcon.leftIconSvg); + expect(find.byType(FlowyIconEmojiPicker), findsOneWidget); + }, + ); + + final icons = find.byWidgetPredicate( + (w) => w is FlowySvg && w.svgString == firstIcon.svgString, + ); + expect(icons, findsOneWidget); + await tester.tapIcon(EmojiIconData.icon(firstIcon)); + + final spaceHeader = find.byType(SidebarSpaceHeader); + final spaceIcon = find.descendant( + of: spaceHeader, + matching: find.byWidgetPredicate( + (w) => w is FlowySvg && w.svgString == firstIcon.svgString, + ), + ); + expect(spaceIcon, findsOneWidget); + }); +} diff --git a/frontend/appflowy_flutter/integration_test/desktop/cloud/uncategorized/appflowy_cloud_auth_test.dart b/frontend/appflowy_flutter/integration_test/desktop/cloud/uncategorized/appflowy_cloud_auth_test.dart index 0b649fd6d5..fd65c29927 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/cloud/uncategorized/appflowy_cloud_auth_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/cloud/uncategorized/appflowy_cloud_auth_test.dart @@ -57,12 +57,6 @@ void main() { await tester.openSettings(); await tester.openSettingsPage(SettingsPage.account); - // Scroll to sign-in - await tester.scrollUntilVisible( - find.byType(AccountSignInOutButton), - 100, - scrollable: find.findSettingsScrollable(), - ); await tester.tapButton(find.byType(AccountSignInOutButton)); tester.expectToSeeGoogleLoginButton(); diff --git a/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/collaborative_workspace_test.dart b/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/collaborative_workspace_test.dart index 2de6fb8fa7..4d2e027646 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/collaborative_workspace_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/collaborative_workspace_test.dart @@ -1,10 +1,11 @@ import 'package:appflowy/env/cloud_env.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart'; import 'package:appflowy/shared/feature_flags.dart'; +import 'package:appflowy/shared/loading.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -102,8 +103,7 @@ void main() { expect(memberCount, findsNWidgets(2)); }); - testWidgets('only display one menu item in the workspace menu', - (tester) async { + testWidgets('workspace menu popover behavior test', (tester) async { // only run the test when the feature flag is on if (!FeatureFlag.collaborativeWorkspace.isOn) { return; @@ -128,6 +128,8 @@ void main() { final workspaceItem = find.byWidgetPredicate( (w) => w is WorkspaceMenuItem && w.workspace.name == name, ); + + // the workspace menu shouldn't conflict with logout await tester.hoverOnWidget( workspaceItem, onHover: () async { @@ -136,15 +138,73 @@ void main() { ); expect(moreButton, findsOneWidget); await tester.tapButton(moreButton); + expect(find.text(LocaleKeys.button_rename.tr()), findsOneWidget); + + final logoutButton = find.byType(WorkspaceMoreButton); + await tester.tapButton(logoutButton); + expect(find.text(LocaleKeys.button_logout.tr()), findsOneWidget); + expect(moreButton, findsNothing); + + await tester.tapButton(moreButton); + expect(find.text(LocaleKeys.button_logout.tr()), findsNothing); + expect(moreButton, findsOneWidget); + }, + ); + await tester.sendKeyEvent(LogicalKeyboardKey.escape); + await tester.pumpAndSettle(); + + // clicking on the more action button for the same workspace shouldn't do + // anything + await tester.openCollaborativeWorkspaceMenu(); + await tester.hoverOnWidget( + workspaceItem, + onHover: () async { + final moreButton = find.byWidgetPredicate( + (w) => w is WorkspaceMoreActionList && w.workspace.name == name, + ); + expect(moreButton, findsOneWidget); + await tester.tapButton(moreButton); + expect(find.text(LocaleKeys.button_rename.tr()), findsOneWidget); // click it again await tester.tapButton(moreButton); // nothing should happen - expect( - find.text(LocaleKeys.button_rename.tr()), - findsOneWidget, - ); + expect(find.text(LocaleKeys.button_rename.tr()), findsOneWidget); + }, + ); + await tester.sendKeyEvent(LogicalKeyboardKey.escape); + await tester.pumpAndSettle(); + + // clicking on the more button of another workspace should close the menu + // for this one + await tester.openCollaborativeWorkspaceMenu(); + final moreButton = find.byWidgetPredicate( + (w) => w is WorkspaceMoreActionList && w.workspace.name == name, + ); + await tester.hoverOnWidget( + workspaceItem, + onHover: () async { + expect(moreButton, findsOneWidget); + await tester.tapButton(moreButton); + expect(find.text(LocaleKeys.button_rename.tr()), findsOneWidget); + }, + ); + + final otherWorspaceItem = find.byWidgetPredicate( + (w) => w is WorkspaceMenuItem && w.workspace.name != name, + ); + final otherMoreButton = find.byWidgetPredicate( + (w) => w is WorkspaceMoreActionList && w.workspace.name != name, + ); + await tester.hoverOnWidget( + otherWorspaceItem, + onHover: () async { + expect(otherMoreButton, findsOneWidget); + await tester.tapButton(otherMoreButton); + expect(find.text(LocaleKeys.button_rename.tr()), findsOneWidget); + + expect(moreButton, findsNothing); }, ); }); diff --git a/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/share_menu_test.dart b/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/share_menu_test.dart index c2dc378386..70bb46279e 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/share_menu_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/share_menu_test.dart @@ -54,7 +54,7 @@ void main() { ); final shareValues = plainText! - .replaceAll('https://${ShareConstants.shareBaseUrl}/', '') + .replaceAll('${ShareConstants.defaultBaseWebDomain}/app/', '') .split('/'); final workspaceId = shareValues[0]; expect(workspaceId, isNotEmpty); diff --git a/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/tabs_test.dart b/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/tabs_test.dart index 6661075878..5c07d99afa 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/tabs_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/tabs_test.dart @@ -1,5 +1,6 @@ import 'package:appflowy/env/cloud_env.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart'; +import 'package:appflowy/plugins/document/presentation/editor_page.dart'; +import 'package:appflowy/shared/loading.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart'; import 'package:appflowy/workspace/presentation/home/tabs/flowy_tab.dart'; import 'package:appflowy/workspace/presentation/home/tabs/tabs_manager.dart'; @@ -71,5 +72,16 @@ void main() { expect(find.byType(FlowyTab), findsNothing); }); + + testWidgets('the space view should not be opened', (tester) async { + await tester.initializeAppFlowy( + cloudType: AuthenticatorType.appflowyCloudSelfHost, + ); + await tester.tapGoogleLoginInButton(); + await tester.expectToSeeHomePageWithGetStartedPage(); + + expect(find.byType(AppFlowyEditorPage), findsNothing); + expect(find.text('Blank page'), findsOne); + }); }); } diff --git a/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/workspace_settings_test.dart b/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/workspace_settings_test.dart index 8b081c04c6..a58fea25b8 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/workspace_settings_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/cloud/workspace/workspace_settings_test.dart @@ -120,6 +120,10 @@ void main() { widget is PublishedViewItem && widget.publishInfoView.view.name == pageName, ); + if (pageItem.evaluate().isEmpty) { + return; + } + expect(pageItem, findsOneWidget); // comment it out because it's not allowed to update the namespace in free plan @@ -249,7 +253,7 @@ More actions for published page: await tester.openSettings(); await tester.openSettingsPage(SettingsPage.sites); // wait the backend return the sites data - await tester.wait(1000); + await tester.wait(2000); // check if the page is published in sites page final pageItem = find.byWidgetPredicate( @@ -257,6 +261,10 @@ More actions for published page: widget is PublishedViewItem && widget.publishInfoView.view.name == pageName, ); + if (pageItem.evaluate().isEmpty) { + return; + } + expect(pageItem, findsOneWidget); final copyLinkItem = find.text(LocaleKeys.shareAction_copyLink.tr()); diff --git a/frontend/appflowy_flutter/integration_test/desktop/command_palette/folder_search_test.dart b/frontend/appflowy_flutter/integration_test/desktop/command_palette/folder_search_test.dart index 80c907b9e9..0b77a0167b 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/command_palette/folder_search_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/command_palette/folder_search_test.dart @@ -1,6 +1,15 @@ +import 'dart:convert'; +import 'dart:math'; + +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart'; import 'package:appflowy/workspace/presentation/command_palette/command_palette.dart'; import 'package:appflowy/workspace/presentation/command_palette/widgets/search_field.dart'; -import 'package:appflowy/workspace/presentation/command_palette/widgets/search_result_tile.dart'; +import 'package:appflowy/workspace/presentation/command_palette/widgets/search_result_cell.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pbenum.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -8,7 +17,9 @@ import 'package:integration_test/integration_test.dart'; import '../../shared/util.dart'; void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + setUpAll(() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + }); group('Folder Search', () { testWidgets('Search for views', (tester) async { @@ -33,21 +44,106 @@ void main() { await tester.pumpAndSettle(const Duration(milliseconds: 200)); // Expect two search results "ViewOna" and "ViewOne" (Distance 1 to ViewOna) - expect(find.byType(SearchResultTile), findsNWidgets(2)); + expect(find.byType(SearchResultCell), findsNWidgets(2)); // The score should be higher for "ViewOna" thus it should be shown first final secondDocumentWidget = tester - .widget(find.byType(SearchResultTile).first) as SearchResultTile; - expect(secondDocumentWidget.result.data, secondDocument); + .widget(find.byType(SearchResultCell).first) as SearchResultCell; + expect(secondDocumentWidget.item.displayName, secondDocument); // Change search to "ViewOne" await tester.enterText(searchFieldFinder, firstDocument); await tester.pumpAndSettle(const Duration(seconds: 1)); // The score should be higher for "ViewOne" thus it should be shown first - final firstDocumentWidget = tester - .widget(find.byType(SearchResultTile).first) as SearchResultTile; - expect(firstDocumentWidget.result.data, firstDocument); + final firstDocumentWidget = tester.widget( + find.byType(SearchResultCell).first, + ) as SearchResultCell; + expect(firstDocumentWidget.item.displayName, firstDocument); + }); + + testWidgets('Displaying icons in search results', (tester) async { + final randomValue = Random().nextInt(10000) + 10000; + final pageNames = ['First Page-$randomValue', 'Second Page-$randomValue']; + + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + final emojiIconData = await tester.loadIcon(); + + /// create two pages + for (final pageName in pageNames) { + await tester.createNewPageWithNameUnderParent(name: pageName); + await tester.updatePageIconInTitleBarByName( + name: pageName, + layout: ViewLayoutPB.Document, + icon: emojiIconData, + ); + } + + await tester.toggleCommandPalette(); + + /// search for `Page` + final searchFieldFinder = find.descendant( + of: find.byType(SearchField), + matching: find.byType(FlowyTextField), + ); + await tester.enterText(searchFieldFinder, 'Page-$randomValue'); + await tester.pumpAndSettle(const Duration(milliseconds: 200)); + expect(find.byType(SearchResultCell), findsNWidgets(2)); + + /// check results + final svgs = find.descendant( + of: find.byType(SearchResultCell), + matching: find.byType(FlowySvg), + ); + expect(svgs, findsNWidgets(2)); + + final firstSvg = svgs.first.evaluate().first.widget as FlowySvg, + lastSvg = svgs.last.evaluate().first.widget as FlowySvg; + final iconData = IconsData.fromJson(jsonDecode(emojiIconData.emoji)); + + /// icon displayed correctly + expect(firstSvg.svgString, iconData.svgString); + expect(lastSvg.svgString, iconData.svgString); + + testWidgets('select the content in document and search', (tester) async { + const firstDocument = ''; // empty document + + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + + await tester.createNewPageWithNameUnderParent(name: firstDocument); + await tester.editor.updateSelection( + Selection( + start: Position( + path: [0], + ), + end: Position( + path: [0], + offset: 10, + ), + ), + ); + await tester.pumpAndSettle(); + + expect( + find.byType(FloatingToolbar), + findsOneWidget, + ); + + await tester.toggleCommandPalette(); + expect(find.byType(CommandPaletteModal), findsOneWidget); + + expect( + find.text(LocaleKeys.menuAppHeader_defaultNewPageName.tr()), + findsOneWidget, + ); + + expect( + find.text(firstDocument), + findsOneWidget, + ); + }); }); }); } diff --git a/frontend/appflowy_flutter/integration_test/desktop/command_palette/recent_history_test.dart b/frontend/appflowy_flutter/integration_test/desktop/command_palette/recent_history_test.dart index 277ae8f21e..b9495ae0e7 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/command_palette/recent_history_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/command_palette/recent_history_test.dart @@ -1,5 +1,5 @@ import 'package:appflowy/workspace/presentation/command_palette/command_palette.dart'; -import 'package:appflowy/workspace/presentation/command_palette/widgets/recent_view_tile.dart'; +import 'package:appflowy/workspace/presentation/command_palette/widgets/search_recent_view_cell.dart'; import 'package:appflowy/workspace/presentation/command_palette/widgets/recent_views_list.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -27,11 +27,12 @@ void main() { expect(find.byType(RecentViewsList), findsOneWidget); // Expect three recent history items - expect(find.byType(RecentViewTile), findsNWidgets(3)); + expect(find.byType(SearchRecentViewCell), findsNWidgets(3)); // Expect the first item to be the last viewed document final firstDocumentWidget = - tester.widget(find.byType(RecentViewTile).first) as RecentViewTile; + tester.widget(find.byType(SearchRecentViewCell).first) + as SearchRecentViewCell; expect(firstDocumentWidget.view.name, secondDocument); }); }); diff --git a/frontend/appflowy_flutter/integration_test/desktop/database/database_calendar_test.dart b/frontend/appflowy_flutter/integration_test/desktop/database/database_calendar_test.dart index d6df648bb3..3a565cbee9 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/database/database_calendar_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/database/database_calendar_test.dart @@ -1,4 +1,5 @@ import 'package:appflowy/plugins/database/calendar/presentation/calendar_event_editor.dart'; +import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pbenum.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart'; @@ -9,7 +10,14 @@ import '../../shared/database_test_op.dart'; import '../../shared/util.dart'; void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + setUpAll(() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + RecentIcons.enable = false; + }); + + tearDownAll(() { + RecentIcons.enable = true; + }); group('calendar', () { testWidgets('update calendar layout', (tester) async { diff --git a/frontend/appflowy_flutter/integration_test/desktop/database/database_field_settings_test.dart b/frontend/appflowy_flutter/integration_test/desktop/database/database_field_settings_test.dart index 9b9434d3d7..a71110f1e0 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/database/database_field_settings_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/database/database_field_settings_test.dart @@ -15,6 +15,7 @@ void main() { await tester.initializeAppFlowy(); await tester.tapAnonymousSignInButton(); + // create a database and add a linked database view await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid); await tester.tapCreateLinkedDatabaseViewButton(DatabaseLayoutPB.Grid); @@ -29,6 +30,11 @@ void main() { await tester.tapHidePropertyButton(); tester.noFieldWithName('New field 1'); + // create another field, New field 1 to be hidden still + await tester.tapNewPropertyButton(); + await tester.dismissFieldEditor(); + tester.noFieldWithName('New field 1'); + // go back to inline database view, expect field to be shown await tester.tapTabBarLinkedViewByViewName('Untitled'); tester.findFieldWithName('New field 1'); @@ -60,5 +66,40 @@ void main() { await tester.tapDatabaseSortButton(); await tester.tapCreateSortByFieldType(FieldType.RichText, "New field 1"); }); + + testWidgets('field cell width', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + + // create a database and add a linked database view + await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid); + await tester.tapCreateLinkedDatabaseViewButton(DatabaseLayoutPB.Grid); + + // create a field + await tester.scrollToRight(find.byType(GridPage)); + await tester.tapNewPropertyButton(); + await tester.renameField('New field 1'); + await tester.dismissFieldEditor(); + + // check the width of the field + expect(tester.getFieldWidth('New field 1'), 150); + + // change the width of the field + await tester.changeFieldWidth('New field 1', 200); + expect(tester.getFieldWidth('New field 1'), 205); + + // create another field, New field 1 to be same width + await tester.tapNewPropertyButton(); + await tester.dismissFieldEditor(); + expect(tester.getFieldWidth('New field 1'), 205); + + // go back to inline database view, expect New field 1 to be 150px + await tester.tapTabBarLinkedViewByViewName('Untitled'); + expect(tester.getFieldWidth('New field 1'), 150); + + // go back to linked database view, expect New field 1 to be 205px + await tester.tapTabBarLinkedViewByViewName('Grid'); + expect(tester.getFieldWidth('New field 1'), 205); + }); }); } diff --git a/frontend/appflowy_flutter/integration_test/desktop/database/database_field_test.dart b/frontend/appflowy_flutter/integration_test/desktop/database/database_field_test.dart index 1422aa8aee..6ce248a8a1 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/database/database_field_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/database/database_field_test.dart @@ -1,12 +1,12 @@ -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart'; import 'package:appflowy/plugins/database/widgets/field/type_option_editor/select/select_option.dart'; +import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart'; import 'package:appflowy/util/field_type_extension.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -14,7 +14,14 @@ import '../../shared/database_test_op.dart'; import '../../shared/util.dart'; void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + setUpAll(() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + RecentIcons.enable = false; + }); + + tearDownAll(() { + RecentIcons.enable = true; + }); group('grid edit field test:', () { testWidgets('rename existing field', (tester) async { diff --git a/frontend/appflowy_flutter/integration_test/desktop/database/database_icon_test.dart b/frontend/appflowy_flutter/integration_test/desktop/database/database_icon_test.dart new file mode 100644 index 0000000000..e6a629ded5 --- /dev/null +++ b/frontend/appflowy_flutter/integration_test/desktop/database/database_icon_test.dart @@ -0,0 +1,190 @@ +import 'dart:convert'; + +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/database/tab_bar/desktop/tab_bar_add_button.dart'; +import 'package:appflowy/plugins/database/tab_bar/desktop/tab_bar_header.dart'; +import 'package:appflowy/plugins/database/widgets/database_layout_ext.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart'; +import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart'; +import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart'; +import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/sidebar_folder.dart'; +import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../../shared/emoji.dart'; +import '../../shared/util.dart'; + +void main() { + setUpAll(() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + RecentIcons.enable = false; + }); + + tearDownAll(() { + RecentIcons.enable = true; + }); + + testWidgets('change icon', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + final iconData = await tester.loadIcon(); + + const pageName = 'Database'; + await tester.createNewPageWithNameUnderParent( + layout: ViewLayoutPB.Grid, + name: pageName, + ); + + /// create board + final addButton = find.byType(AddDatabaseViewButton); + await tester.tapButton(addButton); + await tester.tapButton( + find.text( + '${LocaleKeys.grid_createView.tr()} ${DatabaseLayoutPB.Board.layoutName}', + findRichText: true, + ), + ); + + /// create calendar + await tester.tapButton(addButton); + await tester.tapButton( + find.text( + '${LocaleKeys.grid_createView.tr()} ${DatabaseLayoutPB.Calendar.layoutName}', + findRichText: true, + ), + ); + + final databaseTabBarItem = find.byType(DatabaseTabBarItem); + expect(databaseTabBarItem, findsNWidgets(3)); + final gridItem = databaseTabBarItem.first, + boardItem = databaseTabBarItem.at(1), + calendarItem = databaseTabBarItem.last; + + /// change the icon of grid + /// the first tapping is to select specific item + /// the second tapping is to show the menu + await tester.tapButton(gridItem); + await tester.tapButton(gridItem); + + /// change icon + await tester + .tapButton(find.text(LocaleKeys.disclosureAction_changeIcon.tr())); + await tester.tapIcon(iconData, enableColor: false); + final gridIcon = find.descendant( + of: gridItem, + matching: find.byType(RawEmojiIconWidget), + ); + final gridIconWidget = + gridIcon.evaluate().first.widget as RawEmojiIconWidget; + final iconsData = IconsData.fromJson(jsonDecode(iconData.emoji)); + final gridIconsData = + IconsData.fromJson(jsonDecode(gridIconWidget.emoji.emoji)); + expect(gridIconsData.iconName, iconsData.iconName); + + /// change the icon of board + await tester.tapButton(boardItem); + await tester.tapButton(boardItem); + await tester + .tapButton(find.text(LocaleKeys.disclosureAction_changeIcon.tr())); + await tester.tapIcon(iconData, enableColor: false); + final boardIcon = find.descendant( + of: boardItem, + matching: find.byType(RawEmojiIconWidget), + ); + final boardIconWidget = + boardIcon.evaluate().first.widget as RawEmojiIconWidget; + final boardIconsData = + IconsData.fromJson(jsonDecode(boardIconWidget.emoji.emoji)); + expect(boardIconsData.iconName, iconsData.iconName); + + /// change the icon of calendar + await tester.tapButton(calendarItem); + await tester.tapButton(calendarItem); + await tester + .tapButton(find.text(LocaleKeys.disclosureAction_changeIcon.tr())); + await tester.tapIcon(iconData, enableColor: false); + final calendarIcon = find.descendant( + of: calendarItem, + matching: find.byType(RawEmojiIconWidget), + ); + final calendarIconWidget = + calendarIcon.evaluate().first.widget as RawEmojiIconWidget; + final calendarIconsData = + IconsData.fromJson(jsonDecode(calendarIconWidget.emoji.emoji)); + expect(calendarIconsData.iconName, iconsData.iconName); + }); + + testWidgets('change database icon from sidebar', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + final iconData = await tester.loadIcon(); + final icon = IconsData.fromJson(jsonDecode(iconData.emoji)), emoji = '😄'; + + const pageName = 'Database'; + await tester.createNewPageWithNameUnderParent( + layout: ViewLayoutPB.Grid, + name: pageName, + ); + final viewItem = find.descendant( + of: find.byType(SidebarFolder), + matching: find.byWidgetPredicate( + (w) => w is ViewItem && w.view.name == pageName, + ), + ); + + /// change icon to emoji + await tester.tapButton( + find.descendant( + of: viewItem, + matching: find.byType(FlowySvg), + ), + ); + await tester.tapEmoji(emoji); + final iconWidget = find.descendant( + of: viewItem, + matching: find.byType(RawEmojiIconWidget), + ); + expect( + (iconWidget.evaluate().first.widget as RawEmojiIconWidget).emoji.emoji, + emoji, + ); + + /// the icon will not be displayed in database item + Finder databaseIcon = find.descendant( + of: find.byType(DatabaseTabBarItem), + matching: find.byType(FlowySvg), + ); + expect( + (databaseIcon.evaluate().first.widget as FlowySvg).svg, + FlowySvgs.icon_grid_s, + ); + + /// change emoji to icon + await tester.tapButton(iconWidget); + await tester.tapIcon(iconData); + expect( + (iconWidget.evaluate().first.widget as RawEmojiIconWidget).emoji.emoji, + iconData.emoji, + ); + + databaseIcon = find.descendant( + of: find.byType(DatabaseTabBarItem), + matching: find.byType(RawEmojiIconWidget), + ); + final databaseIconWidget = + databaseIcon.evaluate().first.widget as RawEmojiIconWidget; + final databaseIconsData = + IconsData.fromJson(jsonDecode(databaseIconWidget.emoji.emoji)); + expect(icon.svgString, databaseIconsData.svgString); + expect(icon.color, isNotEmpty); + expect(icon.color, databaseIconsData.color); + + /// the icon in database item should not show the color + expect(databaseIconWidget.enableColor, false); + }); +} diff --git a/frontend/appflowy_flutter/integration_test/desktop/database/database_view_test.dart b/frontend/appflowy_flutter/integration_test/desktop/database/database_view_test.dart index e35c9cc9d8..71656c1ea6 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/database/database_view_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/database/database_view_test.dart @@ -1,5 +1,10 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -73,5 +78,37 @@ void main() { await tester.pumpAndSettle(); }); + + testWidgets('insert grid in column', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + + /// create page and show slash menu + await tester.createNewPageWithNameUnderParent(name: 'test page'); + await tester.editor.tapLineOfEditorAt(0); + await tester.editor.showSlashMenu(); + await tester.pumpAndSettle(); + + /// create a column + await tester.editor.tapSlashMenuItemWithName( + LocaleKeys.document_slashMenu_name_twoColumns.tr(), + ); + final actionList = find.byType(BlockActionList); + expect(actionList, findsNWidgets(2)); + final position = tester.getCenter(actionList.last); + + /// tap the second child of column + await tester.tapAt(position.copyWith(dx: position.dx + 50)); + + /// create a grid + await tester.editor.showSlashMenu(); + await tester.pumpAndSettle(); + await tester.editor.tapSlashMenuItemWithName( + LocaleKeys.document_slashMenu_name_grid.tr(), + ); + + final grid = find.byType(GridPageContent); + expect(grid, findsOneWidget); + }); }); } diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_alignment_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_alignment_test.dart index d95d907881..1a8a3fcda8 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/document/document_alignment_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_alignment_test.dart @@ -27,8 +27,9 @@ void main() { await tester.pumpAndSettle(); // click the align center - await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_align_left_s); - await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_align_center_s); + await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_alignment_m); + await tester + .tapButtonWithFlowySvgData(FlowySvgs.toolbar_text_align_center_m); // expect to see the align center final editorState = tester.editor.getCurrentEditorState(); @@ -36,13 +37,15 @@ void main() { expect(first.attributes[blockComponentAlign], 'center'); // click the align right - await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_align_center_s); - await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_align_right_s); + await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_alignment_m); + await tester + .tapButtonWithFlowySvgData(FlowySvgs.toolbar_text_align_right_m); expect(first.attributes[blockComponentAlign], 'right'); // click the align left - await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_align_right_s); - await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_align_left_s); + await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_alignment_m); + await tester + .tapButtonWithFlowySvgData(FlowySvgs.toolbar_text_align_left_m); expect(first.attributes[blockComponentAlign], 'left'); }); @@ -75,7 +78,7 @@ void main() { [ LogicalKeyboardKey.control, LogicalKeyboardKey.shift, - LogicalKeyboardKey.keyE, + LogicalKeyboardKey.keyC, ], tester: tester, withKeyUp: true, diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_callout_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_callout_test.dart new file mode 100644 index 0000000000..b5449ec622 --- /dev/null +++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_callout_test.dart @@ -0,0 +1,67 @@ +import 'dart:convert'; + +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/base/icon/icon_widget.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart'; +import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart'; +import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../../shared/emoji.dart'; +import '../../shared/util.dart'; + +void main() { + setUpAll(() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + RecentIcons.enable = false; + }); + + tearDownAll(() { + RecentIcons.enable = true; + }); + + testWidgets('callout with emoji icon picker', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + final emojiIconData = await tester.loadIcon(); + + /// create a new document + await tester.createNewPageWithNameUnderParent(); + + /// tap the first line of the document + await tester.editor.tapLineOfEditorAt(0); + + /// create callout + await tester.editor.showSlashMenu(); + await tester.pumpAndSettle(); + await tester.editor.tapSlashMenuItemWithName( + LocaleKeys.document_slashMenu_name_callout.tr(), + ); + + /// select an icon + final emojiPickerButton = find.descendant( + of: find.byType(CalloutBlockComponentWidget), + matching: find.byType(EmojiPickerButton), + ); + await tester.tapButton(emojiPickerButton); + await tester.tapIcon(emojiIconData); + + /// verification results + final iconData = IconsData.fromJson(jsonDecode(emojiIconData.emoji)); + final iconWidget = find + .descendant( + of: emojiPickerButton, + matching: find.byType(IconWidget), + ) + .evaluate() + .first + .widget as IconWidget; + final iconWidgetData = iconWidget.iconsData; + expect(iconWidgetData.svgString, iconData.svgString); + expect(iconWidgetData.iconName, iconData.iconName); + expect(iconWidgetData.groupName, iconData.groupName); + }); +} diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_copy_and_paste_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_copy_and_paste_test.dart index 8da4aeccce..d1e34edcb5 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/document/document_copy_and_paste_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_copy_and_paste_test.dart @@ -1,10 +1,12 @@ +import 'dart:async'; import 'dart:io'; import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/block_menu/block_menu_button.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/image/custom_image_block_component/custom_image_block_component.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/custom_link_preview.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/link_preview_menu.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/paste_as/paste_as_menu.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; @@ -19,7 +21,7 @@ import '../../shared/util.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - group('copy and paste in document', () { + group('copy and paste in document:', () { testWidgets('paste multiple lines at the first line', (tester) async { // mock the clipboard const lines = 3; @@ -172,305 +174,316 @@ void main() { }, ); }); - }); - testWidgets('paste text on part of bullet list', (tester) async { - const plainText = 'test'; + testWidgets('paste text on part of bullet list', (tester) async { + const plainText = 'test'; - await tester.pasteContent( - plainText: plainText, - beforeTest: (editorState) async { - final transaction = editorState.transaction; - transaction.insertNodes( - [0], - [ - Node( - type: BulletedListBlockKeys.type, - attributes: { - 'delta': [ - {"insert": "bullet list"}, - ], - }, - ), - ], - ); - - // Set the selection to the second numbered list node (which has empty delta) - transaction.afterSelection = Selection( - start: Position(path: [0], offset: 7), - end: Position(path: [0], offset: 11), - ); - - await editorState.apply(transaction); - await tester.pumpAndSettle(); - }, - (editorState) { - final node = editorState.getNodeAtPath([0]); - expect(node?.delta?.toPlainText(), 'bullet test'); - expect(node?.type, BulletedListBlockKeys.type); - }, - ); - }); - - testWidgets('paste image(png) from memory', (tester) async { - final image = await rootBundle.load('assets/test/images/sample.png'); - final bytes = image.buffer.asUint8List(); - await tester.pasteContent(image: ('png', bytes), (editorState) { - expect(editorState.document.root.children.length, 1); - final node = editorState.getNodeAtPath([0])!; - expect(node.type, ImageBlockKeys.type); - expect(node.attributes[ImageBlockKeys.url], isNotNull); - }); - }); - - testWidgets('paste image(jpeg) from memory', (tester) async { - final image = await rootBundle.load('assets/test/images/sample.jpeg'); - final bytes = image.buffer.asUint8List(); - await tester.pasteContent(image: ('jpeg', bytes), (editorState) { - expect(editorState.document.root.children.length, 1); - final node = editorState.getNodeAtPath([0])!; - expect(node.type, ImageBlockKeys.type); - expect(node.attributes[ImageBlockKeys.url], isNotNull); - }); - }); - - testWidgets('paste image(gif) from memory', (tester) async { - final image = await rootBundle.load('assets/test/images/sample.gif'); - final bytes = image.buffer.asUint8List(); - await tester.pasteContent(image: ('gif', bytes), (editorState) { - expect(editorState.document.root.children.length, 1); - final node = editorState.getNodeAtPath([0])!; - expect(node.type, ImageBlockKeys.type); - expect(node.attributes[ImageBlockKeys.url], isNotNull); - }); - }); - - testWidgets( - 'format the selected text to href when pasting url if available', - (tester) async { - const text = 'appflowy'; - const url = 'https://appflowy.io'; await tester.pasteContent( - plainText: url, + plainText: plainText, beforeTest: (editorState) async { - await tester.ime.insertText(text); - await tester.editor.updateSelection( - Selection.single( - path: [0], - startOffset: 0, - endOffset: text.length, - ), + final transaction = editorState.transaction; + transaction.insertNodes( + [0], + [ + Node( + type: BulletedListBlockKeys.type, + attributes: { + 'delta': [ + {"insert": "bullet list"}, + ], + }, + ), + ], ); - }, - (editorState) { - final node = editorState.getNodeAtPath([0])!; - expect(node.type, ParagraphBlockKeys.type); - expect(node.delta!.toJson(), [ - { - 'insert': text, - 'attributes': {'href': url}, - } - ]); - }, - ); - }, - ); - // https://github.com/AppFlowy-IO/AppFlowy/issues/3263 - testWidgets( - 'paste the image from clipboard when html and image are both available', - (tester) async { - const html = - '''image'''; - final image = await rootBundle.load('assets/test/images/sample.png'); - final bytes = image.buffer.asUint8List(); - await tester.pasteContent( - html: html, - image: ('png', bytes), - (editorState) { - expect(editorState.document.root.children.length, 1); - final node = editorState.getNodeAtPath([0])!; - expect(node.type, ImageBlockKeys.type); - }, - ); - }, - ); + // Set the selection to the second numbered list node (which has empty delta) + transaction.afterSelection = Selection( + start: Position(path: [0], offset: 7), + end: Position(path: [0], offset: 11), + ); - testWidgets('paste the html content contains section', (tester) async { - const html = - '''

AppFlowy
Hello World
'''; - await tester.pasteContent(html: html, (editorState) { - expect(editorState.document.root.children.length, 2); - final node1 = editorState.getNodeAtPath([0])!; - final node2 = editorState.getNodeAtPath([1])!; - expect(node1.type, ParagraphBlockKeys.type); - expect(node2.type, ParagraphBlockKeys.type); - }); - }); - - testWidgets('paste the html from google translation', (tester) async { - const html = - '''
new force
Assessment focus: potential motivations, empathy

➢Personality characteristics and potential motivations:
-Reflection of self-worth
-Need to be respected
-Have a unique definition of success
-Be true to your own lifestyle
'''; - await tester.pasteContent(html: html, (editorState) { - expect(editorState.document.root.children.length, 8); - }); - }); - - testWidgets( - 'auto convert url to link preview block', - (tester) async { - const url = 'https://appflowy.io'; - await tester.pasteContent(plainText: url, (editorState) async { - // the second one is the paragraph node - expect(editorState.document.root.children.length, 2); - final node = editorState.getNodeAtPath([0])!; - expect(node.type, LinkPreviewBlockKeys.type); - expect(node.attributes[LinkPreviewBlockKeys.url], url); - }); - - // hover on the link preview block - // click the more button - // and select convert to link - await tester.hoverOnWidget( - find.byType(CustomLinkPreviewWidget), - onHover: () async { - final convertToLinkButton = find.byWidgetPredicate((widget) { - return widget is MenuBlockButton && - widget.tooltip == - LocaleKeys.document_plugins_urlPreview_convertToLink.tr(); - }); - expect(convertToLinkButton, findsOneWidget); - await tester.tap(convertToLinkButton); + await editorState.apply(transaction); await tester.pumpAndSettle(); }, + (editorState) { + final node = editorState.getNodeAtPath([0]); + expect(node?.delta?.toPlainText(), 'bullet test'); + expect(node?.type, BulletedListBlockKeys.type); + }, ); + }); - await tester.pumpAndSettle(); - - final editorState = tester.editor.getCurrentEditorState(); - final textNode = editorState.getNodeAtPath([0])!; - expect(textNode.type, ParagraphBlockKeys.type); - expect(textNode.delta!.toJson(), [ - { - 'insert': url, - 'attributes': {'href': url}, - } - ]); - }, - ); - - testWidgets( - 'ctrl/cmd+z to undo the auto convert url to link preview block', - (tester) async { - const url = 'https://appflowy.io'; - await tester.pasteContent(plainText: url, (editorState) async { - // the second one is the paragraph node - expect(editorState.document.root.children.length, 2); + testWidgets('paste image(png) from memory', (tester) async { + final image = await rootBundle.load('assets/test/images/sample.png'); + final bytes = image.buffer.asUint8List(); + await tester.pasteContent(image: ('png', bytes), (editorState) { + expect(editorState.document.root.children.length, 1); final node = editorState.getNodeAtPath([0])!; - expect(node.type, LinkPreviewBlockKeys.type); - expect(node.attributes[LinkPreviewBlockKeys.url], url); + expect(node.type, ImageBlockKeys.type); + expect(node.attributes[ImageBlockKeys.url], isNotNull); }); - - await tester.editor.tapLineOfEditorAt(0); - await tester.simulateKeyEvent( - LogicalKeyboardKey.keyZ, - isControlPressed: - UniversalPlatform.isLinux || UniversalPlatform.isWindows, - isMetaPressed: UniversalPlatform.isMacOS, - ); - await tester.pumpAndSettle(); - - final editorState = tester.editor.getCurrentEditorState(); - final node = editorState.getNodeAtPath([0])!; - expect(node.type, ParagraphBlockKeys.type); - expect(node.delta!.toJson(), [ - { - 'insert': url, - 'attributes': {'href': url}, - } - ]); - }, - ); - - testWidgets( - 'paste the nodes start with non-delta node', - (tester) async { - await tester.pasteContent((_) {}); - const text = 'Hello World'; - final editorState = tester.editor.getCurrentEditorState(); - final transaction = editorState.transaction; - // [image_block] - // [paragraph_block] - transaction.insertNodes([ - 0, - ], [ - customImageNode(url: ''), - paragraphNode(text: text), - ]); - await editorState.apply(transaction); - await tester.pumpAndSettle(); - - await tester.editor.tapLineOfEditorAt(0); - // select all and copy - await tester.simulateKeyEvent( - LogicalKeyboardKey.keyA, - isControlPressed: - UniversalPlatform.isLinux || UniversalPlatform.isWindows, - isMetaPressed: UniversalPlatform.isMacOS, - ); - await tester.simulateKeyEvent( - LogicalKeyboardKey.keyC, - isControlPressed: - UniversalPlatform.isLinux || UniversalPlatform.isWindows, - isMetaPressed: UniversalPlatform.isMacOS, - ); - - // put the cursor to the end of the paragraph block - await tester.editor.tapLineOfEditorAt(0); - - // paste the content - await tester.simulateKeyEvent( - LogicalKeyboardKey.keyV, - isControlPressed: - UniversalPlatform.isLinux || UniversalPlatform.isWindows, - isMetaPressed: UniversalPlatform.isMacOS, - ); - await tester.pumpAndSettle(); - - // expect the image and the paragraph block are inserted below the cursor - expect(editorState.getNodeAtPath([0])!.type, CustomImageBlockKeys.type); - expect(editorState.getNodeAtPath([1])!.type, ParagraphBlockKeys.type); - expect(editorState.getNodeAtPath([2])!.type, CustomImageBlockKeys.type); - expect(editorState.getNodeAtPath([3])!.type, ParagraphBlockKeys.type); - }, - ); - - testWidgets('paste the url without protocol', (tester) async { - // paste the image that from local file - const plainText = '1.jpg'; - final image = await rootBundle.load('assets/test/images/sample.jpeg'); - final bytes = image.buffer.asUint8List(); - await tester.pasteContent(plainText: plainText, image: ('jpeg', bytes), - (editorState) { - final node = editorState.getNodeAtPath([0])!; - expect(node.type, ImageBlockKeys.type); - expect(node.attributes[ImageBlockKeys.url], isNotEmpty); }); - }); - testWidgets('paste the image url', (tester) async { - const plainText = 'https://appflowy.io/1.jpg'; - final image = await rootBundle.load('assets/test/images/sample.jpeg'); - final bytes = image.buffer.asUint8List(); - await tester.pasteContent(plainText: plainText, image: ('jpeg', bytes), - (editorState) { - final node = editorState.getNodeAtPath([0])!; - expect(node.type, ImageBlockKeys.type); - expect(node.attributes[ImageBlockKeys.url], isNotEmpty); + testWidgets('paste image(jpeg) from memory', (tester) async { + final image = await rootBundle.load('assets/test/images/sample.jpeg'); + final bytes = image.buffer.asUint8List(); + await tester.pasteContent(image: ('jpeg', bytes), (editorState) { + expect(editorState.document.root.children.length, 1); + final node = editorState.getNodeAtPath([0])!; + expect(node.type, ImageBlockKeys.type); + expect(node.attributes[ImageBlockKeys.url], isNotNull); + }); }); - }); - const testMarkdownText = ''' + testWidgets('paste image(gif) from memory', (tester) async { + final image = await rootBundle.load('assets/test/images/sample.gif'); + final bytes = image.buffer.asUint8List(); + await tester.pasteContent(image: ('gif', bytes), (editorState) { + expect(editorState.document.root.children.length, 1); + final node = editorState.getNodeAtPath([0])!; + expect(node.type, ImageBlockKeys.type); + expect(node.attributes[ImageBlockKeys.url], isNotNull); + }); + }); + + testWidgets( + 'format the selected text to href when pasting url if available', + (tester) async { + const text = 'appflowy'; + const url = 'https://appflowy.io'; + await tester.pasteContent( + plainText: url, + beforeTest: (editorState) async { + await tester.ime.insertText(text); + await tester.editor.updateSelection( + Selection.single( + path: [0], + startOffset: 0, + endOffset: text.length, + ), + ); + }, + (editorState) { + final node = editorState.getNodeAtPath([0])!; + expect(node.type, ParagraphBlockKeys.type); + expect(node.delta!.toJson(), [ + { + 'insert': text, + 'attributes': {'href': url}, + } + ]); + }, + ); + }, + ); + + // https://github.com/AppFlowy-IO/AppFlowy/issues/3263 + testWidgets( + 'paste the image from clipboard when html and image are both available', + (tester) async { + const html = + '''image'''; + final image = await rootBundle.load('assets/test/images/sample.png'); + final bytes = image.buffer.asUint8List(); + await tester.pasteContent( + html: html, + image: ('png', bytes), + (editorState) { + expect(editorState.document.root.children.length, 1); + final node = editorState.getNodeAtPath([0])!; + expect(node.type, ImageBlockKeys.type); + }, + ); + }, + ); + + testWidgets('paste the html content contains section', (tester) async { + const html = + '''
AppFlowy
Hello World
'''; + await tester.pasteContent(html: html, (editorState) { + expect(editorState.document.root.children.length, 2); + final node1 = editorState.getNodeAtPath([0])!; + final node2 = editorState.getNodeAtPath([1])!; + expect(node1.type, ParagraphBlockKeys.type); + expect(node2.type, ParagraphBlockKeys.type); + }); + }); + + testWidgets('paste the html from google translation', (tester) async { + const html = + '''
new force
Assessment focus: potential motivations, empathy

➢Personality characteristics and potential motivations:
-Reflection of self-worth
-Need to be respected
-Have a unique definition of success
-Be true to your own lifestyle
'''; + await tester.pasteContent(html: html, (editorState) { + expect(editorState.document.root.children.length, 8); + }); + }); + + testWidgets( + 'auto convert url to link preview block', + (tester) async { + const url = 'https://appflowy.io'; + await tester.pasteContent(plainText: url, (editorState) async { + final pasteAsMenu = find.byType(PasteAsMenu); + expect(pasteAsMenu, findsOneWidget); + final bookmarkButton = find.text( + LocaleKeys.document_plugins_linkPreview_typeSelection_bookmark.tr(), + ); + await tester.tapButton(bookmarkButton); + // the second one is the paragraph node + expect(editorState.document.root.children.length, 1); + final node = editorState.getNodeAtPath([0])!; + expect(node.type, LinkPreviewBlockKeys.type); + expect(node.attributes[LinkPreviewBlockKeys.url], url); + }); + + // hover on the link preview block + // click the more button + // and select convert to link + await tester.hoverOnWidget( + find.byType(CustomLinkPreviewWidget), + onHover: () async { + /// show menu + final menu = find.byType(CustomLinkPreviewMenu); + expect(menu, findsOneWidget); + await tester.tapButton(menu); + + final convertToLinkButton = find.text( + LocaleKeys.document_plugins_linkPreview_linkPreviewMenu_toUrl + .tr(), + ); + expect(convertToLinkButton, findsOneWidget); + await tester.tapButton(convertToLinkButton); + }, + ); + + final editorState = tester.editor.getCurrentEditorState(); + final textNode = editorState.getNodeAtPath([0])!; + expect(textNode.type, ParagraphBlockKeys.type); + expect(textNode.delta!.toJson(), [ + { + 'insert': url, + 'attributes': {'href': url}, + } + ]); + }, + ); + + testWidgets( + 'ctrl/cmd+z to undo the auto convert url to link preview block', + (tester) async { + const url = 'https://appflowy.io'; + await tester.pasteContent(plainText: url, (editorState) async { + final pasteAsMenu = find.byType(PasteAsMenu); + expect(pasteAsMenu, findsOneWidget); + final bookmarkButton = find.text( + LocaleKeys.document_plugins_linkPreview_typeSelection_bookmark.tr(), + ); + await tester.tapButton(bookmarkButton); + // the second one is the paragraph node + expect(editorState.document.root.children.length, 1); + final node = editorState.getNodeAtPath([0])!; + expect(node.type, LinkPreviewBlockKeys.type); + expect(node.attributes[LinkPreviewBlockKeys.url], url); + }); + + await tester.simulateKeyEvent( + LogicalKeyboardKey.keyZ, + isControlPressed: + UniversalPlatform.isLinux || UniversalPlatform.isWindows, + isMetaPressed: UniversalPlatform.isMacOS, + ); + await tester.pumpAndSettle(); + + final editorState = tester.editor.getCurrentEditorState(); + final node = editorState.getNodeAtPath([0])!; + expect(node.type, ParagraphBlockKeys.type); + expect(node.delta!.toJson(), [ + { + 'insert': url, + 'attributes': {'href': url}, + } + ]); + }, + ); + + testWidgets( + 'paste the nodes start with non-delta node', + (tester) async { + await tester.pasteContent((_) {}); + const text = 'Hello World'; + final editorState = tester.editor.getCurrentEditorState(); + final transaction = editorState.transaction; + // [image_block] + // [paragraph_block] + transaction.insertNodes([ + 0, + ], [ + customImageNode(url: ''), + paragraphNode(text: text), + ]); + await editorState.apply(transaction); + await tester.pumpAndSettle(); + + await tester.editor.tapLineOfEditorAt(0); + // select all and copy + await tester.simulateKeyEvent( + LogicalKeyboardKey.keyA, + isControlPressed: + UniversalPlatform.isLinux || UniversalPlatform.isWindows, + isMetaPressed: UniversalPlatform.isMacOS, + ); + await tester.simulateKeyEvent( + LogicalKeyboardKey.keyC, + isControlPressed: + UniversalPlatform.isLinux || UniversalPlatform.isWindows, + isMetaPressed: UniversalPlatform.isMacOS, + ); + + // put the cursor to the end of the paragraph block + await tester.editor.tapLineOfEditorAt(0); + + // paste the content + await tester.simulateKeyEvent( + LogicalKeyboardKey.keyV, + isControlPressed: + UniversalPlatform.isLinux || UniversalPlatform.isWindows, + isMetaPressed: UniversalPlatform.isMacOS, + ); + await tester.pumpAndSettle(); + + // expect the image and the paragraph block are inserted below the cursor + expect(editorState.getNodeAtPath([0])!.type, CustomImageBlockKeys.type); + expect(editorState.getNodeAtPath([1])!.type, ParagraphBlockKeys.type); + expect(editorState.getNodeAtPath([2])!.type, CustomImageBlockKeys.type); + expect(editorState.getNodeAtPath([3])!.type, ParagraphBlockKeys.type); + }, + ); + + testWidgets('paste the url without protocol', (tester) async { + // paste the image that from local file + const plainText = '1.jpg'; + final image = await rootBundle.load('assets/test/images/sample.jpeg'); + final bytes = image.buffer.asUint8List(); + await tester.pasteContent(plainText: plainText, image: ('jpeg', bytes), + (editorState) { + final node = editorState.getNodeAtPath([0])!; + expect(node.type, ImageBlockKeys.type); + expect(node.attributes[ImageBlockKeys.url], isNotEmpty); + }); + }); + + testWidgets('paste the image url', (tester) async { + const plainText = 'http://example.com/1.jpg'; + final image = await rootBundle.load('assets/test/images/sample.jpeg'); + final bytes = image.buffer.asUint8List(); + await tester.pasteContent(plainText: plainText, image: ('jpeg', bytes), + (editorState) { + final node = editorState.getNodeAtPath([0])!; + expect(node.type, ImageBlockKeys.type); + expect(node.attributes[ImageBlockKeys.url], isNotEmpty); + }); + }); + + const testMarkdownText = ''' # I'm h1 ## I'm h2 ### I'm h3 @@ -478,40 +491,41 @@ void main() { ##### I'm h5 ###### I'm h6'''; - testWidgets('paste markdowns', (tester) async { - await tester.pasteContent( - plainText: testMarkdownText, - (editorState) { - final children = editorState.document.root.children; - expect(children.length, 6); - for (int i = 1; i <= children.length; i++) { - final text = children[i - 1].delta!.toPlainText(); - expect(text, 'I\'m h$i'); - } - }, - ); - }); + testWidgets('paste markdowns', (tester) async { + await tester.pasteContent( + plainText: testMarkdownText, + (editorState) { + final children = editorState.document.root.children; + expect(children.length, 6); + for (int i = 1; i <= children.length; i++) { + final text = children[i - 1].delta!.toPlainText(); + expect(text, 'I\'m h$i'); + } + }, + ); + }); - testWidgets('paste markdowns as plain', (tester) async { - await tester.pasteContent( - plainText: testMarkdownText, - pasteAsPlain: true, - (editorState) { - final children = editorState.document.root.children; - expect(children.length, 6); - for (int i = 1; i <= children.length; i++) { - final text = children[i - 1].delta!.toPlainText(); - final expectText = '${'#' * i} I\'m h$i'; - expect(text, expectText); - } - }, - ); + testWidgets('paste markdowns as plain', (tester) async { + await tester.pasteContent( + plainText: testMarkdownText, + pasteAsPlain: true, + (editorState) { + final children = editorState.document.root.children; + expect(children.length, 6); + for (int i = 1; i <= children.length; i++) { + final text = children[i - 1].delta!.toPlainText(); + final expectText = '${'#' * i} I\'m h$i'; + expect(text, expectText); + } + }, + ); + }); }); } extension on WidgetTester { Future pasteContent( - void Function(EditorState editorState) test, { + FutureOr Function(EditorState editorState) test, { Future Function(EditorState editorState)? beforeTest, String? plainText, String? html, @@ -523,7 +537,7 @@ extension on WidgetTester { await tapAnonymousSignInButton(); // create a new document - await createNewPageWithNameUnderParent(name: 'Test Document'); + await createNewPageWithNameUnderParent(); // tap the editor await tapButton(find.byType(AppFlowyEditor)); @@ -546,8 +560,8 @@ extension on WidgetTester { isShiftPressed: pasteAsPlain, isMetaPressed: Platform.isMacOS, ); - await pumpAndSettle(); + await pumpAndSettle(const Duration(milliseconds: 1000)); - test(editor.getCurrentEditorState()); + await test(editor.getCurrentEditorState()); } } diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_create_and_delete_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_create_and_delete_test.dart index 43320509ce..c2e00a4b48 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/document/document_create_and_delete_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_create_and_delete_test.dart @@ -13,6 +13,8 @@ void main() { await tester.initializeAppFlowy(); await tester.tapAnonymousSignInButton(); + final finder = find.text(gettingStarted, findRichText: true); + await tester.pumpUntilFound(finder, timeout: const Duration(seconds: 2)); // create a new document const pageName = 'Test Document'; diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_link_preview_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_link_preview_test.dart new file mode 100644 index 0000000000..39f8bfd4f6 --- /dev/null +++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_link_preview_test.dart @@ -0,0 +1,453 @@ +import 'dart:io'; + +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_hover_menu.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/link_embed/link_embed_block_component.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/link_embed/link_embed_menu.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/custom_link_preview_block_component.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/link_preview_menu.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/paste_as/paste_as_menu.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_link_block.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_link_error_preview.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_link_preview.dart'; +import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../../shared/util.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + const avaliableLink = 'https://appflowy.io/', + unavailableLink = 'www.thereIsNoting.com'; + + Future preparePage(WidgetTester tester, {String? pageName}) async { + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + await tester.createNewPageWithNameUnderParent(name: pageName); + await tester.editor.tapLineOfEditorAt(0); + } + + Future pasteLink(WidgetTester tester, String link) async { + await getIt() + .setData(ClipboardServiceData(plainText: link)); + + /// paste the link + await tester.simulateKeyEvent( + LogicalKeyboardKey.keyV, + isControlPressed: Platform.isLinux || Platform.isWindows, + isMetaPressed: Platform.isMacOS, + ); + await tester.pumpAndSettle(Duration(seconds: 1)); + } + + Future pasteAs( + WidgetTester tester, + String link, + PasteMenuType type, { + Duration waitTime = const Duration(milliseconds: 500), + }) async { + await pasteLink(tester, link); + final convertToMentionButton = find.text(type.title); + await tester.tapButton(convertToMentionButton); + await tester.pumpAndSettle(waitTime); + } + + void checkUrl(Node node, String link) { + expect(node.type, ParagraphBlockKeys.type); + expect(node.delta!.toJson(), [ + { + 'insert': link, + 'attributes': {'href': link}, + } + ]); + } + + void checkMention(Node node, String link) { + final delta = node.delta!; + final insert = (delta.first as TextInsert).text; + final attributes = delta.first.attributes; + expect(insert, MentionBlockKeys.mentionChar); + final mention = + attributes?[MentionBlockKeys.mention] as Map; + expect(mention[MentionBlockKeys.type], MentionType.externalLink.name); + expect(mention[MentionBlockKeys.url], avaliableLink); + } + + void checkBookmark(Node node, String link) { + expect(node.type, LinkPreviewBlockKeys.type); + expect(node.attributes[LinkPreviewBlockKeys.url], link); + } + + void checkEmbed(Node node, String link) { + expect(node.type, LinkPreviewBlockKeys.type); + expect(node.attributes[LinkEmbedKeys.previewType], LinkEmbedKeys.embed); + expect(node.attributes[LinkPreviewBlockKeys.url], link); + } + + group('Paste as URL', () { + Future pasteAndTurnInto( + WidgetTester tester, + String link, + String title, + ) async { + await pasteLink(tester, link); + final convertToLinkButton = find + .text(LocaleKeys.document_plugins_linkPreview_typeSelection_URL.tr()); + await tester.tapButton(convertToLinkButton); + + /// hover link and turn into mention + await tester.hoverOnWidget( + find.byType(LinkHoverTrigger), + onHover: () async { + final turnintoButton = find.byFlowySvg(FlowySvgs.turninto_m); + await tester.tapButton(turnintoButton); + final convertToButton = find.text(title); + await tester.tapButton(convertToButton); + await tester.pumpAndSettle(Duration(seconds: 1)); + }, + ); + } + + testWidgets('paste a link', (tester) async { + final link = avaliableLink; + await preparePage(tester); + await pasteLink(tester, link); + final convertToLinkButton = find + .text(LocaleKeys.document_plugins_linkPreview_typeSelection_URL.tr()); + await tester.tapButton(convertToLinkButton); + final node = tester.editor.getNodeAtPath([0]); + checkUrl(node, link); + }); + + testWidgets('paste a link and turn into mention', (tester) async { + final link = avaliableLink; + await preparePage(tester); + await pasteAndTurnInto( + tester, + link, + LinkConvertMenuCommand.toMention.title, + ); + + /// check metion values + final node = tester.editor.getNodeAtPath([0]); + checkMention(node, link); + }); + + testWidgets('paste a link and turn into bookmark', (tester) async { + final link = avaliableLink; + await preparePage(tester); + await pasteAndTurnInto( + tester, + link, + LinkConvertMenuCommand.toBookmark.title, + ); + + /// check metion values + final node = tester.editor.getNodeAtPath([0]); + checkBookmark(node, link); + }); + + testWidgets('paste a link and turn into embed', (tester) async { + final link = avaliableLink; + await preparePage(tester); + await pasteAndTurnInto( + tester, + link, + LinkConvertMenuCommand.toEmbed.title, + ); + + /// check metion values + final node = tester.editor.getNodeAtPath([0]); + checkEmbed(node, link); + }); + }); + + group('Paste as Mention', () { + Future pasteAsMention(WidgetTester tester, String link) => + pasteAs(tester, link, PasteMenuType.mention); + + String getMentionLink(Node node) { + final insert = node.delta?.first as TextInsert?; + final mention = insert?.attributes?[MentionBlockKeys.mention] + as Map?; + return mention?[MentionBlockKeys.url] ?? ''; + } + + Future hoverMentionAndClick( + WidgetTester tester, + String command, + ) async { + final mentionLink = find.byType(MentionLinkBlock); + expect(mentionLink, findsOneWidget); + await tester.hoverOnWidget( + mentionLink, + onHover: () async { + final errorPreview = find.byType(MentionLinkErrorPreview); + expect(errorPreview, findsOneWidget); + final convertButton = find.byFlowySvg(FlowySvgs.turninto_m); + await tester.tapButton(convertButton); + final menuButton = find.text(command); + await tester.tapButton(menuButton); + }, + ); + } + + testWidgets('paste a link as mention', (tester) async { + final link = avaliableLink; + await preparePage(tester); + await pasteAsMention(tester, link); + final node = tester.editor.getNodeAtPath([0]); + checkMention(node, link); + }); + + testWidgets('paste as mention and copy link', (tester) async { + final link = avaliableLink; + await preparePage(tester); + await pasteAsMention(tester, link); + final mentionLink = find.byType(MentionLinkBlock); + expect(mentionLink, findsOneWidget); + await tester.hoverOnWidget( + mentionLink, + onHover: () async { + final preview = find.byType(MentionLinkPreview); + if (!preview.hasFound) { + final copyButton = find.byFlowySvg(FlowySvgs.toolbar_link_m); + await tester.tapButton(copyButton); + } else { + final moreOptionButton = find.byFlowySvg(FlowySvgs.toolbar_more_m); + await tester.tapButton(moreOptionButton); + final copyButton = + find.text(MentionLinktMenuCommand.copyLink.title); + await tester.tapButton(copyButton); + } + }, + ); + final clipboardContent = await getIt().getData(); + expect(clipboardContent.plainText, link); + }); + + testWidgets('paste as error mention and turninto url', (tester) async { + String link = unavailableLink; + await preparePage(tester); + await pasteAsMention(tester, link); + Node node = tester.editor.getNodeAtPath([0]); + link = getMentionLink(node); + await hoverMentionAndClick( + tester, + MentionLinktErrorMenuCommand.toURL.title, + ); + node = tester.editor.getNodeAtPath([0]); + checkUrl(node, link); + }); + + testWidgets('paste as error mention and turninto embed', (tester) async { + String link = unavailableLink; + await preparePage(tester); + await pasteAsMention(tester, link); + Node node = tester.editor.getNodeAtPath([0]); + link = getMentionLink(node); + await hoverMentionAndClick( + tester, + MentionLinktErrorMenuCommand.toEmbed.title, + ); + node = tester.editor.getNodeAtPath([0]); + checkEmbed(node, link); + }); + + testWidgets('paste as error mention and turninto bookmark', (tester) async { + String link = unavailableLink; + await preparePage(tester); + await pasteAsMention(tester, link); + Node node = tester.editor.getNodeAtPath([0]); + link = getMentionLink(node); + await hoverMentionAndClick( + tester, + MentionLinktErrorMenuCommand.toBookmark.title, + ); + node = tester.editor.getNodeAtPath([0]); + checkBookmark(node, link); + }); + + testWidgets('paste as error mention and remove link', (tester) async { + String link = unavailableLink; + await preparePage(tester); + await pasteAsMention(tester, link); + Node node = tester.editor.getNodeAtPath([0]); + link = getMentionLink(node); + await hoverMentionAndClick( + tester, + MentionLinktErrorMenuCommand.removeLink.title, + ); + node = tester.editor.getNodeAtPath([0]); + expect(node.type, ParagraphBlockKeys.type); + expect(node.delta!.toJson(), [ + {'insert': link}, + ]); + }); + }); + + group('Paste as Bookmark', () { + Future pasteAsBookmark(WidgetTester tester, String link) => + pasteAs(tester, link, PasteMenuType.bookmark); + + Future hoverAndClick( + WidgetTester tester, + LinkPreviewMenuCommand command, + ) async { + final bookmark = find.byType(CustomLinkPreviewBlockComponent); + expect(bookmark, findsOneWidget); + await tester.hoverOnWidget( + bookmark, + onHover: () async { + final menuButton = find.byFlowySvg(FlowySvgs.toolbar_more_m); + await tester.tapButton(menuButton); + final commandButton = find.text(command.title); + await tester.tapButton(commandButton); + }, + ); + } + + testWidgets('paste a link as bookmark', (tester) async { + final link = avaliableLink; + await preparePage(tester); + await pasteAsBookmark(tester, link); + final node = tester.editor.getNodeAtPath([0]); + checkBookmark(node, link); + }); + + testWidgets('paste a link as bookmark and convert to mention', + (tester) async { + final link = avaliableLink; + await preparePage(tester); + await pasteAsBookmark(tester, link); + await hoverAndClick(tester, LinkPreviewMenuCommand.convertToMention); + final node = tester.editor.getNodeAtPath([0]); + checkMention(node, link); + }); + + testWidgets('paste a link as bookmark and convert to url', (tester) async { + final link = avaliableLink; + await preparePage(tester); + await pasteAsBookmark(tester, link); + await hoverAndClick(tester, LinkPreviewMenuCommand.convertToUrl); + final node = tester.editor.getNodeAtPath([0]); + checkUrl(node, link); + }); + + testWidgets('paste a link as bookmark and convert to embed', + (tester) async { + final link = avaliableLink; + await preparePage(tester); + await pasteAsBookmark(tester, link); + await hoverAndClick(tester, LinkPreviewMenuCommand.convertToEmbed); + final node = tester.editor.getNodeAtPath([0]); + checkEmbed(node, link); + }); + + testWidgets('paste a link as bookmark and copy link', (tester) async { + final link = avaliableLink; + await preparePage(tester); + await pasteAsBookmark(tester, link); + await hoverAndClick(tester, LinkPreviewMenuCommand.copyLink); + final clipboardContent = await getIt().getData(); + expect(clipboardContent.plainText, link); + }); + + testWidgets('paste a link as bookmark and replace link', (tester) async { + final link = avaliableLink; + await preparePage(tester); + await pasteAsBookmark(tester, link); + await hoverAndClick(tester, LinkPreviewMenuCommand.replace); + await tester.simulateKeyEvent( + LogicalKeyboardKey.keyA, + isControlPressed: Platform.isLinux || Platform.isWindows, + isMetaPressed: Platform.isMacOS, + ); + await tester.simulateKeyEvent(LogicalKeyboardKey.delete); + await tester.enterText(find.byType(TextFormField), unavailableLink); + await tester.tapButton(find.text(LocaleKeys.button_replace.tr())); + final node = tester.editor.getNodeAtPath([0]); + checkBookmark(node, unavailableLink); + }); + + testWidgets('paste a link as bookmark and remove link', (tester) async { + final link = avaliableLink; + await preparePage(tester); + await pasteAsBookmark(tester, link); + await hoverAndClick(tester, LinkPreviewMenuCommand.removeLink); + final node = tester.editor.getNodeAtPath([0]); + expect(node.type, ParagraphBlockKeys.type); + expect(node.delta!.toJson(), [ + {'insert': link}, + ]); + }); + }); + group('Paste as Embed', () { + Future pasteAsEmbed(WidgetTester tester, String link) => + pasteAs(tester, link, PasteMenuType.embed); + + Future hoverAndConvert( + WidgetTester tester, + LinkEmbedConvertCommand command, + ) async { + final embed = find.byType(LinkEmbedBlockComponent); + expect(embed, findsOneWidget); + await tester.hoverOnWidget( + embed, + onHover: () async { + final menuButton = find.byFlowySvg(FlowySvgs.turninto_m); + await tester.tapButton(menuButton); + final commandButton = find.text(command.title); + await tester.tapButton(commandButton); + }, + ); + } + + testWidgets('paste a link as embed', (tester) async { + final link = avaliableLink; + await preparePage(tester); + await pasteAsEmbed(tester, link); + final node = tester.editor.getNodeAtPath([0]); + checkEmbed(node, link); + }); + + testWidgets('paste a link as bookmark and convert to mention', + (tester) async { + final link = avaliableLink; + await preparePage(tester); + await pasteAsEmbed(tester, link); + await hoverAndConvert(tester, LinkEmbedConvertCommand.toMention); + final node = tester.editor.getNodeAtPath([0]); + checkMention(node, link); + }); + + testWidgets('paste a link as bookmark and convert to url', (tester) async { + final link = avaliableLink; + await preparePage(tester); + await pasteAsEmbed(tester, link); + await hoverAndConvert(tester, LinkEmbedConvertCommand.toURL); + final node = tester.editor.getNodeAtPath([0]); + checkUrl(node, link); + }); + + testWidgets('paste a link as bookmark and convert to bookmark', + (tester) async { + final link = avaliableLink; + await preparePage(tester); + await pasteAsEmbed(tester, link); + await hoverAndConvert(tester, LinkEmbedConvertCommand.toBookmark); + final node = tester.editor.getNodeAtPath([0]); + checkBookmark(node, link); + }); + }); +} diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_more_actions_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_more_actions_test.dart index d4cc11d7f0..eeb2ea3925 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/document/document_more_actions_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_more_actions_test.dart @@ -1,4 +1,10 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart'; +import 'package:appflowy/workspace/presentation/widgets/more_view_actions/widgets/view_meta_info.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -31,4 +37,104 @@ void main() { expect(pageFinder, findsNWidgets(1)); }); }); + + testWidgets('count title towards word count', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + await tester.createNewPageWithNameUnderParent(); + + Finder title = tester.editor.findDocumentTitle(''); + + await tester.openMoreViewActions(); + final viewMetaInfo = find.byType(ViewMetaInfo); + expect(viewMetaInfo, findsOneWidget); + + ViewMetaInfo viewMetaInfoWidget = + viewMetaInfo.evaluate().first.widget as ViewMetaInfo; + Counters titleCounter = viewMetaInfoWidget.titleCounters!; + + expect(titleCounter.charCount, 0); + expect(titleCounter.wordCount, 0); + + /// input [str1] within title + const str1 = 'Hello', + str2 = '$str1 AppFlowy', + str3 = '$str2!', + str4 = 'Hello world'; + await tester.simulateKeyEvent(LogicalKeyboardKey.escape); + await tester.tapButton(title); + await tester.enterText(title, str1); + await tester.pumpAndSettle(const Duration(seconds: 1)); + await tester.openMoreViewActions(); + viewMetaInfoWidget = viewMetaInfo.evaluate().first.widget as ViewMetaInfo; + titleCounter = viewMetaInfoWidget.titleCounters!; + expect(titleCounter.charCount, str1.length); + expect(titleCounter.wordCount, 1); + + /// input [str2] within title + title = tester.editor.findDocumentTitle(str1); + await tester.simulateKeyEvent(LogicalKeyboardKey.escape); + await tester.tapButton(title); + await tester.enterText(title, str2); + await tester.pumpAndSettle(const Duration(seconds: 1)); + await tester.openMoreViewActions(); + viewMetaInfoWidget = viewMetaInfo.evaluate().first.widget as ViewMetaInfo; + titleCounter = viewMetaInfoWidget.titleCounters!; + expect(titleCounter.charCount, str2.length); + expect(titleCounter.wordCount, 2); + + /// input [str3] within title + title = tester.editor.findDocumentTitle(str2); + await tester.simulateKeyEvent(LogicalKeyboardKey.escape); + await tester.tapButton(title); + await tester.enterText(title, str3); + await tester.pumpAndSettle(const Duration(seconds: 1)); + await tester.openMoreViewActions(); + viewMetaInfoWidget = viewMetaInfo.evaluate().first.widget as ViewMetaInfo; + titleCounter = viewMetaInfoWidget.titleCounters!; + expect(titleCounter.charCount, str3.length); + expect(titleCounter.wordCount, 2); + + /// input [str4] within document + await tester.simulateKeyEvent(LogicalKeyboardKey.escape); + await tester.editor + .updateSelection(Selection.collapsed(Position(path: [0]))); + await tester.pumpAndSettle(); + await tester.editor + .getCurrentEditorState() + .insertTextAtCurrentSelection(str4); + await tester.pumpAndSettle(const Duration(seconds: 1)); + await tester.openMoreViewActions(); + final texts = + find.descendant(of: viewMetaInfo, matching: find.byType(FlowyText)); + expect(texts, findsNWidgets(3)); + viewMetaInfoWidget = viewMetaInfo.evaluate().first.widget as ViewMetaInfo; + titleCounter = viewMetaInfoWidget.titleCounters!; + final Counters documentCounters = viewMetaInfoWidget.documentCounters!; + final wordCounter = texts.evaluate().elementAt(0).widget as FlowyText, + charCounter = texts.evaluate().elementAt(1).widget as FlowyText; + final numberFormat = NumberFormat(); + expect( + wordCounter.text, + LocaleKeys.moreAction_wordCount.tr( + args: [ + numberFormat + .format(titleCounter.wordCount + documentCounters.wordCount) + .toString(), + ], + ), + ); + expect( + charCounter.text, + LocaleKeys.moreAction_charCount.tr( + args: [ + numberFormat + .format( + titleCounter.charCount + documentCounters.charCount, + ) + .toString(), + ], + ), + ); + }); } diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_option_action_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_option_action_test.dart index cbc634cf02..6ec12287a8 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/document/document_option_action_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_option_action_test.dart @@ -76,13 +76,12 @@ void main() { LocaleKeys.document_slashMenu_name_heading1.tr(): HeadingBlockKeys.type, LocaleKeys.document_slashMenu_name_heading2.tr(): HeadingBlockKeys.type, LocaleKeys.document_slashMenu_name_heading3.tr(): HeadingBlockKeys.type, - LocaleKeys.document_slashMenu_name_bulletedList.tr(): + LocaleKeys.editor_bulletedListShortForm.tr(): BulletedListBlockKeys.type, - LocaleKeys.document_slashMenu_name_numberedList.tr(): + LocaleKeys.editor_numberedListShortForm.tr(): NumberedListBlockKeys.type, LocaleKeys.document_slashMenu_name_quote.tr(): QuoteBlockKeys.type, - LocaleKeys.document_slashMenu_name_todoList.tr(): - TodoListBlockKeys.type, + LocaleKeys.editor_checkbox.tr(): TodoListBlockKeys.type, LocaleKeys.document_slashMenu_name_callout.tr(): CalloutBlockKeys.type, LocaleKeys.document_slashMenu_name_text.tr(): ParagraphBlockKeys.type, }; @@ -117,13 +116,12 @@ void main() { LocaleKeys.document_slashMenu_name_heading1.tr(): HeadingBlockKeys.type, LocaleKeys.document_slashMenu_name_heading2.tr(): HeadingBlockKeys.type, LocaleKeys.document_slashMenu_name_heading3.tr(): HeadingBlockKeys.type, - LocaleKeys.document_slashMenu_name_bulletedList.tr(): + LocaleKeys.editor_bulletedListShortForm.tr(): BulletedListBlockKeys.type, - LocaleKeys.document_slashMenu_name_numberedList.tr(): + LocaleKeys.editor_numberedListShortForm.tr(): NumberedListBlockKeys.type, LocaleKeys.document_slashMenu_name_quote.tr(): QuoteBlockKeys.type, - LocaleKeys.document_slashMenu_name_todoList.tr(): - TodoListBlockKeys.type, + LocaleKeys.editor_checkbox.tr(): TodoListBlockKeys.type, LocaleKeys.document_slashMenu_name_callout.tr(): CalloutBlockKeys.type, LocaleKeys.document_slashMenu_name_text.tr(): ParagraphBlockKeys.type, }; diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_selection_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_selection_test.dart index bd0fd18c50..de1cb880a5 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/document/document_selection_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_selection_test.dart @@ -1,5 +1,6 @@ import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -47,5 +48,41 @@ void main() { expect(editorState.selection!.start.offset, 0); }); + + testWidgets('select and delete text', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + + /// create a new document + await tester.createNewPageWithNameUnderParent(); + + /// input text + final editor = tester.editor; + final editorState = editor.getCurrentEditorState(); + + const inputText = 'Test for text selection and deletion'; + final texts = inputText.split(' '); + await editor.tapLineOfEditorAt(0); + await tester.ime.insertText(inputText); + + /// selecte and delete + int index = 0; + while (texts.isNotEmpty) { + final text = texts.removeAt(0); + await tester.editor.updateSelection( + Selection( + start: Position(path: [0], offset: index), + end: Position(path: [0], offset: index + text.length), + ), + ); + await tester.simulateKeyEvent(LogicalKeyboardKey.delete); + index++; + } + + /// excpete the text value is correct + final node = editorState.getNodeAtPath([0])!; + final nodeText = node.delta?.toPlainText() ?? ''; + expect(nodeText, ' ' * (index - 1)); + }); }); } diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_sub_page_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_sub_page_test.dart index e6bdf9e6b6..50f0f903bc 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/document/document_sub_page_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_sub_page_test.dart @@ -1,7 +1,9 @@ import 'dart:io'; import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/sub_page/sub_page_block_component.dart'; +import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart'; import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; @@ -11,6 +13,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; +import '../../shared/emoji.dart'; import '../../shared/util.dart'; // Test cases for the Document SubPageBlock that needs to be covered: @@ -37,7 +40,14 @@ import '../../shared/util.dart'; const _defaultPageName = ""; void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + setUpAll(() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + RecentIcons.enable = false; + }); + + tearDownAll(() { + RecentIcons.enable = true; + }); group('Document SubPageBlock tests', () { testWidgets('Insert a new SubPageBlock from Slash menu items', @@ -48,11 +58,6 @@ void main() { await tester.insertSubPageFromSlashMenu(); - await tester.expandOrCollapsePage( - pageName: 'SubPageBlock', - layout: ViewLayoutPB.Document, - ); - expect( find.text(LocaleKeys.menuAppHeader_defaultNewPageName.tr()), findsNWidgets(3), @@ -67,12 +72,6 @@ void main() { await tester.insertSubPageFromSlashMenu(); - await tester.expandOrCollapsePage( - pageName: 'SubPageBlock', - layout: ViewLayoutPB.Document, - ); - await tester.pumpAndSettle(); - await tester.renamePageWithSecondary(_defaultPageName, 'Child page'); expect(find.text('Child page'), findsNWidgets(2)); @@ -91,11 +90,6 @@ void main() { await tester.insertSubPageFromSlashMenu(); - await tester.expandOrCollapsePage( - pageName: 'SubPageBlock', - layout: ViewLayoutPB.Document, - ); - await tester.renamePageWithSecondary(_defaultPageName, 'Child page'); expect(find.text('Child page'), findsNWidgets(2)); @@ -144,11 +138,6 @@ void main() { await tester.insertSubPageFromSlashMenu(); - await tester.expandOrCollapsePage( - pageName: 'SubPageBlock', - layout: ViewLayoutPB.Document, - ); - await tester.renamePageWithSecondary(_defaultPageName, 'Child page'); expect(find.text('Child page'), findsNWidgets(2)); @@ -202,11 +191,6 @@ void main() { await tester.insertSubPageFromSlashMenu(); - await tester.expandOrCollapsePage( - pageName: 'SubPageBlock', - layout: ViewLayoutPB.Document, - ); - await tester.renamePageWithSecondary(_defaultPageName, 'Child page'); expect(find.text('Child page'), findsNWidgets(2)); @@ -243,11 +227,6 @@ void main() { await tester.insertSubPageFromSlashMenu(); - await tester.expandOrCollapsePage( - pageName: 'SubPageBlock', - layout: ViewLayoutPB.Document, - ); - await tester.renamePageWithSecondary(_defaultPageName, 'Child page'); expect(find.text('Child page'), findsNWidgets(2)); @@ -293,11 +272,6 @@ void main() { await tester.insertSubPageFromSlashMenu(); - await tester.expandOrCollapsePage( - pageName: 'SubPageBlock', - layout: ViewLayoutPB.Document, - ); - await tester.renamePageWithSecondary(_defaultPageName, 'Child page'); expect(find.text('Child page'), findsNWidgets(2)); @@ -336,11 +310,6 @@ void main() { await tester.insertSubPageFromSlashMenu(true); - await tester.expandOrCollapsePage( - pageName: 'SubPageBlock', - layout: ViewLayoutPB.Document, - ); - await tester.renamePageWithSecondary(_defaultPageName, 'Child page'); expect(find.text('Child page'), findsNWidgets(2)); @@ -384,11 +353,6 @@ void main() { await tester.insertSubPageFromSlashMenu(); - await tester.expandOrCollapsePage( - pageName: 'SubPageBlock', - layout: ViewLayoutPB.Document, - ); - await tester.renamePageWithSecondary(_defaultPageName, 'Child page'); expect(find.text('Child page'), findsNWidgets(2)); expect(find.byType(SubPageBlockComponent), findsOneWidget); @@ -411,12 +375,6 @@ void main() { await tester.createNewPageWithNameUnderParent(name: 'SubPageBlock'); await tester.insertSubPageFromSlashMenu(); - - await tester.expandOrCollapsePage( - pageName: 'SubPageBlock', - layout: ViewLayoutPB.Document, - ); - await tester.renamePageWithSecondary(_defaultPageName, 'Child page'); expect(find.text('Child page'), findsNWidgets(2)); @@ -437,11 +395,6 @@ void main() { await tester.insertSubPageFromSlashMenu(true); - await tester.expandOrCollapsePage( - pageName: 'SubPageBlock', - layout: ViewLayoutPB.Document, - ); - expect(find.byType(SubPageBlockComponent), findsOneWidget); final beforeNode = tester.editor.getNodeAtPath([1]); @@ -498,6 +451,43 @@ void main() { expect(find.text('Parent'), findsNWidgets(2)); }); + + testWidgets('Displaying icon of subpage', (tester) async { + const firstPage = 'FirstPage'; + + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + await tester.createNewPageWithNameUnderParent(name: firstPage); + final icon = await tester.loadIcon(); + + /// create subpage + await tester.editor.tapLineOfEditorAt(0); + await tester.editor.showSlashMenu(); + await tester.editor.tapSlashMenuItemWithName( + LocaleKeys.document_slashMenu_subPage_name.tr(), + offset: 100, + ); + + /// add icon + await tester.editor.hoverOnCoverToolbar(); + await tester.editor.tapAddIconButton(); + await tester.tapIcon(icon); + await tester.pumpAndSettle(); + await tester.openPage(firstPage); + + await tester.expandOrCollapsePage( + pageName: firstPage, + layout: ViewLayoutPB.Document, + ); + + /// check if there is a icon in document + final iconWidget = find.byWidgetPredicate((w) { + if (w is! RawEmojiIconWidget) return false; + final iconData = w.emoji.emoji; + return iconData == icon.emoji; + }); + expect(iconWidget, findsOneWidget); + }); }); } diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_test_runner_4.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_test_runner_4.dart index a05545753e..bc0671834b 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/document/document_test_runner_4.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_test_runner_4.dart @@ -13,6 +13,7 @@ import 'document_with_multi_image_block_test.dart' as document_with_multi_image_block_test; import 'document_with_simple_table_test.dart' as document_with_simple_table_test; +import 'document_link_preview_test.dart' as document_link_preview_test; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -28,4 +29,5 @@ void main() { document_find_menu_test.main(); document_toolbar_test.main(); document_with_simple_table_test.main(); + document_link_preview_test.main(); } diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_toolbar_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_toolbar_test.dart index c3a086626f..f455cd479d 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/document/document_toolbar_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_toolbar_test.dart @@ -1,5 +1,19 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/desktop_floating_toolbar.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_create_menu.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_edit_menu.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_hover_menu.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/toolbar_item/custom_link_toolbar_item.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/toolbar_item/more_option_toolbar_item.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/toolbar_item/text_suggestions_toolbar_item.dart'; +import 'package:appflowy/startup/startup.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -8,24 +22,33 @@ import '../../shared/util.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + Future selectText(WidgetTester tester, String text) async { + await tester.editor.updateSelection( + Selection.single( + path: [0], + startOffset: 0, + endOffset: text.length, + ), + ); + } + + Future prepareForToolbar(WidgetTester tester, String text) async { + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + + await tester.createNewPageWithNameUnderParent(); + + await tester.editor.tapLineOfEditorAt(0); + await tester.ime.insertText(text); + await selectText(tester, text); + } + group('document toolbar:', () { testWidgets('font family', (tester) async { - await tester.initializeAppFlowy(); - await tester.tapAnonymousSignInButton(); - - await tester.createNewPageWithNameUnderParent(); - - await tester.editor.tapLineOfEditorAt(0); - const text = 'font family'; - await tester.ime.insertText(text); - await tester.editor.updateSelection( - Selection.single( - path: [0], - startOffset: 0, - endOffset: text.length, - ), - ); + await prepareForToolbar(tester, 'font family'); + // tap more options button + await tester.tapButtonWithFlowySvgData(FlowySvgs.toolbar_more_m); // tap the font family button final fontFamilyButton = find.byKey(kFontFamilyToolbarItemKey); await tester.tapButton(fontFamilyButton); @@ -46,5 +69,302 @@ void main() { abel, ); }); + + testWidgets('heading 1~3', (tester) async { + const text = 'heading'; + await prepareForToolbar(tester, text); + + Future testChangeHeading( + FlowySvgData svg, + String title, + int level, + ) async { + /// tap suggestions item + final suggestionsButton = find.byKey(kSuggestionsItemKey); + await tester.tapButton(suggestionsButton); + + /// tap item + await tester.ensureVisible(find.byFlowySvg(svg)); + await tester.tapButton(find.byFlowySvg(svg)); + + /// check the type of node is [HeadingBlockKeys.type] + await selectText(tester, text); + final editorState = tester.editor.getCurrentEditorState(); + final selection = editorState.selection!; + final node = editorState.getNodeAtPath(selection.start.path)!, + nodeLevel = node.attributes[HeadingBlockKeys.level]!; + expect(node.type, HeadingBlockKeys.type); + expect(nodeLevel, level); + + /// show toolbar again + await selectText(tester, text); + + /// the text of suggestions item should be changed + expect( + find.descendant(of: suggestionsButton, matching: find.text(title)), + findsOneWidget, + ); + } + + await testChangeHeading( + FlowySvgs.type_h1_m, + LocaleKeys.document_toolbar_h1.tr(), + 1, + ); + + await testChangeHeading( + FlowySvgs.type_h2_m, + LocaleKeys.document_toolbar_h2.tr(), + 2, + ); + await testChangeHeading( + FlowySvgs.type_h3_m, + LocaleKeys.document_toolbar_h3.tr(), + 3, + ); + }); + + testWidgets('toggle 1~3', (tester) async { + const text = 'toggle'; + await prepareForToolbar(tester, text); + + Future testChangeToggle( + FlowySvgData svg, + String title, + int? level, + ) async { + /// tap suggestions item + final suggestionsButton = find.byKey(kSuggestionsItemKey); + await tester.tapButton(suggestionsButton); + + /// tap item + await tester.ensureVisible(find.byFlowySvg(svg)); + await tester.tapButton(find.byFlowySvg(svg)); + + /// check the type of node is [HeadingBlockKeys.type] + await selectText(tester, text); + final editorState = tester.editor.getCurrentEditorState(); + final selection = editorState.selection!; + final node = editorState.getNodeAtPath(selection.start.path)!, + nodeLevel = node.attributes[ToggleListBlockKeys.level]; + expect(node.type, ToggleListBlockKeys.type); + expect(nodeLevel, level); + + /// show toolbar again + await selectText(tester, text); + + /// the text of suggestions item should be changed + expect( + find.descendant(of: suggestionsButton, matching: find.text(title)), + findsOneWidget, + ); + } + + await testChangeToggle( + FlowySvgs.type_toggle_list_m, + LocaleKeys.editor_toggleListShortForm.tr(), + null, + ); + + await testChangeToggle( + FlowySvgs.type_toggle_h1_m, + LocaleKeys.editor_toggleHeading1ShortForm.tr(), + 1, + ); + + await testChangeToggle( + FlowySvgs.type_toggle_h2_m, + LocaleKeys.editor_toggleHeading2ShortForm.tr(), + 2, + ); + + await testChangeToggle( + FlowySvgs.type_toggle_h3_m, + LocaleKeys.editor_toggleHeading3ShortForm.tr(), + 3, + ); + }); + + testWidgets('toolbar will not rebuild after click item', (tester) async { + const text = 'Test rebuilding'; + await prepareForToolbar(tester, text); + Finder toolbar = find.byType(DesktopFloatingToolbar); + Element toolbarElement = toolbar.evaluate().first; + final elementHashcode = toolbarElement.hashCode; + final boldButton = find.byFlowySvg(FlowySvgs.toolbar_bold_m), + underlineButton = find.byFlowySvg(FlowySvgs.toolbar_underline_m), + italicButton = find.byFlowySvg(FlowySvgs.toolbar_inline_italic_m); + + /// tap format buttons + await tester.tapButton(boldButton); + await tester.tapButton(underlineButton); + await tester.tapButton(italicButton); + toolbar = find.byType(DesktopFloatingToolbar); + toolbarElement = toolbar.evaluate().first; + + /// check if the toolbar is not rebuilt + expect(elementHashcode, toolbarElement.hashCode); + final editorState = tester.editor.getCurrentEditorState(); + + /// check text formats + expect( + editorState + .getDeltaAttributeValueInSelection(AppFlowyRichTextKeys.bold), + true, + ); + expect( + editorState + .getDeltaAttributeValueInSelection(AppFlowyRichTextKeys.italic), + true, + ); + expect( + editorState + .getDeltaAttributeValueInSelection(AppFlowyRichTextKeys.underline), + true, + ); + }); + }); + + group('document toolbar: link', () { + String? getLinkFromNode(Node node) { + for (final insert in node.delta!) { + final link = insert.attributes?.href; + if (link != null) return link; + } + return null; + } + + bool isPageLink(Node node) { + for (final insert in node.delta!) { + final isPage = insert.attributes?.isPage; + if (isPage == true) return true; + } + return false; + } + + String getNodeText(Node node) { + for (final insert in node.delta!) { + if (insert is TextInsert) return insert.text; + } + return ''; + } + + testWidgets('insert link and remove link', (tester) async { + const text = 'insert link', link = 'https://test.appflowy.cloud'; + await prepareForToolbar(tester, text); + + final toolbar = find.byType(DesktopFloatingToolbar); + expect(toolbar, findsOneWidget); + + /// tap link button to show CreateLinkMenu + final linkButton = find.byFlowySvg(FlowySvgs.toolbar_link_m); + await tester.tapButton(linkButton); + final createLinkMenu = find.byType(LinkCreateMenu); + expect(createLinkMenu, findsOneWidget); + + /// test esc to close + await tester.simulateKeyEvent(LogicalKeyboardKey.escape); + expect(toolbar, findsNothing); + + /// show toolbar again + await tester.editor.tapLineOfEditorAt(0); + await selectText(tester, text); + await tester.tapButton(linkButton); + + /// insert link + final textField = find.descendant( + of: createLinkMenu, + matching: find.byType(TextFormField), + ); + await tester.enterText(textField, link); + await tester.pumpAndSettle(); + await tester.simulateKeyEvent(LogicalKeyboardKey.enter); + Node node = tester.editor.getNodeAtPath([0]); + expect(getLinkFromNode(node), link); + await tester.simulateKeyEvent(LogicalKeyboardKey.escape); + + /// hover link + await tester.hoverOnWidget(find.byType(LinkHoverTrigger)); + final hoverMenu = find.byType(LinkHoverMenu); + expect(hoverMenu, findsOneWidget); + + /// copy link + final copyButton = find.descendant( + of: hoverMenu, + matching: find.byFlowySvg(FlowySvgs.toolbar_link_m), + ); + await tester.tapButton(copyButton); + final clipboardContent = await getIt().getData(); + final plainText = clipboardContent.plainText; + expect(plainText, link); + + /// remove link + await tester.hoverOnWidget(find.byType(LinkHoverTrigger)); + await tester.tapButton(find.byFlowySvg(FlowySvgs.toolbar_link_unlink_m)); + node = tester.editor.getNodeAtPath([0]); + expect(getLinkFromNode(node), null); + }); + + testWidgets('insert link and edit link', (tester) async { + const text = 'edit link', + link = 'https://test.appflowy.cloud', + afterText = '$text after'; + await prepareForToolbar(tester, text); + + /// tap link button to show CreateLinkMenu + final linkButton = find.byFlowySvg(FlowySvgs.toolbar_link_m); + await tester.tapButton(linkButton); + + /// search for page and select it + final textField = find.descendant( + of: find.byType(LinkCreateMenu), + matching: find.byType(TextFormField), + ); + await tester.enterText(textField, gettingStarted); + await tester.pumpAndSettle(); + await tester.simulateKeyEvent(LogicalKeyboardKey.enter); + await tester.simulateKeyEvent(LogicalKeyboardKey.escape); + + Node node = tester.editor.getNodeAtPath([0]); + expect(isPageLink(node), true); + expect(getLinkFromNode(node) == link, false); + + /// hover link + await tester.hoverOnWidget(find.byType(LinkHoverTrigger)); + + /// click edit button to show LinkEditMenu + final editButton = find.byFlowySvg(FlowySvgs.toolbar_link_edit_m); + await tester.tapButton(editButton); + final linkEditMenu = find.byType(LinkEditMenu); + expect(linkEditMenu, findsOneWidget); + + /// change the link text + final titleField = find.descendant( + of: linkEditMenu, + matching: find.byType(TextFormField), + ); + await tester.enterText(titleField, afterText); + await tester.pumpAndSettle(); + await tester.tapButton( + find.descendant(of: linkEditMenu, matching: find.text(gettingStarted)), + ); + final linkField = find.ancestor( + of: find.text(LocaleKeys.document_toolbar_linkInputHint.tr()), + matching: find.byType(TextFormField), + ); + await tester.enterText(linkField, link); + await tester.pumpAndSettle(); + await tester.simulateKeyEvent(LogicalKeyboardKey.enter); + + /// apply the change + final applyButton = + find.text(LocaleKeys.settings_appearance_documentSettings_apply.tr()); + await tester.tapButton(applyButton); + + node = tester.editor.getNodeAtPath([0]); + expect(isPageLink(node), false); + expect(getLinkFromNode(node), link); + expect(getNodeText(node), afterText); + }); }); } diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_cover_image_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_cover_image_test.dart index c9f844f374..84b6790403 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_cover_image_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_cover_image_test.dart @@ -1,15 +1,23 @@ +import 'dart:io'; + import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/header/document_cover_widget.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_util.dart'; import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_emoji_mart/flutter_emoji_mart.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; +import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; import '../../shared/emoji.dart'; +import '../../shared/mock/mock_file_picker.dart'; import '../../shared/util.dart'; void main() { @@ -60,6 +68,59 @@ void main() { tester.expectToSeeNoDocumentCover(); }); + testWidgets('document cover local image tests', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + + tester.expectToSeeNoDocumentCover(); + + // Hover over cover toolbar to show 'Add Cover' and 'Add Icon' buttons + await tester.editor.hoverOnCoverToolbar(); + + // Insert a document cover + await tester.editor.tapOnAddCover(); + tester.expectToSeeDocumentCover(CoverType.asset); + + // Hover over the cover to show the 'Change Cover' and delete buttons + await tester.editor.hoverOnCover(); + tester.expectChangeCoverAndDeleteButton(); + + // Change cover to a local image image + final imagePath = await rootBundle.load('assets/test/images/sample.jpeg'); + final tempDirectory = await getTemporaryDirectory(); + final localImagePath = p.join(tempDirectory.path, 'sample.jpeg'); + final imageFile = File(localImagePath) + ..writeAsBytesSync(imagePath.buffer.asUint8List()); + + await tester.editor.hoverOnCover(); + await tester.editor.tapOnChangeCover(); + + final uploadButton = find.findTextInFlowyText( + LocaleKeys.document_imageBlock_upload_label.tr(), + ); + await tester.tapButton(uploadButton); + + mockPickFilePaths(paths: [localImagePath]); + await tester.tapButtonWithName( + LocaleKeys.document_imageBlock_upload_placeholder.tr(), + ); + + await tester.pumpAndSettle(); + tester.expectToSeeDocumentCover(CoverType.file); + + // Remove the cover + await tester.editor.hoverOnCover(); + await tester.editor.tapOnRemoveCover(); + tester.expectToSeeNoDocumentCover(); + + // Test if deleteImageFromLocalStorage(localImagePath) function is called once + await tester.pump(kDoubleTapTimeout); + expect(deleteImageTestCounter, 1); + + // delete temp files + await imageFile.delete(); + }); + testWidgets('document icon tests', (tester) async { await tester.initializeAppFlowy(); await tester.tapAnonymousSignInButton(); diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_image_block_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_image_block_test.dart index 2e6d8959c1..3dcd6be8ae 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_image_block_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_image_block_test.dart @@ -7,19 +7,16 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/image/cust import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_placeholder.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/image/resizeable_image.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_menu/upload_image_menu.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/embed_image_url_widget.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy_editor/appflowy_editor.dart' hide UploadImageMenu, ResizableImage; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; -import 'package:run_with_network_images/run_with_network_images.dart'; import '../../shared/mock/mock_file_picker.dart'; import '../../shared/util.dart'; @@ -29,58 +26,6 @@ void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('image block in document', () { - Future testEmbedImage(WidgetTester tester, String url) async { - await tester.initializeAppFlowy(); - await tester.tapAnonymousSignInButton(); - - // create a new document - await tester.createNewPageWithNameUnderParent( - name: LocaleKeys.document_plugins_image_addAnImageDesktop.tr(), - ); - - // tap the first line of the document - await tester.editor.tapLineOfEditorAt(0); - await tester.editor.showSlashMenu(); - await tester.editor.tapSlashMenuItemWithName( - LocaleKeys.document_slashMenu_name_image.tr(), - ); - expect(find.byType(CustomImageBlockComponent), findsOneWidget); - expect(find.byType(ImagePlaceholder), findsOneWidget); - expect( - find.descendant( - of: find.byType(ImagePlaceholder), - matching: find.byType(AppFlowyPopover), - ), - findsOneWidget, - ); - expect(find.byType(UploadImageMenu), findsOneWidget); - - await tester.tapButtonWithName( - LocaleKeys.document_imageBlock_embedLink_label.tr(), - ); - await tester.enterText( - find.descendant( - of: find.byType(EmbedImageUrlWidget), - matching: find.byType(TextField), - ), - url, - ); - await tester.tapButton( - find.descendant( - of: find.byType(EmbedImageUrlWidget), - matching: find.text( - LocaleKeys.document_imageBlock_embedLink_label.tr(), - findRichText: true, - ), - ), - ); - await tester.pumpAndSettle(); - expect(find.byType(ResizableImage), findsOneWidget); - final node = tester.editor.getCurrentEditorState().getNodeAtPath([0])!; - expect(node.type, ImageBlockKeys.type); - expect(node.attributes[ImageBlockKeys.url], url); - } - testWidgets('insert an image from local file', (tester) async { await tester.initializeAppFlowy(); await tester.tapAnonymousSignInButton(); @@ -131,43 +76,6 @@ void main() { file.deleteSync(); }); - testWidgets('insert a gif image from network', (tester) async { - await testEmbedImage( - tester, - 'https://www.easygifanimator.net/images/samples/sparkles.gif', - ); - }); - - testWidgets('insert an image from unsplash', (tester) async { - await runWithNetworkImages(() async { - await tester.initializeAppFlowy(); - await tester.tapAnonymousSignInButton(); - - // create a new document - await tester.createNewPageWithNameUnderParent( - name: LocaleKeys.document_plugins_image_addAnImageDesktop.tr(), - ); - - // tap the first line of the document - await tester.editor.tapLineOfEditorAt(0); - await tester.editor.showSlashMenu(); - await tester.editor.tapSlashMenuItemWithName( - LocaleKeys.document_slashMenu_name_image.tr(), - ); - expect(find.byType(CustomImageBlockComponent), findsOneWidget); - expect(find.byType(ImagePlaceholder), findsOneWidget); - expect( - find.descendant( - of: find.byType(ImagePlaceholder), - matching: find.byType(AppFlowyPopover), - ), - findsOneWidget, - ); - expect(find.byType(UploadImageMenu), findsOneWidget); - expect(find.text('Unsplash'), findsOneWidget); - }); - }); - testWidgets('insert two images from local file at once', (tester) async { await tester.initializeAppFlowy(); await tester.tapAnonymousSignInButton(); diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_inline_math_equation_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_inline_math_equation_test.dart index 45f688bd58..67e0149cd1 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_inline_math_equation_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_inline_math_equation_test.dart @@ -1,9 +1,11 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -32,9 +34,15 @@ void main() { Selection.single(path: [0], startOffset: 0, endOffset: formula.length), ); + // tap the more options button + final moreOptionButton = find.findFlowyTooltip( + LocaleKeys.document_toolbar_moreOptions.tr(), + ); + await tester.tapButton(moreOptionButton); + // tap the inline math equation button - final inlineMathEquationButton = find.findFlowyTooltip( - LocaleKeys.document_plugins_createInlineMathEquation.tr(), + final inlineMathEquationButton = find.text( + LocaleKeys.document_toolbar_equation.tr(), ); await tester.tapButton(inlineMathEquationButton); @@ -77,10 +85,15 @@ void main() { Selection.single(path: [0], startOffset: 0, endOffset: formula.length), ); - // tap the inline math equation button - var inlineMathEquationButton = find.findFlowyTooltip( - LocaleKeys.document_plugins_createInlineMathEquation.tr(), + // tap the more options button + final moreOptionButton = find.findFlowyTooltip( + LocaleKeys.document_toolbar_moreOptions.tr(), ); + await tester.tapButton(moreOptionButton); + + // tap the inline math equation button + final inlineMathEquationButton = + find.byFlowySvg(FlowySvgs.type_formula_m); await tester.tapButton(inlineMathEquationButton); // expect to see the math equation block @@ -92,17 +105,7 @@ void main() { Selection.single(path: [0], startOffset: 0, endOffset: 1), ); - // expect to the see the inline math equation button is highlighted - inlineMathEquationButton = find.descendant( - of: find.findFlowyTooltip( - LocaleKeys.document_plugins_createInlineMathEquation.tr(), - ), - matching: find.byType(SVGIconItemWidget), - ); - expect( - tester.widget(inlineMathEquationButton).isHighlight, - isTrue, - ); + await tester.tapButton(moreOptionButton); // cancel the format await tester.tapButton(inlineMathEquationButton); @@ -133,10 +136,15 @@ void main() { Selection.single(path: [0], startOffset: 0, endOffset: formula.length), ); - // tap the inline math equation button - final inlineMathEquationButton = find.findFlowyTooltip( - LocaleKeys.document_plugins_createInlineMathEquation.tr(), + // tap the more options button + final moreOptionButton = find.findFlowyTooltip( + LocaleKeys.document_toolbar_moreOptions.tr(), ); + await tester.tapButton(moreOptionButton); + + // tap the inline math equation button + final inlineMathEquationButton = + find.byFlowySvg(FlowySvgs.type_formula_m); await tester.tapButton(inlineMathEquationButton); // expect to see the math equation block @@ -163,5 +171,55 @@ void main() { lessThan(5), ); }); + + testWidgets('insert inline math equation by shortcut', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + + // create a new document + await tester.createNewPageWithNameUnderParent( + name: 'insert inline math equation by shortcut', + ); + + // tap the first line of the document + await tester.editor.tapLineOfEditorAt(0); + // insert a inline page + const formula = 'E = MC ^ 2'; + await tester.ime.insertText(formula); + await tester.editor.updateSelection( + Selection.single(path: [0], startOffset: 0, endOffset: formula.length), + ); + + // mock key event + await tester.simulateKeyEvent( + LogicalKeyboardKey.keyE, + isShiftPressed: true, + isControlPressed: true, + ); + + // expect to see the math equation block + final inlineMathEquation = find.byType(InlineMathEquation); + expect(inlineMathEquation, findsOneWidget); + + await tester.editor.tapLineOfEditorAt(0); + const text = 'Hello World'; + await tester.ime.insertText(text); + + final inlineText = find.textContaining(text, findRichText: true); + expect(inlineText, findsOneWidget); + + // the text should be in the same line with the math equation + final inlineMathEquationPosition = tester.getRect(inlineMathEquation); + final textPosition = tester.getRect(inlineText); + // allow 5px difference + expect( + (textPosition.top - inlineMathEquationPosition.top).abs(), + lessThan(5), + ); + expect( + (textPosition.bottom - inlineMathEquationPosition.bottom).abs(), + lessThan(5), + ); + }); }); } diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_multi_image_block_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_multi_image_block_test.dart index 68ad7db7e5..d8b0784a39 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_multi_image_block_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_multi_image_block_test.dart @@ -48,7 +48,7 @@ void main() { await tester.editor.showSlashMenu(); await tester.editor.tapSlashMenuItemWithName( LocaleKeys.document_slashMenu_name_photoGallery.tr(), - offset: 80, + offset: 100, ); expect(find.byType(MultiImageBlockComponent), findsOneWidget); expect(find.byType(MultiImagePlaceholder), findsOneWidget); @@ -146,7 +146,7 @@ void main() { await tester.editor.showSlashMenu(); await tester.editor.tapSlashMenuItemWithName( LocaleKeys.document_slashMenu_name_photoGallery.tr(), - offset: 80, + offset: 100, ); expect(find.byType(MultiImageBlockComponent), findsOneWidget); expect(find.byType(MultiImagePlaceholder), findsOneWidget); diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_toggle_heading_block_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_toggle_heading_block_test.dart index 8eb47fc15f..c4aa289855 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_toggle_heading_block_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_toggle_heading_block_test.dart @@ -1,3 +1,4 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; @@ -85,16 +86,10 @@ void main() { ), ); - await tester.tapButton(find.byType(HeadingPopup)); - await tester.pumpAndSettle(); - - expect( - find.byType(HeadingButton), - findsNWidgets(3), - ); + await tester.tapButton(find.byFlowySvg(FlowySvgs.toolbar_text_format_m)); // tap the H1 button - await tester.tapButton(find.byType(HeadingButton).at(0)); + await tester.tapButton(find.byFlowySvg(FlowySvgs.type_h1_m).at(0)); await tester.pumpAndSettle(); final editorState = tester.editor.getCurrentEditorState(); diff --git a/frontend/appflowy_flutter/integration_test/desktop/settings/shortcuts_settings_test.dart b/frontend/appflowy_flutter/integration_test/desktop/settings/shortcuts_settings_test.dart index 7913a88294..fe91becba6 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/settings/shortcuts_settings_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/settings/shortcuts_settings_test.dart @@ -35,10 +35,12 @@ void main() { await tester.pumpAndSettle(); await tester.hoverOnWidget( - find.descendant( - of: find.byType(ShortcutSettingTile), - matching: find.text(backspaceCmd), - ), + find + .descendant( + of: find.byType(ShortcutSettingTile), + matching: find.text(backspaceCmd), + ) + .first, onHover: () async { await tester.tap(find.byFlowySvg(FlowySvgs.edit_s)); await tester.pumpAndSettle(); diff --git a/frontend/appflowy_flutter/integration_test/desktop/settings/sign_in_page_settings_test.dart b/frontend/appflowy_flutter/integration_test/desktop/settings/sign_in_page_settings_test.dart index b7074be357..047e02da36 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/settings/sign_in_page_settings_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/settings/sign_in_page_settings_test.dart @@ -2,10 +2,10 @@ import 'package:appflowy/env/cloud_env.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/user/presentation/screens/sign_in_screen/desktop_sign_in_screen.dart'; import 'package:appflowy/workspace/presentation/settings/settings_dialog.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; -import 'package:toastification/toastification.dart'; import '../../shared/util.dart'; @@ -23,7 +23,7 @@ void main() { .last; } - group('sign-in page settings: ', () { + group('sign-in page settings:', () { testWidgets('change server type', (tester) async { await tester.initializeAppFlowy(); @@ -45,28 +45,36 @@ void main() { // change the server type to self-host await tester.tapButton(appflowyCloudType); - final selfhostedButton = findServerType( + final selfHostedButton = findServerType( AuthenticatorType.appflowyCloudSelfHost, ); - await tester.tapButton(selfhostedButton); + await tester.tapButton(selfHostedButton); // update server url - const serverUrl = 'https://test.appflowy.cloud'; + const serverUrl = 'https://self-hosted.appflowy.cloud'; await tester.enterText( find.byKey(kSelfHostedTextInputFieldKey), serverUrl, ); await tester.pumpAndSettle(); + // update the web url + const webUrl = 'https://self-hosted.appflowy.com'; + await tester.enterText( + find.byKey(kSelfHostedWebTextInputFieldKey), + webUrl, + ); + await tester.pumpAndSettle(); await tester.tapButton( find.findTextInFlowyText(LocaleKeys.button_save.tr()), ); // wait the app to restart, and the tooltip to disappear - await tester.pumpUntilNotFound(find.byType(BuiltInToastBuilder)); + await tester.pumpUntilNotFound(find.byType(DesktopToast)); await tester.pumpAndSettle(const Duration(milliseconds: 250)); // open settings page to check the result await tester.tapButton(settingsButton); + await tester.pumpAndSettle(const Duration(milliseconds: 250)); // check the server type expect( @@ -78,6 +86,11 @@ void main() { find.text(serverUrl), findsOneWidget, ); + // check the web url + expect( + find.text(webUrl), + findsOneWidget, + ); // reset to appflowy cloud await tester.tapButton( @@ -89,7 +102,7 @@ void main() { ); // wait the app to restart, and the tooltip to disappear - await tester.pumpUntilNotFound(find.byType(BuiltInToastBuilder)); + await tester.pumpUntilNotFound(find.byType(DesktopToast)); await tester.pumpAndSettle(const Duration(milliseconds: 250)); // check the server type diff --git a/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_expand_test.dart b/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_expand_test.dart index f2a1fae8ae..ad18cf3de6 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_expand_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_expand_test.dart @@ -1,8 +1,12 @@ import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/inline_actions/widgets/inline_actions_handler.dart'; import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/sidebar_folder.dart'; import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pbenum.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -44,5 +48,82 @@ void main() { ); expect(isExpanded(type: FolderSpaceType.private), true); }); + + testWidgets('Expanding with subpage', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + const page1 = 'SubPageBloc', page2 = '$page1 2'; + await tester.createNewPageWithNameUnderParent(name: page1); + await tester.createNewPageWithNameUnderParent( + name: page2, + parentName: page1, + ); + + await tester.expandOrCollapsePage( + pageName: gettingStarted, + layout: ViewLayoutPB.Document, + ); + + await tester.tapNewPageButton(); + + await tester.editor.tapLineOfEditorAt(0); + await tester.pumpAndSettle(); + await tester.editor.showSlashMenu(); + await tester.pumpAndSettle(); + + final slashMenu = find + .ancestor( + of: find.byType(SelectionMenuItemWidget), + matching: find.byWidgetPredicate( + (widget) => widget is Scrollable, + ), + ) + .first; + final slashMenuItem = find.text( + LocaleKeys.document_slashMenu_name_linkedDoc.tr(), + ); + await tester.scrollUntilVisible( + slashMenuItem, + 100, + scrollable: slashMenu, + duration: const Duration(milliseconds: 250), + ); + + final menuItemFinder = find.byWidgetPredicate( + (w) => + w is SelectionMenuItemWidget && + w.item.name == LocaleKeys.document_slashMenu_name_linkedDoc.tr(), + ); + + final menuItem = + menuItemFinder.evaluate().first.widget as SelectionMenuItemWidget; + + /// tapSlashMenuItemWithName is not working, so invoke this function directly + menuItem.item.handler( + menuItem.editorState, + menuItem.menuService, + menuItemFinder.evaluate().first, + ); + + await tester.pumpAndSettle(); + final actionHandler = find.byType(InlineActionsHandler); + final subPage = find.descendant( + of: actionHandler, + matching: find.text(page2, findRichText: true), + ); + await tester.tapButton(subPage); + + final subpageBlock = find.descendant( + of: find.byType(AppFlowyEditor), + matching: find.text(page2, findRichText: true), + ); + + expect(find.text(page2, findRichText: true), findsOneWidget); + await tester.tapButton(subpageBlock); + + /// one is in SectionFolder, another one is in CoverTitle + /// the last one is in FlowyNavigation + expect(find.text(page2, findRichText: true), findsNWidgets(3)); + }); }); } diff --git a/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_favorites_test.dart b/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_favorites_test.dart index 729ee62a3e..3345ed30ab 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_favorites_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_favorites_test.dart @@ -196,5 +196,58 @@ void main() { await tester.pumpAndSettle(); }, ); + + testWidgets( + 'reorder favorites', + (tester) async { + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + + /// there are no favorite views + final favorites = find.descendant( + of: find.byType(FavoriteFolder), + matching: find.byType(ViewItem), + ); + expect(favorites, findsNothing); + + /// create views and then favorite them + const pageNames = ['001', '002', '003']; + for (final name in pageNames) { + await tester.createNewPageWithNameUnderParent(name: name); + } + for (final name in pageNames) { + await tester.favoriteViewByName(name); + } + expect(favorites, findsNWidgets(pageNames.length)); + + final oldNames = favorites + .evaluate() + .map((e) => (e.widget as ViewItem).view.name) + .toList(); + expect(oldNames, pageNames); + + /// drag first to last + await tester.reorderFavorite( + fromName: '001', + toName: '003', + ); + List newNames = favorites + .evaluate() + .map((e) => (e.widget as ViewItem).view.name) + .toList(); + expect(newNames, ['002', '003', '001']); + + /// drag first to second + await tester.reorderFavorite( + fromName: '002', + toName: '003', + ); + newNames = favorites + .evaluate() + .map((e) => (e.widget as ViewItem).view.name) + .toList(); + expect(newNames, ['003', '002', '001']); + }, + ); }); } diff --git a/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_icon_test.dart b/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_icon_test.dart index 7c7b9e1f1c..2236f03960 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_icon_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_icon_test.dart @@ -1,12 +1,11 @@ import 'package:appflowy/plugins/base/emoji/emoji_picker.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart'; import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; -import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart'; import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart'; -import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon_popup.dart'; import 'package:appflowy/workspace/presentation/widgets/view_title_bar.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:flowy_infra_ui/style_widget/text_field.dart'; +import 'package:flowy_svg/flowy_svg.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -27,21 +26,6 @@ void main() { RecentIcons.enable = true; }); - Future loadIcon() async { - await loadIconGroups(); - final groups = kIconGroups!; - final firstGroup = groups.first; - final firstIcon = firstGroup.icons.first; - return EmojiIconData.icon( - IconsData( - firstGroup.name, - firstIcon.content, - firstIcon.name, - builtInSpaceColors.first, - ), - ); - } - testWidgets('Update page emoji in sidebar', (tester) async { await tester.initializeAppFlowy(); await tester.tapAnonymousSignInButton(); @@ -160,7 +144,7 @@ void main() { testWidgets('Update page icon in sidebar', (tester) async { await tester.initializeAppFlowy(); await tester.tapAnonymousSignInButton(); - final iconData = await loadIcon(); + final iconData = await tester.loadIcon(); // create document, board, grid and calendar views for (final value in ViewLayoutPB.values) { @@ -192,7 +176,7 @@ void main() { testWidgets('Update page icon in title bar', (tester) async { await tester.initializeAppFlowy(); await tester.tapAnonymousSignInButton(); - final iconData = await loadIcon(); + final iconData = await tester.loadIcon(); // create document, board, grid and calendar views for (final value in ViewLayoutPB.values) { @@ -226,4 +210,137 @@ void main() { ); } }); + + testWidgets('Update page custom image icon in title bar', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + + /// prepare local image + final iconData = await tester.prepareImageIcon(); + + // create document, board, grid and calendar views + for (final value in ViewLayoutPB.values) { + if (value == ViewLayoutPB.Chat) { + continue; + } + + await tester.createNewPageWithNameUnderParent( + name: value.name, + parentName: gettingStarted, + layout: value, + ); + + // update its icon + await tester.updatePageIconInTitleBarByName( + name: value.name, + layout: value, + icon: iconData, + ); + + tester.expectViewHasIcon( + value.name, + value, + iconData, + ); + + tester.expectViewTitleHasIcon( + value.name, + value, + iconData, + ); + } + }); + + testWidgets('Update page custom svg icon in title bar', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + + /// prepare local image + final iconData = await tester.prepareSvgIcon(); + + // create document, board, grid and calendar views + for (final value in ViewLayoutPB.values) { + if (value == ViewLayoutPB.Chat) { + continue; + } + + await tester.createNewPageWithNameUnderParent( + name: value.name, + parentName: gettingStarted, + layout: value, + ); + + // update its icon + await tester.updatePageIconInTitleBarByName( + name: value.name, + layout: value, + icon: iconData, + ); + + tester.expectViewHasIcon( + value.name, + value, + iconData, + ); + + tester.expectViewTitleHasIcon( + value.name, + value, + iconData, + ); + } + }); + + testWidgets('Update page custom svg icon in title bar by pasting a link', + (tester) async { + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + + /// prepare local image + const testIconLink = + 'https://beta.appflowy.cloud/api/file_storage/008e6f23-516b-4d8d-b1fe-2b75c51eee26/v1/blob/6bdf8dff%2D0e54%2D4d35%2D9981%2Dcde68bef1141/BGpLnRtb3AGBNgSJsceu70j83zevYKrMLzqsTIJcBeI=.svg'; + + /// create document, board, grid and calendar views + for (final value in ViewLayoutPB.values) { + if (value == ViewLayoutPB.Chat) { + continue; + } + + await tester.createNewPageWithNameUnderParent( + name: value.name, + parentName: gettingStarted, + layout: value, + ); + + /// update its icon + await tester.updatePageIconInTitleBarByPasteALink( + name: value.name, + layout: value, + iconLink: testIconLink, + ); + + /// check if there is a svg in page + final pageName = tester.findPageName( + value.name, + layout: value, + ); + final imageInPage = find.descendant( + of: pageName, + matching: find.byType(SvgPicture), + ); + expect(imageInPage, findsOneWidget); + + /// check if there is a svg in title + final imageInTitle = find.descendant( + of: find.byType(ViewTitleBar), + matching: find.byWidgetPredicate((w) { + if (w is! SvgPicture) return false; + final loader = w.bytesLoader; + if (loader is! SvgFileLoader) return false; + return loader.file.path.endsWith('.svg'); + }), + ); + expect(imageInTitle, findsOneWidget); + } + }); } diff --git a/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_recent_icon_test.dart b/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_recent_icon_test.dart new file mode 100644 index 0000000000..2b724ffac1 --- /dev/null +++ b/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_recent_icon_test.dart @@ -0,0 +1,56 @@ +import 'package:appflowy/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart'; +import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; +import 'package:appflowy/shared/icon_emoji_picker/icon.dart'; +import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart'; +import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; +import 'package:appflowy/workspace/presentation/widgets/view_title_bar.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../shared/base.dart'; +import '../../shared/common_operations.dart'; +import '../../shared/expectation.dart'; + +void main() { + testWidgets('Skip the empty group name icon in recent icons', (tester) async { + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + + /// clear local data + RecentIcons.clear(); + await loadIconGroups(); + final groups = kIconGroups!; + final List localIcons = []; + for (final e in groups) { + localIcons.addAll(e.icons.map((e) => RecentIcon(e, e.name)).toList()); + } + await RecentIcons.putIcon(RecentIcon(localIcons.first.icon, '')); + await tester.openPage(gettingStarted); + final title = find.descendant( + of: find.byType(ViewTitleBar), + matching: find.text(gettingStarted), + ); + await tester.tapButton(title); + + /// tap emoji picker button + await tester.tapButton(find.byType(EmojiPickerButton)); + expect(find.byType(FlowyIconEmojiPicker), findsOneWidget); + + /// tap icon tab + final pickTab = find.byType(PickerTab); + final iconTab = find.descendant( + of: pickTab, + matching: find.text(PickerTabType.icon.tr), + ); + await tester.tapButton(iconTab); + + expect(find.byType(FlowyIconPicker), findsOneWidget); + + /// no recent icons + final recentText = find.descendant( + of: find.byType(FlowyIconPicker), + matching: find.text('Recent'), + ); + expect(recentText, findsNothing); + }); +} diff --git a/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_test_runner.dart b/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_test_runner.dart index 9e4212f955..ef7d3dbc8b 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_test_runner.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_test_runner.dart @@ -2,6 +2,7 @@ import 'package:integration_test/integration_test.dart'; import 'sidebar_favorites_test.dart' as sidebar_favorite_test; import 'sidebar_icon_test.dart' as sidebar_icon_test; +import 'sidebar_recent_icon_test.dart' as sidebar_recent_icon_test; import 'sidebar_test.dart' as sidebar_test; import 'sidebar_view_item_test.dart' as sidebar_view_item_test; @@ -14,4 +15,5 @@ void main() { sidebar_favorite_test.main(); sidebar_icon_test.main(); sidebar_view_item_test.main(); + sidebar_recent_icon_test.main(); } diff --git a/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_view_item_test.dart b/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_view_item_test.dart index 1400ccebe3..f2b721e686 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_view_item_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_view_item_test.dart @@ -1,6 +1,7 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/base/emoji/emoji_picker.dart'; import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; +import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart'; import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -12,7 +13,14 @@ import '../../shared/emoji.dart'; import '../../shared/util.dart'; void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + setUpAll(() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + RecentIcons.enable = false; + }); + + tearDownAll(() { + RecentIcons.enable = true; + }); group('Sidebar view item tests', () { testWidgets('Access view item context menu by right click', (tester) async { diff --git a/frontend/appflowy_flutter/integration_test/desktop/uncategorized/emoji_shortcut_test.dart b/frontend/appflowy_flutter/integration_test/desktop/uncategorized/emoji_shortcut_test.dart index 554a6eecbf..d3226a3ad0 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/uncategorized/emoji_shortcut_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/uncategorized/emoji_shortcut_test.dart @@ -1,42 +1,166 @@ import 'dart:io'; -import 'package:appflowy/workspace/presentation/settings/widgets/emoji_picker/emoji_picker.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; -import 'package:appflowy_editor/src/editor/editor_component/service/editor.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/emoji/emoji_handler.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; - -import '../../shared/keyboard.dart'; import '../../shared/util.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + Future prepare(WidgetTester tester) async { + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + await tester.createNewPageWithNameUnderParent(); + await tester.editor.tapLineOfEditorAt(0); + } + // May be better to move this to an existing test but unsure what it fits with group('Keyboard shortcuts related to emojis', () { testWidgets('cmd/ctrl+alt+e shortcut opens the emoji picker', (tester) async { - await tester.initializeAppFlowy(); - await tester.tapAnonymousSignInButton(); + await prepare(tester); - final Finder editor = find.byType(AppFlowyEditor); - await tester.tap(editor); - await tester.pumpAndSettle(); + expect(find.byType(EmojiHandler), findsNothing); - expect(find.byType(EmojiSelectionMenu), findsNothing); - - await FlowyTestKeyboard.simulateKeyDownEvent( - [ - Platform.isMacOS - ? LogicalKeyboardKey.meta - : LogicalKeyboardKey.control, - LogicalKeyboardKey.alt, - LogicalKeyboardKey.keyE, - ], - tester: tester, + await tester.simulateKeyEvent( + LogicalKeyboardKey.keyE, + isAltPressed: true, + isMetaPressed: Platform.isMacOS, + isControlPressed: !Platform.isMacOS, ); + await tester.pumpAndSettle(Duration(seconds: 1)); + expect(find.byType(EmojiHandler), findsOneWidget); - expect(find.byType(EmojiSelectionMenu), findsOneWidget); + /// press backspace to hide the emoji picker + await tester.simulateKeyEvent(LogicalKeyboardKey.backspace); + expect(find.byType(EmojiHandler), findsNothing); + }); + + testWidgets('insert emoji by slash menu', (tester) async { + await prepare(tester); + await tester.editor.showSlashMenu(); + + /// show emoji picler + await tester.editor.tapSlashMenuItemWithName( + LocaleKeys.document_slashMenu_name_emoji.tr(), + offset: 100, + ); + await tester.pumpAndSettle(Duration(seconds: 1)); + expect(find.byType(EmojiHandler), findsOneWidget); + await tester.simulateKeyEvent(LogicalKeyboardKey.enter); + final firstNode = + tester.editor.getCurrentEditorState().getNodeAtPath([0])!; + + /// except the emoji is in document + expect(firstNode.delta!.toPlainText().contains('😀'), true); + }); + }); + + group('insert emoji by colon', () { + Future createNewDocumentAndShowEmojiList( + WidgetTester tester, { + String? search, + }) async { + await prepare(tester); + await tester.ime.insertText(':${search ?? 'a'}'); + await tester.pumpAndSettle(Duration(seconds: 1)); + } + + testWidgets('insert with click', (tester) async { + await createNewDocumentAndShowEmojiList(tester); + + /// emoji list is showing + final emojiHandler = find.byType(EmojiHandler); + expect(emojiHandler, findsOneWidget); + final emojiButtons = + find.descendant(of: emojiHandler, matching: find.byType(FlowyButton)); + final firstTextFinder = find.descendant( + of: emojiButtons.first, + matching: find.byType(FlowyText), + ); + final emojiText = + (firstTextFinder.evaluate().first.widget as FlowyText).text; + + /// click first emoji item + await tester.tapButton(emojiButtons.first); + final firstNode = + tester.editor.getCurrentEditorState().getNodeAtPath([0])!; + + /// except the emoji is in document + expect(emojiText.contains(firstNode.delta!.toPlainText()), true); + }); + + testWidgets('insert with arrow and enter', (tester) async { + await createNewDocumentAndShowEmojiList(tester); + + /// emoji list is showing + final emojiHandler = find.byType(EmojiHandler); + expect(emojiHandler, findsOneWidget); + final emojiButtons = + find.descendant(of: emojiHandler, matching: find.byType(FlowyButton)); + + /// tap arrow down and arrow up + await tester.simulateKeyEvent(LogicalKeyboardKey.arrowUp); + await tester.simulateKeyEvent(LogicalKeyboardKey.arrowDown); + + final firstTextFinder = find.descendant( + of: emojiButtons.first, + matching: find.byType(FlowyText), + ); + final emojiText = + (firstTextFinder.evaluate().first.widget as FlowyText).text; + + /// tap enter + await tester.simulateKeyEvent(LogicalKeyboardKey.enter); + final firstNode = + tester.editor.getCurrentEditorState().getNodeAtPath([0])!; + + /// except the emoji is in document + expect(emojiText.contains(firstNode.delta!.toPlainText()), true); + }); + + testWidgets('insert with searching', (tester) async { + await createNewDocumentAndShowEmojiList(tester, search: 's'); + + /// search for `smiling eyes`, IME is not working, use keyboard input + final searchText = [ + LogicalKeyboardKey.keyM, + LogicalKeyboardKey.keyI, + LogicalKeyboardKey.keyL, + LogicalKeyboardKey.keyI, + LogicalKeyboardKey.keyN, + LogicalKeyboardKey.keyG, + LogicalKeyboardKey.space, + LogicalKeyboardKey.keyE, + LogicalKeyboardKey.keyY, + LogicalKeyboardKey.keyE, + LogicalKeyboardKey.keyS, + ]; + + for (final key in searchText) { + await tester.simulateKeyEvent(key); + } + + /// tap enter + await tester.simulateKeyEvent(LogicalKeyboardKey.enter); + final firstNode = + tester.editor.getCurrentEditorState().getNodeAtPath([0])!; + + /// except the emoji is in document + expect(firstNode.delta!.toPlainText().contains('😄'), true); + }); + + testWidgets('start searching with sapce', (tester) async { + await createNewDocumentAndShowEmojiList(tester, search: ' '); + + /// emoji list is showing + final emojiHandler = find.byType(EmojiHandler); + expect(emojiHandler, findsNothing); }); }); } diff --git a/frontend/appflowy_flutter/integration_test/desktop/uncategorized/hotkeys_test.dart b/frontend/appflowy_flutter/integration_test/desktop/uncategorized/hotkeys_test.dart index 4a38dde920..1d0f13eebc 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/uncategorized/hotkeys_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/uncategorized/hotkeys_test.dart @@ -1,13 +1,12 @@ import 'dart:io'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/workspace/application/settings/prelude.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar.dart'; import 'package:appflowy/workspace/presentation/settings/settings_dialog.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -38,7 +37,7 @@ void main() { LocaleKeys.settings_workspacePage_appearance_options_light.tr(), ), ); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(const Duration(milliseconds: 250)); themeMode = tester.widget(appFinder).themeMode; expect(themeMode, ThemeMode.light); @@ -48,7 +47,7 @@ void main() { LocaleKeys.settings_workspacePage_appearance_options_dark.tr(), ), ); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(const Duration(milliseconds: 250)); themeMode = tester.widget(appFinder).themeMode; expect(themeMode, ThemeMode.dark); @@ -66,10 +65,11 @@ void main() { ], tester: tester, ); - await tester.pumpAndSettle(); + await tester.pumpAndSettle(const Duration(milliseconds: 500)); - themeMode = tester.widget(appFinder).themeMode; - expect(themeMode, ThemeMode.light); + // disable it temporarily. It works on macOS but not on Linux. + // themeMode = tester.widget(appFinder).themeMode; + // expect(themeMode, ThemeMode.light); }); testWidgets('show or hide home menu', (tester) async { diff --git a/frontend/appflowy_flutter/integration_test/desktop/uncategorized/share_markdown_test.dart b/frontend/appflowy_flutter/integration_test/desktop/uncategorized/share_markdown_test.dart index 9a4fe30815..8c3c29ab77 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/uncategorized/share_markdown_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/uncategorized/share_markdown_test.dart @@ -1,12 +1,16 @@ +import 'dart:convert'; import 'dart:io'; import 'package:appflowy/plugins/shared/share/share_button.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pbenum.dart'; +import 'package:archive/archive.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'package:path/path.dart' as p; import '../../shared/mock/mock_file_picker.dart'; import '../../shared/util.dart'; +import '../document/document_with_database_test.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -18,7 +22,7 @@ void main() { // mock the file picker final path = await mockSaveFilePath( - p.join(context.applicationDataDirectory, 'test.md'), + p.join(context.applicationDataDirectory, 'test.zip'), ); // click the share button and select markdown await tester.tapShareButton(); @@ -28,10 +32,14 @@ void main() { tester.expectToExportSuccess(); final file = File(path); - final isExist = file.existsSync(); - expect(isExist, true); - final markdown = file.readAsStringSync(); - expect(markdown, expectedMarkdown); + expect(file.existsSync(), true); + final archive = ZipDecoder().decodeBytes(file.readAsBytesSync()); + for (final entry in archive) { + if (entry.isFile && entry.name.endsWith('.md')) { + final markdown = utf8.decode(entry.content); + expect(markdown, expectedMarkdown); + } + } }); testWidgets( @@ -57,7 +65,7 @@ void main() { final path = await mockSaveFilePath( p.join( context.applicationDataDirectory, - '${shareButtonState.view.name}.md', + '${shareButtonState.view.name}.zip', ), ); @@ -69,10 +77,44 @@ void main() { tester.expectToExportSuccess(); final file = File(path); - final isExist = file.existsSync(); - expect(isExist, true); + expect(file.existsSync(), true); + final archive = ZipDecoder().decodeBytes(file.readAsBytesSync()); + for (final entry in archive) { + if (entry.isFile && entry.name.endsWith('.md')) { + final markdown = utf8.decode(entry.content); + expect(markdown, expectedMarkdown); + } + } }, ); + + testWidgets('share the markdown with database', (tester) async { + final context = await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + await insertLinkedDatabase(tester, ViewLayoutPB.Grid); + + // mock the file picker + final path = await mockSaveFilePath( + p.join(context.applicationDataDirectory, 'test.zip'), + ); + // click the share button and select markdown + await tester.tapShareButton(); + await tester.tapMarkdownButton(); + + // expect to see the success dialog + tester.expectToExportSuccess(); + + final file = File(path); + expect(file.existsSync(), true); + final archive = ZipDecoder().decodeBytes(file.readAsBytesSync()); + bool hasCsvFile = false; + for (final entry in archive) { + if (entry.isFile && entry.name.endsWith('.csv')) { + hasCsvFile = true; + } + } + expect(hasCsvFile, true); + }); }); } diff --git a/frontend/appflowy_flutter/integration_test/desktop/uncategorized/tabs_test.dart b/frontend/appflowy_flutter/integration_test/desktop/uncategorized/tabs_test.dart index d0bc391987..63ec958c54 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/uncategorized/tabs_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/uncategorized/tabs_test.dart @@ -1,11 +1,15 @@ +import 'dart:convert'; import 'dart:io'; import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart'; +import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart'; import 'package:appflowy/workspace/presentation/home/tabs/flowy_tab.dart'; import 'package:appflowy/workspace/presentation/home/tabs/tabs_manager.dart'; import 'package:appflowy/workspace/presentation/widgets/tab_bar_item.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_svg/flowy_svg.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -285,6 +289,39 @@ void main() { expect(tester.isTabAtIndex(secondTabName, 1), isTrue); expect(tester.isTabAtIndex(thirdTabName, 2), isTrue); }); + + testWidgets('displaying icons in tab', (tester) async { + RecentIcons.enable = false; + await tester.initializeAppFlowy(); + await tester.tapAnonymousSignInButton(); + + final icon = await tester.loadIcon(); + // update emoji + await tester.updatePageIconInSidebarByName( + name: gettingStarted, + parentName: gettingStarted, + layout: ViewLayoutPB.Document, + icon: icon, + ); + + /// create new page + await tester.createNewPageWithNameUnderParent(name: _documentName); + + /// open new tab for [gettingStarted] + await tester.openAppInNewTab(gettingStarted, ViewLayoutPB.Document); + + final tabs = find.descendant( + of: find.byType(TabsManager), + matching: find.byType(FlowyTab), + ); + expect(tabs, findsNWidgets(2)); + + final svgInTab = + find.descendant(of: tabs.last, matching: find.byType(FlowySvg)); + final svgWidget = svgInTab.evaluate().first.widget as FlowySvg; + final iconsData = IconsData.fromJson(jsonDecode(icon.emoji)); + expect(svgWidget.svgString, iconsData.svgString); + }); }); } diff --git a/frontend/appflowy_flutter/integration_test/desktop/uncategorized/uncategorized_test_runner_1.dart b/frontend/appflowy_flutter/integration_test/desktop/uncategorized/uncategorized_test_runner_1.dart index f7d94e8b4a..836cfe4ccd 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/uncategorized/uncategorized_test_runner_1.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/uncategorized/uncategorized_test_runner_1.dart @@ -13,7 +13,6 @@ void main() { hotkeys_test.main(); emoji_shortcut_test.main(); hotkeys_test.main(); - emoji_shortcut_test.main(); share_markdown_test.main(); import_files_test.main(); zoom_in_out_test.main(); diff --git a/frontend/appflowy_flutter/integration_test/desktop_runner_9.dart b/frontend/appflowy_flutter/integration_test/desktop_runner_9.dart index 3402db1fd9..451e24cdc1 100644 --- a/frontend/appflowy_flutter/integration_test/desktop_runner_9.dart +++ b/frontend/appflowy_flutter/integration_test/desktop_runner_9.dart @@ -1,9 +1,10 @@ import 'package:integration_test/integration_test.dart'; -import 'desktop/uncategorized/tabs_test.dart' as tabs_test; +import 'desktop/database/database_icon_test.dart' as database_icon_test; +import 'desktop/first_test/first_test.dart' as first_test; import 'desktop/uncategorized/code_block_language_selector_test.dart' as code_language_selector; -import 'desktop/first_test/first_test.dart' as first_test; +import 'desktop/uncategorized/tabs_test.dart' as tabs_test; Future main() async { await runIntegration9OnDesktop(); @@ -15,4 +16,5 @@ Future runIntegration9OnDesktop() async { first_test.main(); tabs_test.main(); code_language_selector.main(); + database_icon_test.main(); } diff --git a/frontend/appflowy_flutter/integration_test/mobile/document/at_menu_test.dart b/frontend/appflowy_flutter/integration_test/mobile/document/at_menu_test.dart new file mode 100644 index 0000000000..2b348d3a2e --- /dev/null +++ b/frontend/appflowy_flutter/integration_test/mobile/document/at_menu_test.dart @@ -0,0 +1,56 @@ +import 'package:appflowy/mobile/presentation/inline_actions/mobile_inline_actions_menu.dart'; +import 'package:appflowy/mobile/presentation/inline_actions/mobile_inline_actions_menu_group.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../../shared/util.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + const title = 'Test At Menu'; + + group('at menu', () { + testWidgets('show at menu', (tester) async { + await tester.launchInAnonymousMode(); + await tester.createPageAndShowAtMenu(title); + final menuWidget = find.byType(MobileInlineActionsMenu); + expect(menuWidget, findsOneWidget); + }); + + testWidgets('search by at menu', (tester) async { + await tester.launchInAnonymousMode(); + await tester.createPageAndShowAtMenu(title); + const searchText = gettingStarted; + await tester.ime.insertText(searchText); + final actionWidgets = find.byType(MobileInlineActionsWidget); + expect(actionWidgets, findsNWidgets(2)); + }); + + testWidgets('tap at menu', (tester) async { + await tester.launchInAnonymousMode(); + await tester.createPageAndShowAtMenu(title); + const searchText = gettingStarted; + await tester.ime.insertText(searchText); + final actionWidgets = find.byType(MobileInlineActionsWidget); + await tester.tap(actionWidgets.last); + expect(find.byType(MentionPageBlock), findsOneWidget); + }); + + testWidgets('create subpage with at menu', (tester) async { + await tester.launchInAnonymousMode(); + await tester.createNewDocumentOnMobile(title); + await tester.editor.tapLineOfEditorAt(0); + const subpageName = 'Subpage'; + await tester.ime.insertText('[[$subpageName'); + await tester.pumpAndSettle(); + final actionWidgets = find.byType(MobileInlineActionsWidget); + await tester.tapButton(actionWidgets.first); + final firstNode = + tester.editor.getCurrentEditorState().getNodeAtPath([0]); + assert(firstNode != null); + expect(firstNode!.delta?.toPlainText().contains('['), false); + }); + }); +} diff --git a/frontend/appflowy_flutter/integration_test/mobile/document/document_test_runner.dart b/frontend/appflowy_flutter/integration_test/mobile/document/document_test_runner.dart index 67fdaaa204..90d5ca6d0d 100644 --- a/frontend/appflowy_flutter/integration_test/mobile/document/document_test_runner.dart +++ b/frontend/appflowy_flutter/integration_test/mobile/document/document_test_runner.dart @@ -1,9 +1,13 @@ import 'package:integration_test/integration_test.dart'; +import 'at_menu_test.dart' as at_menu; +import 'at_menu_test.dart' as at_menu_test; import 'page_style_test.dart' as page_style_test; import 'plus_menu_test.dart' as plus_menu_test; import 'simple_table_test.dart' as simple_table_test; +import 'slash_menu_test.dart' as slash_menu; import 'title_test.dart' as title_test; +import 'toolbar_test.dart' as toolbar_test; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -12,5 +16,9 @@ void main() { title_test.main(); page_style_test.main(); plus_menu_test.main(); + at_menu_test.main(); simple_table_test.main(); + toolbar_test.main(); + slash_menu.main(); + at_menu.main(); } diff --git a/frontend/appflowy_flutter/integration_test/mobile/document/icon_test.dart b/frontend/appflowy_flutter/integration_test/mobile/document/icon_test.dart new file mode 100644 index 0000000000..da7c7e92e7 --- /dev/null +++ b/frontend/appflowy_flutter/integration_test/mobile/document/icon_test.dart @@ -0,0 +1,104 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/mobile/presentation/base/view_page/app_bar_buttons.dart'; +import 'package:appflowy/mobile/presentation/presentation.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_icon.dart'; +import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../../shared/emoji.dart'; +import '../../shared/util.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('document title:', () { + testWidgets('update page custom image icon in title bar', (tester) async { + await tester.launchInAnonymousMode(); + + /// prepare local image + final iconData = await tester.prepareImageIcon(); + + /// create an empty page + await tester + .tapButton(find.byKey(BottomNavigationBarItemType.add.valueKey)); + + /// show Page style page + await tester.tapButton(find.byType(MobileViewPageLayoutButton)); + final pageStyleIcon = find.byType(PageStyleIcon); + final iconInPageStyleIcon = find.descendant( + of: pageStyleIcon, + matching: find.byType(RawEmojiIconWidget), + ); + expect(iconInPageStyleIcon, findsNothing); + + /// show icon picker + await tester.tapButton(pageStyleIcon); + + /// upload custom icon + await tester.pickImage(iconData); + + /// check result + final documentPage = find.byType(MobileDocumentScreen); + final rawEmojiIconFinder = find + .descendant( + of: documentPage, + matching: find.byType(RawEmojiIconWidget), + ) + .last; + final rawEmojiIconWidget = + rawEmojiIconFinder.evaluate().first.widget as RawEmojiIconWidget; + final iconDataInWidget = rawEmojiIconWidget.emoji; + expect(iconDataInWidget.type, FlowyIconType.custom); + final imageFinder = + find.descendant(of: rawEmojiIconFinder, matching: find.byType(Image)); + expect(imageFinder, findsOneWidget); + }); + + testWidgets('update page custom svg icon in title bar', (tester) async { + await tester.launchInAnonymousMode(); + + /// prepare local image + final iconData = await tester.prepareSvgIcon(); + + /// create an empty page + await tester + .tapButton(find.byKey(BottomNavigationBarItemType.add.valueKey)); + + /// show Page style page + await tester.tapButton(find.byType(MobileViewPageLayoutButton)); + final pageStyleIcon = find.byType(PageStyleIcon); + final iconInPageStyleIcon = find.descendant( + of: pageStyleIcon, + matching: find.byType(RawEmojiIconWidget), + ); + expect(iconInPageStyleIcon, findsNothing); + + /// show icon picker + await tester.tapButton(pageStyleIcon); + + /// upload custom icon + await tester.pickImage(iconData); + + /// check result + final documentPage = find.byType(MobileDocumentScreen); + final rawEmojiIconFinder = find + .descendant( + of: documentPage, + matching: find.byType(RawEmojiIconWidget), + ) + .last; + final rawEmojiIconWidget = + rawEmojiIconFinder.evaluate().first.widget as RawEmojiIconWidget; + final iconDataInWidget = rawEmojiIconWidget.emoji; + expect(iconDataInWidget.type, FlowyIconType.custom); + final svgFinder = find.descendant( + of: rawEmojiIconFinder, + matching: find.byType(SvgPicture), + ); + expect(svgFinder, findsOneWidget); + }); + }); +} diff --git a/frontend/appflowy_flutter/integration_test/mobile/document/page_style_test.dart b/frontend/appflowy_flutter/integration_test/mobile/document/page_style_test.dart index 4d34861850..e3d3bc093f 100644 --- a/frontend/appflowy_flutter/integration_test/mobile/document/page_style_test.dart +++ b/frontend/appflowy_flutter/integration_test/mobile/document/page_style_test.dart @@ -1,17 +1,30 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart'; import 'package:appflowy/mobile/presentation/base/view_page/app_bar_buttons.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_buttons.dart'; +import 'package:appflowy/mobile/presentation/mobile_bottom_navigation_bar.dart'; import 'package:appflowy/plugins/document/presentation/editor_page.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_icon.dart'; +import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; +import '../../shared/emoji.dart'; import '../../shared/util.dart'; void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + setUpAll(() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + RecentIcons.enable = false; + }); + + tearDownAll(() { + RecentIcons.enable = true; + }); group('document page style:', () { double getCurrentEditorFontSize() { @@ -114,5 +127,37 @@ void main() { ); expect(builtInCover, findsOneWidget); }); + + testWidgets('page style icon', (tester) async { + await tester.launchInAnonymousMode(); + + final createPageButton = + find.byKey(BottomNavigationBarItemType.add.valueKey); + await tester.tapButton(createPageButton); + + /// toggle the preset button + await tester.tapSvgButton(FlowySvgs.m_layout_s); + + /// select document plugins emoji + final pageStyleIcon = find.byType(PageStyleIcon); + + /// there should be none of emoji + final noneText = find.text(LocaleKeys.pageStyle_none.tr()); + expect(noneText, findsOneWidget); + await tester.tapButton(pageStyleIcon); + + /// select an emoji + const emoji = '😄'; + await tester.tapEmoji(emoji); + await tester.tapSvgButton(FlowySvgs.m_layout_s); + expect(noneText, findsNothing); + expect( + find.descendant( + of: pageStyleIcon, + matching: find.text(emoji), + ), + findsOneWidget, + ); + }); }); } diff --git a/frontend/appflowy_flutter/integration_test/mobile/document/plus_menu_test.dart b/frontend/appflowy_flutter/integration_test/mobile/document/plus_menu_test.dart index bdd84f9098..b54c543f7e 100644 --- a/frontend/appflowy_flutter/integration_test/mobile/document/plus_menu_test.dart +++ b/frontend/appflowy_flutter/integration_test/mobile/document/plus_menu_test.dart @@ -1,6 +1,9 @@ import 'dart:async'; import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/mobile/presentation/inline_actions/mobile_inline_actions_menu.dart'; +import 'package:appflowy/mobile/presentation/inline_actions/mobile_inline_actions_menu_group.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -85,5 +88,32 @@ void main() { equals(Selection.collapsed(Position(path: [2]))), ); }); + + const title = 'Test Plus Menu'; + testWidgets('show plus menu', (tester) async { + await tester.launchInAnonymousMode(); + await tester.createPageAndShowPlusMenu(title); + final menuWidget = find.byType(MobileInlineActionsMenu); + expect(menuWidget, findsOneWidget); + }); + + testWidgets('search by plus menu', (tester) async { + await tester.launchInAnonymousMode(); + await tester.createPageAndShowPlusMenu(title); + const searchText = gettingStarted; + await tester.ime.insertText(searchText); + final actionWidgets = find.byType(MobileInlineActionsWidget); + expect(actionWidgets, findsNWidgets(2)); + }); + + testWidgets('tap plus menu', (tester) async { + await tester.launchInAnonymousMode(); + await tester.createPageAndShowPlusMenu(title); + const searchText = gettingStarted; + await tester.ime.insertText(searchText); + final actionWidgets = find.byType(MobileInlineActionsWidget); + await tester.tap(actionWidgets.last); + expect(find.byType(MentionPageBlock), findsOneWidget); + }); }); } diff --git a/frontend/appflowy_flutter/integration_test/mobile/document/simple_table_test.dart b/frontend/appflowy_flutter/integration_test/mobile/document/simple_table_test.dart index fcd5494218..546baebb31 100644 --- a/frontend/appflowy_flutter/integration_test/mobile/document/simple_table_test.dart +++ b/frontend/appflowy_flutter/integration_test/mobile/document/simple_table_test.dart @@ -2,8 +2,10 @@ import 'dart:async'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/_simple_table_bottom_sheet_actions.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -243,6 +245,53 @@ void main() { expect(table.isHeaderColumnEnabled, isTrue); expect(table.isHeaderRowEnabled, isTrue); + // disable header column + { + // focus on the first cell + unawaited( + editorState.updateSelectionWithReason( + Selection.collapsed(Position(path: firstParagraphPath)), + reason: SelectionUpdateReason.uiEvent, + ), + ); + await tester.pumpAndSettle(); + + // click the row menu button + await tester.clickColumnMenuButton(0); + + final toggleButton = find.descendant( + of: find.byType(SimpleTableHeaderActionButton), + matching: find.byType(CupertinoSwitch), + ); + await tester.tapButton(toggleButton); + } + + // enable header row + { + // focus on the first cell + unawaited( + editorState.updateSelectionWithReason( + Selection.collapsed(Position(path: firstParagraphPath)), + reason: SelectionUpdateReason.uiEvent, + ), + ); + await tester.pumpAndSettle(); + + // click the row menu button + await tester.clickRowMenuButton(0); + + // enable header column + final toggleButton = find.descendant( + of: find.byType(SimpleTableHeaderActionButton), + matching: find.byType(CupertinoSwitch), + ); + await tester.tapButton(toggleButton); + } + + // check the table is updated + expect(table.isHeaderColumnEnabled, isFalse); + expect(table.isHeaderRowEnabled, isFalse); + // set to page width { final table = editorState.getNodeAtPath([0])!; @@ -371,13 +420,22 @@ void main() { // click the column menu button await tester.clickColumnMenuButton(0); - // clear content - await tester.tapButton( - find.findTextInFlowyText( - LocaleKeys.document_plugins_simpleTable_moreActions_clearContents - .tr(), - ), + final clearContents = find.findTextInFlowyText( + LocaleKeys.document_plugins_simpleTable_moreActions_clearContents + .tr(), ); + + // clear content + final scrollable = find.descendant( + of: find.byType(SimpleTableBottomSheet), + matching: find.byType(Scrollable), + ); + await tester.scrollUntilVisible( + clearContents, + 100, + scrollable: scrollable, + ); + await tester.tapButton(clearContents); await tester.cancelTableActionMenu(); // check the first cell is empty @@ -427,7 +485,7 @@ void main() { // open the plus menu and select the heading block { await tester.openPlusMenuAndClickButton( - LocaleKeys.editor_toggleHeading1ShortForm.tr(), + LocaleKeys.editor_heading1.tr(), ); // check the heading block is inserted @@ -436,5 +494,61 @@ void main() { expect(heading.level, equals(1)); } }); + + testWidgets(''' +1. insert a simple table via + menu +2. resize column +''', (tester) async { + await tester.launchInAnonymousMode(); + await tester.createNewDocumentOnMobile('simple table'); + + final editorState = tester.editor.getCurrentEditorState(); + // focus on the editor + unawaited( + editorState.updateSelectionWithReason( + Selection.collapsed(Position(path: [0])), + reason: SelectionUpdateReason.uiEvent, + ), + ); + await tester.pumpAndSettle(); + + final beforeWidth = editorState.getNodeAtPath([0, 0, 0])!.columnWidth; + + // find the first cell + { + final resizeHandle = find.byType(SimpleTableColumnResizeHandle).first; + final offset = tester.getCenter(resizeHandle); + final gesture = await tester.startGesture(offset, pointer: 7); + await tester.pumpAndSettle(); + + await gesture.moveBy(const Offset(100, 0)); + await tester.pumpAndSettle(); + + await gesture.up(); + await tester.pumpAndSettle(); + } + + // check the table is updated + final afterWidth1 = editorState.getNodeAtPath([0, 0, 0])!.columnWidth; + expect(afterWidth1, greaterThan(beforeWidth)); + + // resize back to the original width + { + final resizeHandle = find.byType(SimpleTableColumnResizeHandle).first; + final offset = tester.getCenter(resizeHandle); + final gesture = await tester.startGesture(offset, pointer: 7); + await tester.pumpAndSettle(); + + await gesture.moveBy(const Offset(-100, 0)); + await tester.pumpAndSettle(); + + await gesture.up(); + await tester.pumpAndSettle(); + } + + // check the table is updated + final afterWidth2 = editorState.getNodeAtPath([0, 0, 0])!.columnWidth; + expect(afterWidth2, equals(beforeWidth)); + }); }); } diff --git a/frontend/appflowy_flutter/integration_test/mobile/document/slash_menu_test.dart b/frontend/appflowy_flutter/integration_test/mobile/document/slash_menu_test.dart new file mode 100644 index 0000000000..11031d2b71 --- /dev/null +++ b/frontend/appflowy_flutter/integration_test/mobile/document/slash_menu_test.dart @@ -0,0 +1,84 @@ +import 'package:appflowy/mobile/presentation/selection_menu/mobile_selection_menu_item.dart'; +import 'package:appflowy/mobile/presentation/selection_menu/mobile_selection_menu_item_widget.dart'; +import 'package:appflowy/mobile/presentation/selection_menu/mobile_selection_menu_widget.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/mobile_items.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../../shared/util.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + const title = 'Test Slash Menu'; + + group('slash menu', () { + testWidgets('show slash menu', (tester) async { + await tester.launchInAnonymousMode(); + await tester.createPageAndShowSlashMenu(title); + final menuWidget = find.byType(MobileSelectionMenuWidget); + expect(menuWidget, findsOneWidget); + final items = + (menuWidget.evaluate().first.widget as MobileSelectionMenuWidget) + .items; + int i = 0; + for (final item in items) { + final localItem = mobileItems[i]; + expect(item.name, localItem.name); + i++; + } + }); + + testWidgets('search by slash menu', (tester) async { + await tester.launchInAnonymousMode(); + await tester.createPageAndShowSlashMenu(title); + const searchText = 'Heading'; + await tester.ime.insertText(searchText); + final itemWidgets = find.byType(MobileSelectionMenuItemWidget); + int number = 0; + for (final item in mobileItems) { + if (item is MobileSelectionMenuItem) { + for (final childItem in item.children) { + if (childItem.name + .toLowerCase() + .contains(searchText.toLowerCase())) { + number++; + } + } + } else { + if (item.name.toLowerCase().contains(searchText.toLowerCase())) { + number++; + } + } + } + expect(itemWidgets, findsNWidgets(number)); + }); + + testWidgets('tap to show submenu', (tester) async { + await tester.launchInAnonymousMode(); + await tester.createNewDocumentOnMobile(title); + await tester.editor.tapLineOfEditorAt(0); + final listview = find.descendant( + of: find.byType(MobileSelectionMenuWidget), + matching: find.byType(ListView), + ); + for (final item in mobileItems) { + if (item is! MobileSelectionMenuItem) continue; + await tester.editor.showSlashMenu(); + await tester.scrollUntilVisible( + find.text(item.name), + 50, + scrollable: listview, + duration: const Duration(milliseconds: 250), + ); + await tester.tap(find.text(item.name)); + final childrenLength = ((listview.evaluate().first.widget as ListView) + .childrenDelegate as SliverChildListDelegate) + .children + .length; + expect(childrenLength, item.children.length); + } + }); + }); +} diff --git a/frontend/appflowy_flutter/integration_test/mobile/document/toolbar_test.dart b/frontend/appflowy_flutter/integration_test/mobile/document/toolbar_test.dart new file mode 100644 index 0000000000..72da283cd6 --- /dev/null +++ b/frontend/appflowy_flutter/integration_test/mobile/document/toolbar_test.dart @@ -0,0 +1,117 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/mobile/presentation/editor/mobile_editor_screen.dart'; +import 'package:appflowy/mobile/presentation/mobile_bottom_navigation_bar.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/appflowy_mobile_toolbar_item.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/util.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/style_widget/text_field.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../../shared/util.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + group('toolbar menu:', () { + testWidgets('insert links', (tester) async { + await tester.launchInAnonymousMode(); + + final createPageButton = find.byKey( + BottomNavigationBarItemType.add.valueKey, + ); + await tester.tapButton(createPageButton); + expect(find.byType(MobileDocumentScreen), findsOneWidget); + + final editor = find.byType(AppFlowyEditor); + expect(editor, findsOneWidget); + final editorState = tester.editor.getCurrentEditorState(); + + /// move cursor to content + final root = editorState.document.root; + final lastNode = root.children.lastOrNull; + await editorState.updateSelectionWithReason( + Selection.collapsed(Position(path: lastNode!.path)), + ); + await tester.pumpAndSettle(); + + /// insert two lines of text + const strFirst = 'FirstLine', + strSecond = 'SecondLine', + link = 'google.com'; + await editorState.insertTextAtCurrentSelection(strFirst); + await tester.pumpAndSettle(); + await editorState.insertNewLine(); + await tester.pumpAndSettle(); + await editorState.insertTextAtCurrentSelection(strSecond); + await tester.pumpAndSettle(); + final firstLine = find.text(strFirst, findRichText: true), + secondLine = find.text(strSecond, findRichText: true); + expect(firstLine, findsOneWidget); + expect(secondLine, findsOneWidget); + + /// select the first line + await tester.longPress(firstLine); + await tester.pumpAndSettle(); + + /// find aa item and tap it + final aaItem = find.byWidgetPredicate( + (widget) => + widget is AppFlowyMobileToolbarIconItem && + widget.icon == FlowySvgs.m_toolbar_aa_m, + ); + expect(aaItem, findsOneWidget); + await tester.tapButton(aaItem); + + /// find link button and tap it + final linkButton = find.byWidgetPredicate( + (widget) => + widget is MobileToolbarMenuItemWrapper && + widget.icon == FlowySvgs.m_toolbar_link_m, + ); + expect(linkButton, findsOneWidget); + await tester.tapButton(linkButton); + + /// input the link + final linkField = find.byWidgetPredicate( + (w) => + w is FlowyTextField && + w.hintText == LocaleKeys.document_inlineLink_url_placeholder.tr(), + ); + await tester.enterText(linkField, link); + await tester.pumpAndSettle(); + + /// complete inputting + await tester.tapButton(find.text(LocaleKeys.button_done.tr())); + + /// do it again + /// select the second line + await tester.longPress(secondLine); + await tester.pumpAndSettle(); + await tester.tapButton(aaItem); + await tester.tapButton(linkButton); + await tester.enterText(linkField, link); + await tester.pumpAndSettle(); + await tester.tapButton(find.text(LocaleKeys.button_done.tr())); + + final firstNode = editorState.getNodeAtPath([0]); + final secondNode = editorState.getNodeAtPath([1]); + + Map commonDeltaJson(String insert) => { + "insert": insert, + "attributes": {"href": link}, + }; + + expect( + firstNode?.delta?.toJson(), + commonDeltaJson(strFirst), + ); + expect( + secondNode?.delta?.toJson(), + commonDeltaJson(strSecond), + ); + }); + }); +} diff --git a/frontend/appflowy_flutter/integration_test/mobile/settings/default_text_direction_test.dart b/frontend/appflowy_flutter/integration_test/mobile/settings/default_text_direction_test.dart new file mode 100644 index 0000000000..158264cad1 --- /dev/null +++ b/frontend/appflowy_flutter/integration_test/mobile/settings/default_text_direction_test.dart @@ -0,0 +1,81 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/mobile/presentation/base/app_bar/app_bar_actions.dart'; +import 'package:appflowy/mobile/presentation/home/setting/settings_popup_menu.dart'; +import 'package:appflowy/mobile/presentation/mobile_bottom_navigation_bar.dart'; +import 'package:appflowy/mobile/presentation/widgets/flowy_option_tile.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../../shared/util.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('Change default text direction', (tester) async { + await tester.launchInAnonymousMode(); + + /// tap [Setting] button + await tester.tapButton(find.byType(HomePageSettingsPopupMenu)); + await tester + .tapButton(find.text(LocaleKeys.settings_popupMenuItem_settings.tr())); + + /// tap [Default Text Direction] + await tester.tapButton( + find.text(LocaleKeys.settings_appearance_textDirection_label.tr()), + ); + + /// there are 3 items: LTR-RTL-AUTO + final bottomSheet = find.ancestor( + of: find.byType(FlowyOptionTile), + matching: find.byType(SafeArea), + ); + final items = find.descendant( + of: bottomSheet, + matching: find.byType(FlowyOptionTile), + ); + expect(items, findsNWidgets(3)); + + /// select [Auto] + await tester.tapButton(items.last); + expect( + find.text(LocaleKeys.settings_appearance_textDirection_auto.tr()), + findsOneWidget, + ); + + /// go back home + await tester.tapButton(find.byType(AppBarImmersiveBackButton)); + + /// create new page + final createPageButton = + find.byKey(BottomNavigationBarItemType.add.valueKey); + await tester.tapButton(createPageButton); + + final editorState = tester.editor.getCurrentEditorState(); + // focus on the editor + await tester.editor.tapLineOfEditorAt(0); + + const testEnglish = 'English', testArabic = 'إنجليزي'; + + /// insert [testEnglish] + await editorState.insertTextAtCurrentSelection(testEnglish); + await tester.pumpAndSettle(); + await editorState.insertNewLine(position: editorState.selection!.end); + await tester.pumpAndSettle(); + + /// insert [testArabic] + await editorState.insertTextAtCurrentSelection(testArabic); + await tester.pumpAndSettle(); + final testEnglishFinder = find.text(testEnglish, findRichText: true), + testArabicFinder = find.text(testArabic, findRichText: true); + final testEnglishRenderBox = + testEnglishFinder.evaluate().first.renderObject as RenderBox, + testArabicRenderBox = + testArabicFinder.evaluate().first.renderObject as RenderBox; + final englishPosition = testEnglishRenderBox.localToGlobal(Offset.zero), + arabicPosition = testArabicRenderBox.localToGlobal(Offset.zero); + expect(englishPosition.dx > arabicPosition.dx, true); + }); +} diff --git a/frontend/appflowy_flutter/integration_test/mobile/settings/scale_factor_test.dart b/frontend/appflowy_flutter/integration_test/mobile/settings/scale_factor_test.dart new file mode 100644 index 0000000000..908caa89d5 --- /dev/null +++ b/frontend/appflowy_flutter/integration_test/mobile/settings/scale_factor_test.dart @@ -0,0 +1,48 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/mobile/presentation/home/setting/settings_popup_menu.dart'; +import 'package:appflowy/workspace/presentation/home/hotkeys.dart'; +import 'package:appflowy/workspace/presentation/widgets/more_view_actions/widgets/font_size_stepper.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../../shared/util.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('test for change scale factor', (tester) async { + await tester.launchInAnonymousMode(); + + /// tap [Setting] button + await tester.tapButton(find.byType(HomePageSettingsPopupMenu)); + await tester + .tapButton(find.text(LocaleKeys.settings_popupMenuItem_settings.tr())); + + /// tap [Font Scale Factor] + await tester.tapButton( + find.text(LocaleKeys.settings_appearance_fontScaleFactor.tr()), + ); + + /// drag slider + final slider = find.descendant( + of: find.byType(FontSizeStepper), + matching: find.byType(Slider), + ); + await tester.slideToValue(slider, 0.8); + expect(appflowyScaleFactor, 0.8); + + await tester.slideToValue(slider, 0.9); + expect(appflowyScaleFactor, 0.9); + + await tester.slideToValue(slider, 1.0); + expect(appflowyScaleFactor, 1.0); + + await tester.slideToValue(slider, 1.1); + expect(appflowyScaleFactor, 1.1); + + await tester.slideToValue(slider, 1.2); + expect(appflowyScaleFactor, 1.2); + }); +} diff --git a/frontend/appflowy_flutter/integration_test/mobile_runner_1.dart b/frontend/appflowy_flutter/integration_test/mobile_runner_1.dart index a17fe909e7..4d92db7d25 100644 --- a/frontend/appflowy_flutter/integration_test/mobile_runner_1.dart +++ b/frontend/appflowy_flutter/integration_test/mobile_runner_1.dart @@ -3,6 +3,8 @@ import 'package:integration_test/integration_test.dart'; import 'mobile/document/document_test_runner.dart' as document_test_runner; import 'mobile/home_page/create_new_page_test.dart' as create_new_page_test; +import 'mobile/settings/default_text_direction_test.dart' + as default_text_direction_test; import 'mobile/sign_in/anonymous_sign_in_test.dart' as anonymous_sign_in_test; Future main() async { @@ -17,4 +19,5 @@ Future runIntegration1OnMobile() async { anonymous_sign_in_test.main(); create_new_page_test.main(); document_test_runner.main(); + default_text_direction_test.main(); } diff --git a/frontend/appflowy_flutter/integration_test/shared/base.dart b/frontend/appflowy_flutter/integration_test/shared/base.dart index f6baa52721..493cb4c1f0 100644 --- a/frontend/appflowy_flutter/integration_test/shared/base.dart +++ b/frontend/appflowy_flutter/integration_test/shared/base.dart @@ -13,6 +13,7 @@ import 'package:appflowy/workspace/application/settings/prelude.dart'; import 'package:flowy_infra/uuid.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:path/path.dart' as p; @@ -175,6 +176,33 @@ extension AppFlowyTestBase on WidgetTester { } } + Future tapDown( + Finder finder, { + int? pointer, + int buttons = kPrimaryButton, + PointerDeviceKind kind = PointerDeviceKind.touch, + bool pumpAndSettle = true, + int milliseconds = 500, + }) async { + final location = getCenter(finder); + final TestGesture gesture = await startGesture( + location, + pointer: pointer, + buttons: buttons, + kind: kind, + ); + await gesture.cancel(); + await gesture.down(location); + await gesture.cancel(); + if (pumpAndSettle) { + await this.pumpAndSettle( + Duration(milliseconds: milliseconds), + EnginePhase.sendSemanticsUpdate, + const Duration(seconds: 15), + ); + } + } + Future tapButtonWithName( String tr, { int milliseconds = 500, @@ -208,6 +236,25 @@ extension AppFlowyTestBase on WidgetTester { Future wait(int milliseconds) async { await pumpAndSettle(Duration(milliseconds: milliseconds)); } + + Future slideToValue( + Finder slider, + double value, { + double paddingOffset = 24.0, + }) async { + final sliderWidget = slider.evaluate().first.widget as Slider; + final range = sliderWidget.max - sliderWidget.min; + final initialRate = (value - sliderWidget.min) / range; + final totalWidth = getSize(slider).width - (2 * paddingOffset); + final zeroPoint = getTopLeft(slider) + + Offset( + paddingOffset + initialRate * totalWidth, + getSize(slider).height / 2, + ); + final calculatedOffset = value * (totalWidth / 100); + await dragFrom(zeroPoint, Offset(calculatedOffset, 0)); + await pumpAndSettle(); + } } extension AppFlowyFinderTestBase on CommonFinders { diff --git a/frontend/appflowy_flutter/integration_test/shared/common_operations.dart b/frontend/appflowy_flutter/integration_test/shared/common_operations.dart index 6ac67d0e33..d7a505d152 100644 --- a/frontend/appflowy_flutter/integration_test/shared/common_operations.dart +++ b/frontend/appflowy_flutter/integration_test/shared/common_operations.dart @@ -13,16 +13,19 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_tab import 'package:appflowy/plugins/shared/share/share_button.dart'; import 'package:appflowy/shared/feature_flags.dart'; import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; +import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart'; import 'package:appflowy/shared/text_field/text_filed_with_metric_lines.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/user/presentation/screens/screens.dart'; import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; +import 'package:appflowy/workspace/presentation/home/menu/sidebar/favorites/favorite_folder.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/footer/sidebar_footer.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/sidebar_new_page_button.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/sidebar_space_header.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/sidebar_space_menu.dart'; +import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon_popup.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart'; import 'package:appflowy/workspace/presentation/home/menu/view/draggable_view_item.dart'; @@ -47,6 +50,8 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; import 'package:universal_platform/universal_platform.dart'; import 'emoji.dart'; @@ -62,12 +67,10 @@ extension CommonOperations on WidgetTester { } else { // cloud version final anonymousButton = find.byType(SignInAnonymousButtonV2); - await tapButton(anonymousButton); + await tapButton(anonymousButton, warnIfMissed: true); } - if (Platform.isWindows) { - await pumpAndSettle(const Duration(milliseconds: 200)); - } + await pumpAndSettle(const Duration(milliseconds: 200)); } Future tapContinousAnotherWay() async { @@ -598,6 +601,23 @@ extension CommonOperations on WidgetTester { await pumpAndSettle(); } + Future reorderFavorite({ + required String fromName, + required String toName, + }) async { + final from = find.descendant( + of: find.byType(FavoriteFolder), + matching: find.text(fromName), + ), + to = find.descendant( + of: find.byType(FavoriteFolder), + matching: find.text(toName), + ); + final distanceY = getCenter(to).dy - getCenter(from).dx; + await drag(from, Offset(0, distanceY)); + await pumpAndSettle(const Duration(seconds: 1)); + } + // tap the button with [FlowySvgData] Future tapButtonWithFlowySvgData(FlowySvgData svg) async { final button = find.byWidgetPredicate( @@ -609,7 +629,7 @@ extension CommonOperations on WidgetTester { // update the page icon in the sidebar Future updatePageIconInSidebarByName({ required String name, - required String parentName, + String? parentName, required ViewLayoutPB layout, required EmojiIconData icon, }) async { @@ -651,10 +671,31 @@ extension CommonOperations on WidgetTester { await tapEmoji(icon.emoji); } else if (icon.type == FlowyIconType.icon) { await tapIcon(icon); + } else if (icon.type == FlowyIconType.custom) { + await pickImage(icon); } await pumpAndSettle(); } + Future updatePageIconInTitleBarByPasteALink({ + required String name, + required ViewLayoutPB layout, + required String iconLink, + }) async { + await openPage( + name, + layout: layout, + ); + final title = find.descendant( + of: find.byType(ViewTitleBar), + matching: find.text(name), + ); + await tapButton(title); + await tapButton(find.byType(EmojiPickerButton)); + await pasteImageLinkAsIcon(iconLink); + await pumpAndSettle(); + } + Future openNotificationHub({int tabIndex = 0}) async { final finder = find.descendant( of: find.byType(NotificationButton), @@ -898,6 +939,60 @@ extension CommonOperations on WidgetTester { await tapAt(Offset.zero); await pumpUntilNotFound(finder); } + + /// load icon list and return the first one + Future loadIcon() async { + await loadIconGroups(); + final groups = kIconGroups!; + final firstGroup = groups.first; + final firstIcon = firstGroup.icons.first; + return EmojiIconData.icon( + IconsData( + firstGroup.name, + firstIcon.name, + builtInSpaceColors.first, + ), + ); + } + + Future prepareImageIcon() async { + final imagePath = await rootBundle.load('assets/test/images/sample.jpeg'); + final tempDirectory = await getTemporaryDirectory(); + final localImagePath = p.join(tempDirectory.path, 'sample.jpeg'); + final imageFile = File(localImagePath) + ..writeAsBytesSync(imagePath.buffer.asUint8List()); + return EmojiIconData.custom(imageFile.path); + } + + Future prepareSvgIcon() async { + final imagePath = await rootBundle.load('assets/test/images/sample.svg'); + final tempDirectory = await getTemporaryDirectory(); + final localImagePath = p.join(tempDirectory.path, 'sample.svg'); + final imageFile = File(localImagePath) + ..writeAsBytesSync(imagePath.buffer.asUint8List()); + return EmojiIconData.custom(imageFile.path); + } + + /// create new page and show slash menu + Future createPageAndShowSlashMenu(String title) async { + await createNewDocumentOnMobile(title); + await editor.tapLineOfEditorAt(0); + await editor.showSlashMenu(); + } + + /// create new page and show at menu + Future createPageAndShowAtMenu(String title) async { + await createNewDocumentOnMobile(title); + await editor.tapLineOfEditorAt(0); + await editor.showAtMenu(); + } + + /// create new page and show plus menu + Future createPageAndShowPlusMenu(String title) async { + await createNewDocumentOnMobile(title); + await editor.tapLineOfEditorAt(0); + await editor.showPlusMenu(); + } } extension SettingsFinder on CommonFinders { diff --git a/frontend/appflowy_flutter/integration_test/shared/database_test_op.dart b/frontend/appflowy_flutter/integration_test/shared/database_test_op.dart index 676c96f042..970965f294 100644 --- a/frontend/appflowy_flutter/integration_test/shared/database_test_op.dart +++ b/frontend/appflowy_flutter/integration_test/shared/database_test_op.dart @@ -1,17 +1,9 @@ import 'dart:io'; -import 'package:appflowy/plugins/database/application/field/filter_entities.dart'; -import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/condition_list.dart'; -import 'package:appflowy/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_checklist_cell.dart'; -import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; -import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database/application/calculations/calculation_type_ext.dart'; +import 'package:appflowy/plugins/database/application/field/filter_entities.dart'; import 'package:appflowy/plugins/database/board/presentation/board_page.dart'; import 'package:appflowy/plugins/database/board/presentation/widgets/board_column_header.dart'; import 'package:appflowy/plugins/database/calendar/application/calendar_bloc.dart'; @@ -27,10 +19,11 @@ import 'package:appflowy/plugins/database/grid/presentation/widgets/common/type_ import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/checkbox.dart'; import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/checklist.dart'; import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/choicechip.dart'; +import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/date.dart'; +import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/condition_list.dart'; import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/option_list.dart'; import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/select_option.dart'; import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/text.dart'; -import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/date.dart'; import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/create_filter_list.dart'; import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/disclosure_button.dart'; import 'package:appflowy/plugins/database/grid/presentation/widgets/footer/grid_footer.dart'; @@ -44,6 +37,7 @@ import 'package:appflowy/plugins/database/grid/presentation/widgets/toolbar/filt import 'package:appflowy/plugins/database/grid/presentation/widgets/toolbar/sort_button.dart'; import 'package:appflowy/plugins/database/tab_bar/desktop/tab_bar_add_button.dart'; import 'package:appflowy/plugins/database/tab_bar/desktop/tab_bar_header.dart'; +import 'package:appflowy/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_checklist_cell.dart'; import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/checkbox.dart'; import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/checklist.dart'; import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/date.dart'; @@ -76,6 +70,8 @@ import 'package:appflowy/plugins/database/widgets/setting/database_setting_actio import 'package:appflowy/plugins/database/widgets/setting/database_settings_list.dart'; import 'package:appflowy/plugins/database/widgets/setting/setting_button.dart'; import 'package:appflowy/plugins/database/widgets/setting/setting_property_list.dart'; +import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; +import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart'; import 'package:appflowy/util/field_type_extension.dart'; import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/clear_date_button.dart'; import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/date_type_option_button.dart'; @@ -90,6 +86,9 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/text_input.dart'; import 'package:flowy_infra_ui/widget/buttons/primary_button.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:path/path.dart' as p; // Non-exported member of the table_calendar library @@ -943,6 +942,31 @@ extension AppFlowyDatabaseTest on WidgetTester { await pumpAndSettle(const Duration(milliseconds: 200)); } + Future changeFieldWidth(String fieldName, double width) async { + final field = find.byWidgetPredicate( + (widget) => widget is GridFieldCell && widget.fieldInfo.name == fieldName, + ); + await hoverOnWidget( + field, + onHover: () async { + final dragHandle = find.descendant( + of: field, + matching: find.byType(DragToExpandLine), + ); + await drag(dragHandle, Offset(width - getSize(field).width, 0)); + await pumpAndSettle(const Duration(milliseconds: 200)); + }, + ); + } + + double getFieldWidth(String fieldName) { + final field = find.byWidgetPredicate( + (widget) => widget is GridFieldCell && widget.fieldInfo.name == fieldName, + ); + + return getSize(field).width; + } + Future findDateEditor(dynamic matcher) async { final finder = find.byType(DateCellEditor); expect(finder, matcher); @@ -1464,6 +1488,7 @@ extension AppFlowyDatabaseTest on WidgetTester { ); await tapButton(button); + await tapButtonWithName(LocaleKeys.button_delete.tr()); } Future dragDropRescheduleCalendarEvent() async { @@ -1571,7 +1596,7 @@ extension AppFlowyDatabaseTest on WidgetTester { of: textField, matching: find.byWidgetPredicate( (widget) => - widget is FlowySvg && widget.svg == FlowySvgs.close_filled_m, + widget is FlowySvg && widget.svg == FlowySvgs.close_filled_s, ), ), ); diff --git a/frontend/appflowy_flutter/integration_test/shared/document_test_operations.dart b/frontend/appflowy_flutter/integration_test/shared/document_test_operations.dart index 491ac9432c..398a3f9657 100644 --- a/frontend/appflowy_flutter/integration_test/shared/document_test_operations.dart +++ b/frontend/appflowy_flutter/integration_test/shared/document_test_operations.dart @@ -307,9 +307,11 @@ class EditorOperations { Future openTurnIntoMenu(Path path) async { await hoverAndClickOptionMenuButton(path); await tester.tapButton( - find.findTextInFlowyText( - LocaleKeys.document_plugins_optionAction_turnInto.tr(), - ), + find + .findTextInFlowyText( + LocaleKeys.document_plugins_optionAction_turnInto.tr(), + ) + .first, ); await tester.pumpUntilFound(find.byType(TurnIntoOptionMenu)); } diff --git a/frontend/appflowy_flutter/integration_test/shared/emoji.dart b/frontend/appflowy_flutter/integration_test/shared/emoji.dart index e6c756edb1..cccd00a3f6 100644 --- a/frontend/appflowy_flutter/integration_test/shared/emoji.dart +++ b/frontend/appflowy_flutter/integration_test/shared/emoji.dart @@ -1,16 +1,24 @@ import 'dart:convert'; +import 'dart:io'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart'; import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; import 'package:appflowy/shared/icon_emoji_picker/icon_color_picker.dart'; import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart'; +import 'package:appflowy/shared/icon_emoji_picker/icon_uploader.dart'; import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; +import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon_popup.dart'; +import 'package:desktop_drop/desktop_drop.dart'; +import 'package:flowy_infra_ui/style_widget/primary_rounded_button.dart'; import 'package:flowy_svg/flowy_svg.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_emoji_mart/flutter_emoji_mart.dart'; import 'package:flutter_test/flutter_test.dart'; import 'base.dart'; +import 'common_operations.dart'; extension EmojiTestExtension on WidgetTester { Future tapEmoji(String emoji) async { @@ -21,7 +29,7 @@ extension EmojiTestExtension on WidgetTester { await tapButton(emojiWidget); } - Future tapIcon(EmojiIconData icon) async { + Future tapIcon(EmojiIconData icon, {bool enableColor = true}) async { final iconsData = IconsData.fromJson(jsonDecode(icon.emoji)); final pickTab = find.byType(PickerTab); expect(pickTab, findsOneWidget); @@ -31,35 +39,106 @@ extension EmojiTestExtension on WidgetTester { matching: find.text(PickerTabType.icon.tr), ); expect(iconTab, findsOneWidget); - expect(find.byType(FlowyIconPicker), findsNothing); - await tap(iconTab); - await pumpAndSettle(); - expect(find.byType(FlowyIconPicker), findsOneWidget); + await tapButton(iconTab); final selectedSvg = find.descendant( of: find.byType(FlowyIconPicker), matching: find.byWidgetPredicate( - (w) => w is FlowySvg && w.svgString == iconsData.iconContent, + (w) => w is FlowySvg && w.svgString == iconsData.svgString, ), ); - expect(find.byType(IconColorPicker), findsNothing); - await tapButton(selectedSvg); - final colorPicker = find.byType(IconColorPicker); - expect(colorPicker, findsOneWidget); - final selectedColor = find.descendant( - of: colorPicker, - matching: find.byWidgetPredicate((w) { - if (w is Container) { - final d = w.decoration; - if (d is ShapeDecoration) { - if (d.color == - Color(int.parse(iconsData.color ?? builtInSpaceColors.first))) { - return true; + + await tapButton(selectedSvg.first); + if (enableColor) { + final colorPicker = find.byType(IconColorPicker); + expect(colorPicker, findsOneWidget); + final selectedColor = find.descendant( + of: colorPicker, + matching: find.byWidgetPredicate((w) { + if (w is Container) { + final d = w.decoration; + if (d is ShapeDecoration) { + if (d.color == + Color( + int.parse(iconsData.color ?? builtInSpaceColors.first), + )) { + return true; + } } } - } - return false; - }), + return false; + }), + ); + await tapButton(selectedColor); + } + } + + Future pickImage(EmojiIconData icon) async { + final pickTab = find.byType(PickerTab); + expect(pickTab, findsOneWidget); + await pumpAndSettle(); + + /// switch to custom tab + final iconTab = find.descendant( + of: pickTab, + matching: find.text(PickerTabType.custom.tr), ); - await tapButton(selectedColor); + expect(iconTab, findsOneWidget); + await tapButton(iconTab); + + /// mock for dragging image + final dropTarget = find.descendant( + of: find.byType(IconUploader), + matching: find.byType(DropTarget), + ); + expect(dropTarget, findsOneWidget); + final dropTargetWidget = dropTarget.evaluate().first.widget as DropTarget; + dropTargetWidget.onDragDone?.call( + DropDoneDetails( + files: [DropItemFile(icon.emoji)], + localPosition: Offset.zero, + globalPosition: Offset.zero, + ), + ); + await pumpAndSettle(const Duration(seconds: 3)); + + /// confirm to upload + final confirmButton = find.descendant( + of: find.byType(IconUploader), + matching: find.byType(PrimaryRoundedButton), + ); + await tapButton(confirmButton); + } + + Future pasteImageLinkAsIcon(String link) async { + final pickTab = find.byType(PickerTab); + expect(pickTab, findsOneWidget); + await pumpAndSettle(); + + /// switch to custom tab + final iconTab = find.descendant( + of: pickTab, + matching: find.text(PickerTabType.custom.tr), + ); + expect(iconTab, findsOneWidget); + await tapButton(iconTab); + + // mock the clipboard + await getIt() + .setData(ClipboardServiceData(plainText: link)); + + // paste the link + await simulateKeyEvent( + LogicalKeyboardKey.keyV, + isControlPressed: Platform.isLinux || Platform.isWindows, + isMetaPressed: Platform.isMacOS, + ); + await pumpAndSettle(const Duration(seconds: 5)); + + /// confirm to upload + final confirmButton = find.descendant( + of: find.byType(IconUploader), + matching: find.byType(PrimaryRoundedButton), + ); + await tapButton(confirmButton); } } diff --git a/frontend/appflowy_flutter/integration_test/shared/expectation.dart b/frontend/appflowy_flutter/integration_test/shared/expectation.dart index cd2f1e3133..3b9ef0d75c 100644 --- a/frontend/appflowy_flutter/integration_test/shared/expectation.dart +++ b/frontend/appflowy_flutter/integration_test/shared/expectation.dart @@ -7,6 +7,8 @@ import 'package:appflowy/plugins/database/widgets/row/row_detail.dart'; import 'package:appflowy/plugins/document/presentation/banner.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/header/document_cover_widget.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart'; +import 'package:appflowy/shared/appflowy_network_image.dart'; +import 'package:appflowy/shared/appflowy_network_svg.dart'; import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart'; import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart'; @@ -21,6 +23,7 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_svg/flowy_svg.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:string_validator/string_validator.dart'; import 'package:universal_platform/universal_platform.dart'; import 'util.dart'; @@ -245,10 +248,27 @@ extension Expectation on WidgetTester { final icon = find.descendant( of: pageName, matching: find.byWidgetPredicate( - (w) => w is FlowySvg && w.svgString == iconsData.iconContent, + (w) => w is FlowySvg && w.svgString == iconsData.svgString, ), ); expect(icon, findsOneWidget); + } else if (type == FlowyIconType.custom) { + final isSvg = data.emoji.endsWith('.svg'); + if (isURL(data.emoji)) { + final image = find.descendant( + of: pageName, + matching: isSvg + ? find.byType(FlowyNetworkSvg) + : find.byType(FlowyNetworkImage), + ); + expect(image, findsOneWidget); + } else { + final image = find.descendant( + of: pageName, + matching: isSvg ? find.byType(SvgPicture) : find.byType(Image), + ); + expect(image, findsOneWidget); + } } } @@ -269,10 +289,34 @@ extension Expectation on WidgetTester { final icon = find.descendant( of: find.byType(ViewTitleBar), matching: find.byWidgetPredicate( - (w) => w is FlowySvg && w.svgString == iconsData.iconContent, + (w) => w is FlowySvg && w.svgString == iconsData.svgString, ), ); expect(icon, findsOneWidget); + } else if (type == FlowyIconType.custom) { + final isSvg = data.emoji.endsWith('.svg'); + if (isURL(data.emoji)) { + final image = find.descendant( + of: find.byType(ViewTitleBar), + matching: isSvg + ? find.byType(FlowyNetworkSvg) + : find.byType(FlowyNetworkImage), + ); + expect(image, findsOneWidget); + } else { + final image = find.descendant( + of: find.byType(ViewTitleBar), + matching: isSvg + ? find.byWidgetPredicate((w) { + if (w is! SvgPicture) return false; + final loader = w.bytesLoader; + if (loader is! SvgFileLoader) return false; + return loader.file.path.endsWith('.svg'); + }) + : find.byType(Image), + ); + expect(image, findsOneWidget); + } } } diff --git a/frontend/appflowy_flutter/integration_test/shared/mock/mock_openai_repository.dart b/frontend/appflowy_flutter/integration_test/shared/mock/mock_openai_repository.dart deleted file mode 100644 index 7201bd89ca..0000000000 --- a/frontend/appflowy_flutter/integration_test/shared/mock/mock_openai_repository.dart +++ /dev/null @@ -1,81 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/error.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/openai_client.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/text_completion.dart'; -import 'package:http/http.dart' as http; -import 'package:mocktail/mocktail.dart'; - -class MyMockClient extends Mock implements http.Client { - @override - Future send(http.BaseRequest request) async { - final requestType = request.method; - final requestUri = request.url; - - if (requestType == 'POST' && - requestUri == OpenAIRequestType.textCompletion.uri) { - final responseHeaders = { - 'content-type': 'text/event-stream', - }; - final responseBody = Stream.fromIterable([ - utf8.encode( - '{ "choices": [{"text": "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula ", "index": 0, "logprobs": null, "finish_reason": null}]}', - ), - utf8.encode('\n'), - utf8.encode('[DONE]'), - ]); - - // Return a mocked response with the expected data - return http.StreamedResponse(responseBody, 200, headers: responseHeaders); - } - - // Return an error response for any other request - return http.StreamedResponse(const Stream.empty(), 404); - } -} - -class MockOpenAIRepository extends HttpOpenAIRepository { - MockOpenAIRepository() : super(apiKey: 'dummyKey', client: MyMockClient()); - - @override - Future getStreamedCompletions({ - required String prompt, - required Future Function() onStart, - required Future Function(TextCompletionResponse response) onProcess, - required Future Function() onEnd, - required void Function(AIError error) onError, - String? suffix, - int maxTokens = 2048, - double temperature = 0.3, - bool useAction = false, - }) async { - final request = http.Request('POST', OpenAIRequestType.textCompletion.uri); - final response = await client.send(request); - - String previousSyntax = ''; - if (response.statusCode == 200) { - await for (final chunk in response.stream - .transform(const Utf8Decoder()) - .transform(const LineSplitter())) { - await onStart(); - final data = chunk.trim().split('data: '); - if (data[0] != '[DONE]') { - final response = TextCompletionResponse.fromJson( - json.decode(data[0]), - ); - if (response.choices.isNotEmpty) { - final text = response.choices.first.text; - if (text == previousSyntax && text == '\n') { - continue; - } - await onProcess(response); - previousSyntax = response.choices.first.text; - } - } else { - await onEnd(); - } - } - } - } -} diff --git a/frontend/appflowy_flutter/integration_test/shared/settings.dart b/frontend/appflowy_flutter/integration_test/shared/settings.dart index aade7bb4c9..bfc5efedde 100644 --- a/frontend/appflowy_flutter/integration_test/shared/settings.dart +++ b/frontend/appflowy_flutter/integration_test/shared/settings.dart @@ -79,7 +79,7 @@ extension AppFlowySettings on WidgetTester { // Enable editing username final editUsernameFinder = find.descendant( of: find.byType(AccountUserProfile), - matching: find.byFlowySvg(FlowySvgs.edit_s), + matching: find.byFlowySvg(FlowySvgs.toolbar_link_edit_m), ); await tap(editUsernameFinder, warnIfMissed: false); await pumpAndSettle(); diff --git a/frontend/appflowy_flutter/ios/Podfile.lock b/frontend/appflowy_flutter/ios/Podfile.lock index ac6f338698..4b7ed5d639 100644 --- a/frontend/appflowy_flutter/ios/Podfile.lock +++ b/frontend/appflowy_flutter/ios/Podfile.lock @@ -1,5 +1,5 @@ PODS: - - app_links (0.0.1): + - app_links (0.0.2): - Flutter - appflowy_backend (0.0.1): - Flutter @@ -66,6 +66,8 @@ PODS: - permission_handler_apple (9.3.0): - Flutter - ReachabilitySwift (5.0.0) + - saver_gallery (0.0.1): + - Flutter - SDWebImage (5.14.2): - SDWebImage/Core (= 5.14.2) - SDWebImage/Core (5.14.2) @@ -79,7 +81,7 @@ PODS: - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS - - sqflite (0.0.3): + - sqflite_darwin (0.0.4): - Flutter - FlutterMacOS - super_native_extensions (0.0.1): @@ -90,6 +92,7 @@ PODS: - Flutter - webview_flutter_wkwebview (0.0.1): - Flutter + - FlutterMacOS DEPENDENCIES: - app_links (from `.symlinks/plugins/app_links/ios`) @@ -108,13 +111,14 @@ DEPENDENCIES: - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) + - saver_gallery (from `.symlinks/plugins/saver_gallery/ios`) - sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - - sqflite (from `.symlinks/plugins/sqflite/darwin`) + - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) - super_native_extensions (from `.symlinks/plugins/super_native_extensions/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`) + - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/darwin`) SPEC REPOS: trunk: @@ -159,53 +163,56 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/path_provider_foundation/darwin" permission_handler_apple: :path: ".symlinks/plugins/permission_handler_apple/ios" + saver_gallery: + :path: ".symlinks/plugins/saver_gallery/ios" sentry_flutter: :path: ".symlinks/plugins/sentry_flutter/ios" share_plus: :path: ".symlinks/plugins/share_plus/ios" shared_preferences_foundation: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" - sqflite: - :path: ".symlinks/plugins/sqflite/darwin" + sqflite_darwin: + :path: ".symlinks/plugins/sqflite_darwin/darwin" super_native_extensions: :path: ".symlinks/plugins/super_native_extensions/ios" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" webview_flutter_wkwebview: - :path: ".symlinks/plugins/webview_flutter_wkwebview/ios" + :path: ".symlinks/plugins/webview_flutter_wkwebview/darwin" SPEC CHECKSUMS: - app_links: e70ca16b4b0f88253b3b3660200d4a10b4ea9795 - appflowy_backend: 144c20d8bfb298c4e10fa3fa6701a9f41bf98b88 - connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d - device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d + app_links: 3da4c36b46cac3bf24eb897f1a6ce80bda109874 + appflowy_backend: 78f6a053f756e6bc29bcc5a2106cbe77b756e97a + connectivity_plus: 481668c94744c30c53b8895afb39159d1e619bdf + device_info_plus: 71ffc6ab7634ade6267c7a93088ed7e4f74e5896 DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 - file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655 - flowy_infra_ui: 0455e1fa8c51885aa1437848e361e99419f34ebc + file_picker: 9b3292d7c8bc68c8a7bf8eb78f730e49c8efc517 + flowy_infra_ui: 931b73a18b54a392ab6152eebe29a63a30751f53 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c - image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 - integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4 - irondash_engine_context: 3458bf979b90d616ffb8ae03a150bafe2e860cc9 - keyboard_height_plugin: 43fa8bba20fd5c4fdeed5076466b8b9d43cc6b86 - open_filex: 6e26e659846ec990262224a12ef1c528bb4edbe4 - package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 + fluttertoast: 76fea30fcf04176325f6864c87306927bd7d2038 + image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a + integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e + irondash_engine_context: 8e58ca8e0212ee9d1c7dc6a42121849986c88486 + keyboard_height_plugin: ef70a8181b29f27670e9e2450814ca6b6dc05b05 + open_filex: 432f3cd11432da3e39f47fcc0df2b1603854eff1 + package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 + saver_gallery: af2d0c762dafda254e0ad025ef0dabd6506cd490 SDWebImage: b9a731e1d6307f44ca703b3976d18c24ca561e84 Sentry: 1fe34e9c2cbba1e347623610d26db121dcb569f1 - sentry_flutter: a39c2a2d67d5e5b9cb0b94a4985c76dd5b3fc737 - share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad - shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 - sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec - super_native_extensions: 4916b3c627a9c7fffdc48a23a9eca0b1ac228fa7 + sentry_flutter: e24b397f9a61fa5bbefd8279c3b2242ca86faa90 + share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 + super_native_extensions: b763c02dc3a8fd078389f410bf15149179020cb4 SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780 Toast: 91b396c56ee72a5790816f40d3a94dd357abc196 - url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe - webview_flutter_wkwebview: 2a23822e9039b7b1bc52e5add778e5d89ad488d1 + url_launcher_ios: 694010445543906933d732453a59da0a173ae33d + webview_flutter_wkwebview: 44d4dee7d7056d5ad185d25b38404436d56c547c PODFILE CHECKSUM: d0d9b4ff572d8695c38eb3f9b490f55cdfc57eca -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/frontend/appflowy_flutter/ios/Runner/AppDelegate.swift b/frontend/appflowy_flutter/ios/Runner/AppDelegate.swift index 70693e4a8c..b636303481 100644 --- a/frontend/appflowy_flutter/ios/Runner/AppDelegate.swift +++ b/frontend/appflowy_flutter/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import UIKit import Flutter -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/frontend/appflowy_flutter/ios/Runner/Info.plist b/frontend/appflowy_flutter/ios/Runner/Info.plist index d1afb2a9db..5d6a52bd2e 100644 --- a/frontend/appflowy_flutter/ios/Runner/Info.plist +++ b/frontend/appflowy_flutter/ios/Runner/Info.plist @@ -1,75 +1,78 @@ - - CADisableMinimumFrameDurationOnPhone - - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleLocalizations - - en - - CFBundleName - AppFlowy - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleSignature - ???? - CFBundleURLTypes - - - CFBundleURLName - - CFBundleURLSchemes - - appflowy-flutter - - - - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - FLTEnableImpeller - - LSRequiresIPhoneOS - - NSAppTransportSecurity - NSAllowsArbitraryLoads - + CADisableMinimumFrameDurationOnPhone + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLocalizations + + en + + CFBundleName + AppFlowy + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleURLName + + CFBundleURLSchemes + + appflowy-flutter + + + + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + FLTEnableImpeller + + LSRequiresIPhoneOS + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + NSPhotoLibraryUsageDescription + AppFlowy needs access to your photos to let you add images to your documents + NSPhotoLibraryAddUsageDescription + AppFlowy needs access to your photos to let you add images to your photo library + UIApplicationSupportsIndirectInputEvents + + NSCameraUsageDescription + AppFlowy needs access to your camera to let you add images to your documents from + camera + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + + UISupportsDocumentBrowser + + UIViewControllerBasedStatusBarAppearance + - NSPhotoLibraryUsageDescription - AppFlowy needs access to your photos to let you add images to your documents - UIApplicationSupportsIndirectInputEvents - - NSCameraUsageDescription - AppFlowy needs access to your camera to let you add images to your documents from camera - UILaunchStoryboardName - LaunchScreen - UIMainStoryboardFile - Main - UISupportedInterfaceOrientations - - UIInterfaceOrientationPortrait - - UISupportedInterfaceOrientations~ipad - - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight - UIInterfaceOrientationPortrait - UIInterfaceOrientationPortraitUpsideDown - - UISupportsDocumentBrowser - - UIViewControllerBasedStatusBarAppearance - - - + \ No newline at end of file diff --git a/frontend/appflowy_flutter/lib/ai/ai.dart b/frontend/appflowy_flutter/lib/ai/ai.dart new file mode 100644 index 0000000000..9bfeeb4e00 --- /dev/null +++ b/frontend/appflowy_flutter/lib/ai/ai.dart @@ -0,0 +1,19 @@ +export 'service/ai_entities.dart'; +export 'service/ai_prompt_input_bloc.dart'; +export 'service/appflowy_ai_service.dart'; +export 'service/error.dart'; +export 'service/ai_model_state_notifier.dart'; +export 'service/select_model_bloc.dart'; +export 'widgets/loading_indicator.dart'; +export 'widgets/prompt_input/action_buttons.dart'; +export 'widgets/prompt_input/desktop_prompt_text_field.dart'; +export 'widgets/prompt_input/file_attachment_list.dart'; +export 'widgets/prompt_input/layout_define.dart'; +export 'widgets/prompt_input/mention_page_bottom_sheet.dart'; +export 'widgets/prompt_input/mention_page_menu.dart'; +export 'widgets/prompt_input/mentioned_page_text_span.dart'; +export 'widgets/prompt_input/predefined_format_buttons.dart'; +export 'widgets/prompt_input/select_sources_bottom_sheet.dart'; +export 'widgets/prompt_input/select_sources_menu.dart'; +export 'widgets/prompt_input/select_model_menu.dart'; +export 'widgets/prompt_input/send_button.dart'; diff --git a/frontend/appflowy_flutter/lib/ai/service/ai_entities.dart b/frontend/appflowy_flutter/lib/ai/service/ai_entities.dart new file mode 100644 index 0000000000..b08fadb7f8 --- /dev/null +++ b/frontend/appflowy_flutter/lib/ai/service/ai_entities.dart @@ -0,0 +1,107 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy_backend/protobuf/flowy-ai/protobuf.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:equatable/equatable.dart'; + +class AIStreamEventPrefix { + static const data = 'data:'; + static const error = 'error:'; + static const metadata = 'metadata:'; + static const start = 'start:'; + static const finish = 'finish:'; + static const comment = 'comment:'; + static const aiResponseLimit = 'AI_RESPONSE_LIMIT'; + static const aiImageResponseLimit = 'AI_IMAGE_RESPONSE_LIMIT'; + static const aiMaxRequired = 'AI_MAX_REQUIRED:'; + static const localAINotReady = 'LOCAL_AI_NOT_READY'; + static const localAIDisabled = 'LOCAL_AI_DISABLED'; +} + +enum AiType { + cloud, + local; + + bool get isCloud => this == cloud; + bool get isLocal => this == local; +} + +class PredefinedFormat extends Equatable { + const PredefinedFormat({ + required this.imageFormat, + required this.textFormat, + }); + + final ImageFormat imageFormat; + final TextFormat? textFormat; + + PredefinedFormatPB toPB() { + return PredefinedFormatPB( + imageFormat: switch (imageFormat) { + ImageFormat.text => ResponseImageFormatPB.TextOnly, + ImageFormat.image => ResponseImageFormatPB.ImageOnly, + ImageFormat.textAndImage => ResponseImageFormatPB.TextAndImage, + }, + textFormat: switch (textFormat) { + TextFormat.paragraph => ResponseTextFormatPB.Paragraph, + TextFormat.bulletList => ResponseTextFormatPB.BulletedList, + TextFormat.numberedList => ResponseTextFormatPB.NumberedList, + TextFormat.table => ResponseTextFormatPB.Table, + _ => null, + }, + ); + } + + @override + List get props => [imageFormat, textFormat]; +} + +enum ImageFormat { + text, + image, + textAndImage; + + bool get hasText => this == text || this == textAndImage; + + FlowySvgData get icon { + return switch (this) { + ImageFormat.text => FlowySvgs.ai_text_s, + ImageFormat.image => FlowySvgs.ai_image_s, + ImageFormat.textAndImage => FlowySvgs.ai_text_image_s, + }; + } + + String get i18n { + return switch (this) { + ImageFormat.text => LocaleKeys.chat_changeFormat_textOnly.tr(), + ImageFormat.image => LocaleKeys.chat_changeFormat_imageOnly.tr(), + ImageFormat.textAndImage => + LocaleKeys.chat_changeFormat_textAndImage.tr(), + }; + } +} + +enum TextFormat { + paragraph, + bulletList, + numberedList, + table; + + FlowySvgData get icon { + return switch (this) { + TextFormat.paragraph => FlowySvgs.ai_paragraph_s, + TextFormat.bulletList => FlowySvgs.ai_list_s, + TextFormat.numberedList => FlowySvgs.ai_number_list_s, + TextFormat.table => FlowySvgs.ai_table_s, + }; + } + + String get i18n { + return switch (this) { + TextFormat.paragraph => LocaleKeys.chat_changeFormat_text.tr(), + TextFormat.bulletList => LocaleKeys.chat_changeFormat_bullet.tr(), + TextFormat.numberedList => LocaleKeys.chat_changeFormat_number.tr(), + TextFormat.table => LocaleKeys.chat_changeFormat_table.tr(), + }; + } +} diff --git a/frontend/appflowy_flutter/lib/ai/service/ai_model_state_notifier.dart b/frontend/appflowy_flutter/lib/ai/service/ai_model_state_notifier.dart new file mode 100644 index 0000000000..0bcc41da9b --- /dev/null +++ b/frontend/appflowy_flutter/lib/ai/service/ai_model_state_notifier.dart @@ -0,0 +1,181 @@ +import 'package:appflowy/ai/ai.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/ai_chat/application/ai_model_switch_listener.dart'; +import 'package:appflowy/workspace/application/settings/ai/local_llm_listener.dart'; +import 'package:appflowy_backend/dispatch/dispatch.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart'; +import 'package:appflowy_result/appflowy_result.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:protobuf/protobuf.dart'; +import 'package:universal_platform/universal_platform.dart'; + +typedef OnModelStateChangedCallback = void Function(AiType, bool, String); +typedef OnAvailableModelsChangedCallback = void Function( + List, + AIModelPB?, +); + +class AIModelStateNotifier { + AIModelStateNotifier({required this.objectId}) + : _localAIListener = + UniversalPlatform.isDesktop ? LocalAIStateListener() : null, + _aiModelSwitchListener = AIModelSwitchListener(objectId: objectId) { + _startListening(); + _init(); + } + + final String objectId; + final LocalAIStateListener? _localAIListener; + final AIModelSwitchListener _aiModelSwitchListener; + LocalAIPB? _localAIState; + AvailableModelsPB? _availableModels; + + // callbacks + final List _stateChangedCallbacks = []; + final List + _availableModelsChangedCallbacks = []; + + void _startListening() { + if (UniversalPlatform.isDesktop) { + _localAIListener?.start( + stateCallback: (state) async { + _localAIState = state; + _notifyStateChanged(); + + if (state.state == RunningStatePB.Running || + state.state == RunningStatePB.Stopped) { + await _loadAvailableModels(); + _notifyAvailableModelsChanged(); + } + }, + ); + } + + _aiModelSwitchListener.start( + onUpdateSelectedModel: (model) async { + final updatedModels = _availableModels?.deepCopy() + ?..selectedModel = model; + _availableModels = updatedModels; + _notifyAvailableModelsChanged(); + + if (model.isLocal && UniversalPlatform.isDesktop) { + await _loadLocalAiState(); + } + _notifyStateChanged(); + }, + ); + } + + void _init() async { + await Future.wait([_loadLocalAiState(), _loadAvailableModels()]); + _notifyStateChanged(); + _notifyAvailableModelsChanged(); + } + + void addListener({ + OnModelStateChangedCallback? onStateChanged, + OnAvailableModelsChangedCallback? onAvailableModelsChanged, + }) { + if (onStateChanged != null) { + _stateChangedCallbacks.add(onStateChanged); + } + if (onAvailableModelsChanged != null) { + _availableModelsChangedCallbacks.add(onAvailableModelsChanged); + } + } + + void removeListener({ + OnModelStateChangedCallback? onStateChanged, + OnAvailableModelsChangedCallback? onAvailableModelsChanged, + }) { + if (onStateChanged != null) { + _stateChangedCallbacks.remove(onStateChanged); + } + if (onAvailableModelsChanged != null) { + _availableModelsChangedCallbacks.remove(onAvailableModelsChanged); + } + } + + Future dispose() async { + _stateChangedCallbacks.clear(); + _availableModelsChangedCallbacks.clear(); + await _localAIListener?.stop(); + await _aiModelSwitchListener.stop(); + } + + (AiType, String, bool) getState() { + if (UniversalPlatform.isMobile) { + return (AiType.cloud, LocaleKeys.chat_inputMessageHint.tr(), true); + } + + final availableModels = _availableModels; + final localAiState = _localAIState; + + if (availableModels == null) { + return (AiType.cloud, LocaleKeys.chat_inputMessageHint.tr(), true); + } + if (localAiState == null) { + Log.warn("Cannot get local AI state"); + return (AiType.cloud, LocaleKeys.chat_inputMessageHint.tr(), true); + } + + if (!availableModels.selectedModel.isLocal) { + return (AiType.cloud, LocaleKeys.chat_inputMessageHint.tr(), true); + } + + final editable = localAiState.state == RunningStatePB.Running; + final hintText = editable + ? LocaleKeys.chat_inputLocalAIMessageHint.tr() + : LocaleKeys.settings_aiPage_keys_localAIInitializing.tr(); + + return (AiType.local, hintText, editable); + } + + (List, AIModelPB?) getAvailableModels() { + final availableModels = _availableModels; + if (availableModels == null) { + return ([], null); + } + return (availableModels.models, availableModels.selectedModel); + } + + void _notifyAvailableModelsChanged() { + final (models, selectedModel) = getAvailableModels(); + for (final callback in _availableModelsChangedCallbacks) { + callback(models, selectedModel); + } + } + + void _notifyStateChanged() { + final (type, hintText, isEditable) = getState(); + for (final callback in _stateChangedCallbacks) { + callback(type, isEditable, hintText); + } + } + + Future _loadAvailableModels() { + final payload = AvailableModelsQueryPB(source: objectId); + return AIEventGetAvailableModels(payload).send().fold( + (models) => _availableModels = models, + (err) => Log.error("Failed to get available models: $err"), + ); + } + + Future _loadLocalAiState() { + return AIEventGetLocalAIState().send().fold( + (localAIState) => _localAIState = localAIState, + (error) => Log.error("Failed to get local AI state: $error"), + ); + } +} + +extension AiModelExtension on AIModelPB { + bool get isDefault { + return name == "Auto"; + } + + String get i18n { + return isDefault ? LocaleKeys.chat_switchModel_autoModel.tr() : name; + } +} diff --git a/frontend/appflowy_flutter/lib/ai/service/ai_prompt_input_bloc.dart b/frontend/appflowy_flutter/lib/ai/service/ai_prompt_input_bloc.dart new file mode 100644 index 0000000000..95854ab047 --- /dev/null +++ b/frontend/appflowy_flutter/lib/ai/service/ai_prompt_input_bloc.dart @@ -0,0 +1,180 @@ +import 'dart:async'; + +import 'package:appflowy/ai/service/ai_model_state_notifier.dart'; +import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +import 'ai_entities.dart'; + +part 'ai_prompt_input_bloc.freezed.dart'; + +class AIPromptInputBloc extends Bloc { + AIPromptInputBloc({ + required String objectId, + required PredefinedFormat? predefinedFormat, + }) : aiModelStateNotifier = AIModelStateNotifier(objectId: objectId), + super(AIPromptInputState.initial(predefinedFormat)) { + _dispatch(); + _startListening(); + _init(); + } + + final AIModelStateNotifier aiModelStateNotifier; + + @override + Future close() async { + await aiModelStateNotifier.dispose(); + return super.close(); + } + + void _dispatch() { + on( + (event, emit) { + event.when( + updateAIState: (aiType, editable, hintText) { + emit( + state.copyWith( + aiType: aiType, + editable: editable, + hintText: hintText, + ), + ); + }, + toggleShowPredefinedFormat: () { + final showPredefinedFormats = !state.showPredefinedFormats; + final predefinedFormat = + showPredefinedFormats && state.predefinedFormat == null + ? PredefinedFormat( + imageFormat: ImageFormat.text, + textFormat: TextFormat.paragraph, + ) + : null; + emit( + state.copyWith( + showPredefinedFormats: showPredefinedFormats, + predefinedFormat: predefinedFormat, + ), + ); + }, + updatePredefinedFormat: (format) { + if (!state.showPredefinedFormats) { + return; + } + emit(state.copyWith(predefinedFormat: format)); + }, + attachFile: (filePath, fileName) { + final newFile = ChatFile.fromFilePath(filePath); + if (newFile != null) { + emit( + state.copyWith( + attachedFiles: [...state.attachedFiles, newFile], + ), + ); + } + }, + removeFile: (file) { + final files = [...state.attachedFiles]; + files.remove(file); + emit( + state.copyWith( + attachedFiles: files, + ), + ); + }, + updateMentionedViews: (views) { + emit( + state.copyWith( + mentionedPages: views, + ), + ); + }, + clearMetadata: () { + emit( + state.copyWith( + attachedFiles: [], + mentionedPages: [], + ), + ); + }, + ); + }, + ); + } + + void _startListening() { + aiModelStateNotifier.addListener( + onStateChanged: (aiType, editable, hintText) { + add(AIPromptInputEvent.updateAIState(aiType, editable, hintText)); + }, + ); + } + + void _init() { + final (aiType, hintText, isEditable) = aiModelStateNotifier.getState(); + add(AIPromptInputEvent.updateAIState(aiType, isEditable, hintText)); + } + + Map consumeMetadata() { + final metadata = { + for (final file in state.attachedFiles) file.filePath: file, + for (final page in state.mentionedPages) page.id: page, + }; + + if (metadata.isNotEmpty && !isClosed) { + add(const AIPromptInputEvent.clearMetadata()); + } + + return metadata; + } +} + +@freezed +class AIPromptInputEvent with _$AIPromptInputEvent { + const factory AIPromptInputEvent.updateAIState( + AiType aiType, + bool editable, + String hintText, + ) = _UpdateAIState; + + const factory AIPromptInputEvent.toggleShowPredefinedFormat() = + _ToggleShowPredefinedFormat; + const factory AIPromptInputEvent.updatePredefinedFormat( + PredefinedFormat format, + ) = _UpdatePredefinedFormat; + const factory AIPromptInputEvent.attachFile( + String filePath, + String fileName, + ) = _AttachFile; + const factory AIPromptInputEvent.removeFile(ChatFile file) = _RemoveFile; + const factory AIPromptInputEvent.updateMentionedViews(List views) = + _UpdateMentionedViews; + const factory AIPromptInputEvent.clearMetadata() = _ClearMetadata; +} + +@freezed +class AIPromptInputState with _$AIPromptInputState { + const factory AIPromptInputState({ + required AiType aiType, + required bool supportChatWithFile, + required bool showPredefinedFormats, + required PredefinedFormat? predefinedFormat, + required List attachedFiles, + required List mentionedPages, + required bool editable, + required String hintText, + }) = _AIPromptInputState; + + factory AIPromptInputState.initial(PredefinedFormat? format) => + AIPromptInputState( + aiType: AiType.cloud, + supportChatWithFile: false, + showPredefinedFormats: format != null, + predefinedFormat: format, + attachedFiles: [], + mentionedPages: [], + editable: true, + hintText: '', + ); +} diff --git a/frontend/appflowy_flutter/lib/ai/service/appflowy_ai_service.dart b/frontend/appflowy_flutter/lib/ai/service/appflowy_ai_service.dart new file mode 100644 index 0000000000..39487652f8 --- /dev/null +++ b/frontend/appflowy_flutter/lib/ai/service/appflowy_ai_service.dart @@ -0,0 +1,204 @@ +import 'dart:async'; +import 'dart:ffi'; +import 'dart:isolate'; + +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_entities.dart'; +import 'package:appflowy/shared/list_extension.dart'; +import 'package:appflowy_backend/dispatch/dispatch.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart'; +import 'package:appflowy_result/appflowy_result.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:fixnum/fixnum.dart' as fixnum; + +import 'ai_entities.dart'; +import 'error.dart'; + +enum LocalAIStreamingState { + notReady, + disabled, +} + +abstract class AIRepository { + Future streamCompletion({ + String? objectId, + required String text, + PredefinedFormat? format, + List sourceIds = const [], + List history = const [], + required CompletionTypePB completionType, + required Future Function() onStart, + required Future Function(String text) processMessage, + required Future Function(String text) processAssistMessage, + required Future Function() onEnd, + required void Function(AIError error) onError, + required void Function(LocalAIStreamingState state) + onLocalAIStreamingStateChange, + }); +} + +class AppFlowyAIService implements AIRepository { + @override + Future<(String, CompletionStream)?> streamCompletion({ + String? objectId, + required String text, + PredefinedFormat? format, + List sourceIds = const [], + List history = const [], + required CompletionTypePB completionType, + required Future Function() onStart, + required Future Function(String text) processMessage, + required Future Function(String text) processAssistMessage, + required Future Function() onEnd, + required void Function(AIError error) onError, + required void Function(LocalAIStreamingState state) + onLocalAIStreamingStateChange, + }) async { + final stream = AppFlowyCompletionStream( + onStart: onStart, + processMessage: processMessage, + processAssistMessage: processAssistMessage, + processError: onError, + onLocalAIStreamingStateChange: onLocalAIStreamingStateChange, + onEnd: onEnd, + ); + + final records = history.map((record) => record.toPB()).toList(); + + final payload = CompleteTextPB( + text: text, + completionType: completionType, + format: format?.toPB(), + streamPort: fixnum.Int64(stream.nativePort), + objectId: objectId ?? '', + ragIds: [ + if (objectId != null) objectId, + ...sourceIds, + ].unique(), + history: records, + ); + + return AIEventCompleteText(payload).send().fold( + (task) => (task.taskId, stream), + (error) { + Log.error(error); + return null; + }, + ); + } +} + +abstract class CompletionStream { + CompletionStream({ + required this.onStart, + required this.processMessage, + required this.processAssistMessage, + required this.processError, + required this.onLocalAIStreamingStateChange, + required this.onEnd, + }); + + final Future Function() onStart; + final Future Function(String text) processMessage; + final Future Function(String text) processAssistMessage; + final void Function(AIError error) processError; + final void Function(LocalAIStreamingState state) + onLocalAIStreamingStateChange; + final Future Function() onEnd; +} + +class AppFlowyCompletionStream extends CompletionStream { + AppFlowyCompletionStream({ + required super.onStart, + required super.processMessage, + required super.processAssistMessage, + required super.processError, + required super.onEnd, + required super.onLocalAIStreamingStateChange, + }) { + _startListening(); + } + + final RawReceivePort _port = RawReceivePort(); + final StreamController _controller = StreamController.broadcast(); + late StreamSubscription _subscription; + int get nativePort => _port.sendPort.nativePort; + + void _startListening() { + _port.handler = _controller.add; + _subscription = _controller.stream.listen( + (event) async { + await _handleEvent(event); + }, + ); + } + + Future dispose() async { + await _controller.close(); + await _subscription.cancel(); + _port.close(); + } + + Future _handleEvent(String event) async { + // Check simple matches first + if (event == AIStreamEventPrefix.aiResponseLimit) { + processError( + AIError( + message: LocaleKeys.ai_textLimitReachedDescription.tr(), + code: AIErrorCode.aiResponseLimitExceeded, + ), + ); + return; + } + + if (event == AIStreamEventPrefix.aiImageResponseLimit) { + processError( + AIError( + message: LocaleKeys.ai_imageLimitReachedDescription.tr(), + code: AIErrorCode.aiImageResponseLimitExceeded, + ), + ); + return; + } + + // Otherwise, parse out prefix:content + if (event.startsWith(AIStreamEventPrefix.aiMaxRequired)) { + processError( + AIError( + message: event.substring(AIStreamEventPrefix.aiMaxRequired.length), + code: AIErrorCode.other, + ), + ); + } else if (event.startsWith(AIStreamEventPrefix.start)) { + await onStart(); + } else if (event.startsWith(AIStreamEventPrefix.data)) { + await processMessage( + event.substring(AIStreamEventPrefix.data.length), + ); + } else if (event.startsWith(AIStreamEventPrefix.comment)) { + await processAssistMessage( + event.substring(AIStreamEventPrefix.comment.length), + ); + } else if (event.startsWith(AIStreamEventPrefix.finish)) { + await onEnd(); + } else if (event.startsWith(AIStreamEventPrefix.localAIDisabled)) { + onLocalAIStreamingStateChange( + LocalAIStreamingState.disabled, + ); + } else if (event.startsWith(AIStreamEventPrefix.localAINotReady)) { + onLocalAIStreamingStateChange( + LocalAIStreamingState.notReady, + ); + } else if (event.startsWith(AIStreamEventPrefix.error)) { + processError( + AIError( + message: event.substring(AIStreamEventPrefix.error.length), + code: AIErrorCode.other, + ), + ); + } else { + Log.debug('Unknown AI event: $event'); + } + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/service/error.dart b/frontend/appflowy_flutter/lib/ai/service/error.dart similarity index 83% rename from frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/service/error.dart rename to frontend/appflowy_flutter/lib/ai/service/error.dart index 0912f9bdcf..0c98e83172 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/service/error.dart +++ b/frontend/appflowy_flutter/lib/ai/service/error.dart @@ -7,7 +7,7 @@ part 'error.g.dart'; class AIError with _$AIError { const factory AIError({ required String message, - @Default(AIErrorCode.other) AIErrorCode code, + required AIErrorCode code, }) = _AIError; factory AIError.fromJson(Map json) => @@ -17,6 +17,8 @@ class AIError with _$AIError { enum AIErrorCode { @JsonValue('AIResponseLimitExceeded') aiResponseLimitExceeded, + @JsonValue('AIImageResponseLimitExceeded') + aiImageResponseLimitExceeded, @JsonValue('Other') other, } diff --git a/frontend/appflowy_flutter/lib/ai/service/select_model_bloc.dart b/frontend/appflowy_flutter/lib/ai/service/select_model_bloc.dart new file mode 100644 index 0000000000..7ad52b9ec4 --- /dev/null +++ b/frontend/appflowy_flutter/lib/ai/service/select_model_bloc.dart @@ -0,0 +1,92 @@ +import 'dart:async'; + +import 'package:appflowy/ai/service/ai_model_state_notifier.dart'; +import 'package:appflowy_backend/dispatch/dispatch.dart'; +import 'package:appflowy_backend/protobuf/flowy-ai/entities.pbserver.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'select_model_bloc.freezed.dart'; + +class SelectModelBloc extends Bloc { + SelectModelBloc({ + required AIModelStateNotifier aiModelStateNotifier, + }) : _aiModelStateNotifier = aiModelStateNotifier, + super(SelectModelState.initial(aiModelStateNotifier)) { + on( + (event, emit) { + event.when( + selectModel: (model) { + AIEventUpdateSelectedModel( + UpdateSelectedModelPB( + source: _aiModelStateNotifier.objectId, + selectedModel: model, + ), + ).send(); + + emit(state.copyWith(selectedModel: model)); + }, + didLoadModels: (models, selectedModel) { + emit( + SelectModelState( + models: models, + selectedModel: selectedModel, + ), + ); + }, + ); + }, + ); + + _aiModelStateNotifier.addListener( + onAvailableModelsChanged: _onAvailableModelsChanged, + ); + } + + final AIModelStateNotifier _aiModelStateNotifier; + + @override + Future close() async { + _aiModelStateNotifier.removeListener( + onAvailableModelsChanged: _onAvailableModelsChanged, + ); + await super.close(); + } + + void _onAvailableModelsChanged( + List models, + AIModelPB? selectedModel, + ) { + if (!isClosed) { + add(SelectModelEvent.didLoadModels(models, selectedModel)); + } + } +} + +@freezed +class SelectModelEvent with _$SelectModelEvent { + const factory SelectModelEvent.selectModel( + AIModelPB model, + ) = _SelectModel; + + const factory SelectModelEvent.didLoadModels( + List models, + AIModelPB? selectedModel, + ) = _DidLoadModels; +} + +@freezed +class SelectModelState with _$SelectModelState { + const factory SelectModelState({ + required List models, + required AIModelPB? selectedModel, + }) = _SelectModelState; + + factory SelectModelState.initial(AIModelStateNotifier notifier) { + final (models, selectedModel) = notifier.getAvailableModels(); + return SelectModelState( + models: models, + selectedModel: selectedModel, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/loading_indicator.dart b/frontend/appflowy_flutter/lib/ai/widgets/loading_indicator.dart similarity index 94% rename from frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/loading_indicator.dart rename to frontend/appflowy_flutter/lib/ai/widgets/loading_indicator.dart index be7bbf7ac9..3a9c96b255 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/loading_indicator.dart +++ b/frontend/appflowy_flutter/lib/ai/widgets/loading_indicator.dart @@ -3,8 +3,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; /// An animated generating indicator for an AI response -class ChatAILoading extends StatelessWidget { - const ChatAILoading({ +class AILoadingIndicator extends StatelessWidget { + const AILoadingIndicator({ super.key, this.text = "", this.duration = const Duration(seconds: 1), @@ -16,8 +16,7 @@ class ChatAILoading extends StatelessWidget { @override Widget build(BuildContext context) { final slice = Duration(milliseconds: duration.inMilliseconds ~/ 5); - return Padding( - padding: const EdgeInsets.only(top: 8.0), + return SelectionContainer.disabled( child: SizedBox( height: 20, child: SeparatedRow( diff --git a/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/action_buttons.dart b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/action_buttons.dart new file mode 100644 index 0000000000..9dd370b39b --- /dev/null +++ b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/action_buttons.dart @@ -0,0 +1,67 @@ +import 'package:flutter/material.dart'; +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/theme_extension.dart'; +import 'package:flowy_infra_ui/style_widget/icon_button.dart'; +import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; + +import 'layout_define.dart'; + +class PromptInputAttachmentButton extends StatelessWidget { + const PromptInputAttachmentButton({required this.onTap, super.key}); + + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return FlowyTooltip( + message: LocaleKeys.chat_uploadFile.tr(), + child: SizedBox.square( + dimension: DesktopAIPromptSizes.actionBarButtonSize, + child: FlowyIconButton( + hoverColor: AFThemeExtension.of(context).lightGreyHover, + radius: BorderRadius.circular(8), + icon: FlowySvg( + FlowySvgs.ai_attachment_s, + size: const Size.square(16), + color: Theme.of(context).iconTheme.color, + ), + onPressed: onTap, + ), + ), + ); + } +} + +class PromptInputMentionButton extends StatelessWidget { + const PromptInputMentionButton({ + super.key, + required this.buttonSize, + required this.iconSize, + required this.onTap, + }); + + final double buttonSize; + final double iconSize; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return FlowyTooltip( + message: LocaleKeys.chat_clickToMention.tr(), + preferBelow: false, + child: FlowyIconButton( + width: buttonSize, + hoverColor: AFThemeExtension.of(context).lightGreyHover, + radius: BorderRadius.circular(8), + icon: FlowySvg( + FlowySvgs.chat_at_s, + size: Size.square(iconSize), + color: Theme.of(context).iconTheme.color, + ), + onPressed: onTap, + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/desktop_prompt_text_field.dart b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/desktop_prompt_text_field.dart new file mode 100644 index 0000000000..a2676f2c15 --- /dev/null +++ b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/desktop_prompt_text_field.dart @@ -0,0 +1,702 @@ +import 'package:appflowy/ai/ai.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/ai_chat/application/chat_input_control_cubit.dart'; +import 'package:appflowy/plugins/ai_chat/presentation/layout_define.dart'; +import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/util/theme_extension.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:extended_text_field/extended_text_field.dart'; +import 'package:flowy_infra/file_picker/file_picker_service.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class DesktopPromptInput extends StatefulWidget { + const DesktopPromptInput({ + super.key, + required this.isStreaming, + required this.textController, + required this.onStopStreaming, + required this.onSubmitted, + required this.selectedSourcesNotifier, + required this.onUpdateSelectedSources, + this.hideDecoration = false, + this.hideFormats = false, + this.extraBottomActionButton, + }); + + final bool isStreaming; + final TextEditingController textController; + final void Function() onStopStreaming; + final void Function(String, PredefinedFormat?, Map) + onSubmitted; + final ValueNotifier> selectedSourcesNotifier; + final void Function(List) onUpdateSelectedSources; + final bool hideDecoration; + final bool hideFormats; + final Widget? extraBottomActionButton; + + @override + State createState() => _DesktopPromptInputState(); +} + +class _DesktopPromptInputState extends State { + final textFieldKey = GlobalKey(); + final layerLink = LayerLink(); + final overlayController = OverlayPortalController(); + final inputControlCubit = ChatInputControlCubit(); + final focusNode = FocusNode(); + + late SendButtonState sendButtonState; + bool isComposing = false; + + @override + void initState() { + super.initState(); + + widget.textController.addListener(handleTextControllerChanged); + focusNode + ..addListener( + () { + if (!widget.hideDecoration) { + setState(() {}); // refresh border color + } + if (!focusNode.hasFocus) { + cancelMentionPage(); // hide menu when lost focus + } + }, + ) + ..onKeyEvent = handleKeyEvent; + + updateSendButtonState(); + + WidgetsBinding.instance.addPostFrameCallback((_) { + focusNode.requestFocus(); + }); + } + + @override + void didUpdateWidget(covariant oldWidget) { + updateSendButtonState(); + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + focusNode.dispose(); + widget.textController.removeListener(handleTextControllerChanged); + inputControlCubit.close(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return BlocProvider.value( + value: inputControlCubit, + child: BlocListener( + listener: (context, state) { + state.maybeWhen( + updateSelectedViews: (selectedViews) { + context + .read() + .add(AIPromptInputEvent.updateMentionedViews(selectedViews)); + }, + orElse: () {}, + ); + }, + child: OverlayPortal( + controller: overlayController, + overlayChildBuilder: (context) { + return PromptInputMentionPageMenu( + anchor: PromptInputAnchor(textFieldKey, layerLink), + textController: widget.textController, + onPageSelected: handlePageSelected, + ); + }, + child: DecoratedBox( + decoration: decoration(context), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ConstrainedBox( + constraints: BoxConstraints( + maxHeight: + DesktopAIPromptSizes.attachedFilesBarPadding.vertical + + DesktopAIPromptSizes.attachedFilesPreviewHeight, + ), + child: TextFieldTapRegion( + child: PromptInputFile( + onDeleted: (file) => context + .read() + .add(AIPromptInputEvent.removeFile(file)), + ), + ), + ), + const VSpace(4.0), + BlocBuilder( + builder: (context, state) { + return Stack( + children: [ + ConstrainedBox( + constraints: getTextFieldConstraints( + state.showPredefinedFormats && !widget.hideFormats, + ), + child: inputTextField(), + ), + if (state.showPredefinedFormats && !widget.hideFormats) + Positioned.fill( + bottom: null, + child: TextFieldTapRegion( + child: Padding( + padding: const EdgeInsetsDirectional.only( + start: 8.0, + ), + child: ChangeFormatBar( + showImageFormats: state.aiType.isCloud, + predefinedFormat: state.predefinedFormat, + spacing: 4.0, + onSelectPredefinedFormat: (format) => + context.read().add( + AIPromptInputEvent + .updatePredefinedFormat(format), + ), + ), + ), + ), + ), + Positioned.fill( + top: null, + child: TextFieldTapRegion( + child: _PromptBottomActions( + showPredefinedFormatBar: + state.showPredefinedFormats, + showPredefinedFormatButton: !widget.hideFormats, + onTogglePredefinedFormatSection: () => + context.read().add( + AIPromptInputEvent + .toggleShowPredefinedFormat(), + ), + onStartMention: startMentionPageFromButton, + sendButtonState: sendButtonState, + onSendPressed: handleSend, + onStopStreaming: widget.onStopStreaming, + selectedSourcesNotifier: + widget.selectedSourcesNotifier, + onUpdateSelectedSources: + widget.onUpdateSelectedSources, + extraBottomActionButton: + widget.extraBottomActionButton, + ), + ), + ), + ], + ); + }, + ), + ], + ), + ), + ), + ), + ); + } + + BoxDecoration decoration(BuildContext context) { + if (widget.hideDecoration) { + return BoxDecoration(); + } + return BoxDecoration( + color: Theme.of(context).colorScheme.surface, + border: Border.all( + color: focusNode.hasFocus + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.outline, + width: focusNode.hasFocus ? 1.5 : 1.0, + ), + borderRadius: const BorderRadius.all(Radius.circular(12.0)), + ); + } + + void startMentionPageFromButton() { + if (overlayController.isShowing) { + return; + } + if (!focusNode.hasFocus) { + focusNode.requestFocus(); + } + widget.textController.text += '@'; + WidgetsBinding.instance.addPostFrameCallback((_) { + if (context.mounted) { + context + .read() + .startSearching(widget.textController.value); + overlayController.show(); + } + }); + } + + void cancelMentionPage() { + if (overlayController.isShowing) { + inputControlCubit.reset(); + overlayController.hide(); + } + } + + void updateSendButtonState() { + if (widget.isStreaming) { + sendButtonState = SendButtonState.streaming; + } else if (widget.textController.text.trim().isEmpty) { + sendButtonState = SendButtonState.disabled; + } else { + sendButtonState = SendButtonState.enabled; + } + } + + void handleSend() { + if (widget.isStreaming) { + return; + } + final trimmedText = inputControlCubit.formatIntputText( + widget.textController.text.trim(), + ); + widget.textController.clear(); + if (trimmedText.isEmpty) { + return; + } + + // get the attached files and mentioned pages + final metadata = context.read().consumeMetadata(); + + final bloc = context.read(); + final showPredefinedFormats = bloc.state.showPredefinedFormats; + final predefinedFormat = bloc.state.predefinedFormat; + + widget.onSubmitted( + trimmedText, + showPredefinedFormats ? predefinedFormat : null, + metadata, + ); + } + + void handleTextControllerChanged() { + setState(() { + // update whether send button is clickable + updateSendButtonState(); + isComposing = !widget.textController.value.composing.isCollapsed; + }); + + if (isComposing) { + return; + } + + // disable mention + return; + + // handle text and selection changes ONLY when mentioning a page + // ignore: dead_code + if (!overlayController.isShowing || + inputControlCubit.filterStartPosition == -1) { + return; + } + + // handle cases where mention a page is cancelled + final textController = widget.textController; + final textSelection = textController.value.selection; + final isSelectingMultipleCharacters = !textSelection.isCollapsed; + final isCaretBeforeStartOfRange = + textSelection.baseOffset < inputControlCubit.filterStartPosition; + final isCaretAfterEndOfRange = + textSelection.baseOffset > inputControlCubit.filterEndPosition; + final isTextSame = inputControlCubit.inputText == textController.text; + + if (isSelectingMultipleCharacters || + isTextSame && (isCaretBeforeStartOfRange || isCaretAfterEndOfRange)) { + cancelMentionPage(); + return; + } + + final previousLength = inputControlCubit.inputText.characters.length; + final currentLength = textController.text.characters.length; + + // delete "@" + if (previousLength != currentLength && isCaretBeforeStartOfRange) { + cancelMentionPage(); + return; + } + + // handle cases where mention the filter is updated + if (previousLength != currentLength) { + final diff = currentLength - previousLength; + final newEndPosition = inputControlCubit.filterEndPosition + diff; + final newFilter = textController.text.substring( + inputControlCubit.filterStartPosition, + newEndPosition, + ); + inputControlCubit.updateFilter( + textController.text, + newFilter, + newEndPosition: newEndPosition, + ); + } else if (!isTextSame) { + final newFilter = textController.text.substring( + inputControlCubit.filterStartPosition, + inputControlCubit.filterEndPosition, + ); + inputControlCubit.updateFilter(textController.text, newFilter); + } + } + + KeyEventResult handleKeyEvent(FocusNode node, KeyEvent event) { + // if (event.character == '@') { + // WidgetsBinding.instance.addPostFrameCallback((_) { + // inputControlCubit.startSearching(widget.textController.value); + // overlayController.show(); + // }); + // } + if (event is KeyDownEvent && + event.logicalKey == LogicalKeyboardKey.escape) { + node.unfocus(); + return KeyEventResult.handled; + } + return KeyEventResult.ignored; + } + + void handlePageSelected(ViewPB view) { + final newText = widget.textController.text.replaceRange( + inputControlCubit.filterStartPosition, + inputControlCubit.filterEndPosition, + view.id, + ); + widget.textController.value = TextEditingValue( + text: newText, + selection: TextSelection.collapsed( + offset: inputControlCubit.filterStartPosition + view.id.length, + affinity: TextAffinity.upstream, + ), + ); + + inputControlCubit.selectPage(view); + overlayController.hide(); + } + + Widget inputTextField() { + return Shortcuts( + shortcuts: buildShortcuts(), + child: Actions( + actions: buildActions(), + child: CompositedTransformTarget( + link: layerLink, + child: BlocBuilder( + builder: (context, state) { + Widget textField = PromptInputTextField( + key: textFieldKey, + editable: state.editable, + cubit: inputControlCubit, + textController: widget.textController, + textFieldFocusNode: focusNode, + contentPadding: + calculateContentPadding(state.showPredefinedFormats), + hintText: state.hintText, + ); + + if (!state.editable) { + textField = FlowyTooltip( + message: LocaleKeys + .settings_aiPage_keys_localAINotReadyTextFieldPrompt + .tr(), + child: textField, + ); + } + + return textField; + }, + ), + ), + ), + ); + } + + BoxConstraints getTextFieldConstraints(bool showPredefinedFormats) { + double minHeight = DesktopAIPromptSizes.textFieldMinHeight + + DesktopAIPromptSizes.actionBarSendButtonSize + + DesktopAIChatSizes.inputActionBarMargin.vertical; + double maxHeight = 300; + if (showPredefinedFormats) { + minHeight += DesktopAIPromptSizes.predefinedFormatButtonHeight; + maxHeight += DesktopAIPromptSizes.predefinedFormatButtonHeight; + } + return BoxConstraints(minHeight: minHeight, maxHeight: maxHeight); + } + + EdgeInsetsGeometry calculateContentPadding(bool showPredefinedFormats) { + final top = showPredefinedFormats + ? DesktopAIPromptSizes.predefinedFormatButtonHeight + : 0.0; + final bottom = DesktopAIPromptSizes.actionBarSendButtonSize + + DesktopAIChatSizes.inputActionBarMargin.vertical; + + return DesktopAIPromptSizes.textFieldContentPadding + .add(EdgeInsets.only(top: top, bottom: bottom)); + } + + Map buildShortcuts() { + if (isComposing) { + return const {}; + } + + return const { + SingleActivator(LogicalKeyboardKey.arrowUp): _FocusPreviousItemIntent(), + SingleActivator(LogicalKeyboardKey.arrowDown): _FocusNextItemIntent(), + SingleActivator(LogicalKeyboardKey.escape): _CancelMentionPageIntent(), + SingleActivator(LogicalKeyboardKey.enter): _SubmitOrMentionPageIntent(), + }; + } + + Map> buildActions() { + return { + _FocusPreviousItemIntent: CallbackAction<_FocusPreviousItemIntent>( + onInvoke: (intent) { + inputControlCubit.updateSelectionUp(); + return; + }, + ), + _FocusNextItemIntent: CallbackAction<_FocusNextItemIntent>( + onInvoke: (intent) { + inputControlCubit.updateSelectionDown(); + return; + }, + ), + _CancelMentionPageIntent: CallbackAction<_CancelMentionPageIntent>( + onInvoke: (intent) { + cancelMentionPage(); + return; + }, + ), + _SubmitOrMentionPageIntent: CallbackAction<_SubmitOrMentionPageIntent>( + onInvoke: (intent) { + if (overlayController.isShowing) { + inputControlCubit.state.maybeWhen( + ready: (visibleViews, focusedViewIndex) { + if (focusedViewIndex != -1 && + focusedViewIndex < visibleViews.length) { + handlePageSelected(visibleViews[focusedViewIndex]); + } + }, + orElse: () {}, + ); + } else { + handleSend(); + } + return; + }, + ), + }; + } +} + +class _SubmitOrMentionPageIntent extends Intent { + const _SubmitOrMentionPageIntent(); +} + +class _CancelMentionPageIntent extends Intent { + const _CancelMentionPageIntent(); +} + +class _FocusPreviousItemIntent extends Intent { + const _FocusPreviousItemIntent(); +} + +class _FocusNextItemIntent extends Intent { + const _FocusNextItemIntent(); +} + +class PromptInputTextField extends StatelessWidget { + const PromptInputTextField({ + super.key, + required this.editable, + required this.cubit, + required this.textController, + required this.textFieldFocusNode, + required this.contentPadding, + this.hintText = "", + }); + + final ChatInputControlCubit cubit; + final TextEditingController textController; + final FocusNode textFieldFocusNode; + final EdgeInsetsGeometry contentPadding; + final bool editable; + final String hintText; + + @override + Widget build(BuildContext context) { + return ExtendedTextField( + controller: textController, + focusNode: textFieldFocusNode, + readOnly: !editable, + enabled: editable, + decoration: InputDecoration( + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + contentPadding: contentPadding, + hintText: hintText, + hintStyle: inputHintTextStyle(context), + isCollapsed: true, + isDense: true, + ), + keyboardType: TextInputType.multiline, + textCapitalization: TextCapitalization.sentences, + minLines: 1, + maxLines: null, + style: Theme.of(context).textTheme.bodyMedium, + specialTextSpanBuilder: PromptInputTextSpanBuilder( + inputControlCubit: cubit, + specialTextStyle: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).colorScheme.primary, + fontWeight: FontWeight.w600, + ), + ), + ); + } + + TextStyle? inputHintTextStyle(BuildContext context) { + return Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).isLightMode + ? const Color(0xFFBDC2C8) + : const Color(0xFF3C3E51), + ); + } +} + +class _PromptBottomActions extends StatelessWidget { + const _PromptBottomActions({ + required this.sendButtonState, + required this.showPredefinedFormatBar, + required this.showPredefinedFormatButton, + required this.onTogglePredefinedFormatSection, + required this.onStartMention, + required this.onSendPressed, + required this.onStopStreaming, + required this.selectedSourcesNotifier, + required this.onUpdateSelectedSources, + this.extraBottomActionButton, + }); + + final bool showPredefinedFormatBar; + final bool showPredefinedFormatButton; + final void Function() onTogglePredefinedFormatSection; + final void Function() onStartMention; + final SendButtonState sendButtonState; + final void Function() onSendPressed; + final void Function() onStopStreaming; + final ValueNotifier> selectedSourcesNotifier; + final void Function(List) onUpdateSelectedSources; + final Widget? extraBottomActionButton; + + @override + Widget build(BuildContext context) { + return Container( + height: DesktopAIPromptSizes.actionBarSendButtonSize, + margin: DesktopAIChatSizes.inputActionBarMargin, + child: BlocBuilder( + builder: (context, state) { + return Row( + children: [ + if (showPredefinedFormatButton) ...[ + _predefinedFormatButton(), + const HSpace( + DesktopAIChatSizes.inputActionBarButtonSpacing, + ), + ], + SelectModelMenu( + aiModelStateNotifier: + context.read().aiModelStateNotifier, + ), + const Spacer(), + if (state.aiType.isCloud) ...[ + _selectSourcesButton(), + const HSpace( + DesktopAIChatSizes.inputActionBarButtonSpacing, + ), + ], + if (extraBottomActionButton != null) ...[ + extraBottomActionButton!, + const HSpace( + DesktopAIChatSizes.inputActionBarButtonSpacing, + ), + ], + // _mentionButton(context), + // const HSpace( + // DesktopAIPromptSizes.actionBarButtonSpacing, + // ), + if (state.supportChatWithFile) ...[ + _attachmentButton(context), + const HSpace( + DesktopAIChatSizes.inputActionBarButtonSpacing, + ), + ], + _sendButton(), + ], + ); + }, + ), + ); + } + + Widget _predefinedFormatButton() { + return PromptInputDesktopToggleFormatButton( + showFormatBar: showPredefinedFormatBar, + onTap: onTogglePredefinedFormatSection, + ); + } + + Widget _selectSourcesButton() { + return PromptInputDesktopSelectSourcesButton( + onUpdateSelectedSources: onUpdateSelectedSources, + selectedSourcesNotifier: selectedSourcesNotifier, + ); + } + + // Widget _mentionButton(BuildContext context) { + // return PromptInputMentionButton( + // iconSize: DesktopAIPromptSizes.actionBarIconSize, + // buttonSize: DesktopAIPromptSizes.actionBarButtonSize, + // onTap: onStartMention, + // ); + // } + + Widget _attachmentButton(BuildContext context) { + return PromptInputAttachmentButton( + onTap: () async { + final path = await getIt().pickFiles( + dialogTitle: '', + type: FileType.custom, + allowedExtensions: ["pdf", "txt", "md"], + ); + + if (path == null) { + return; + } + + for (final file in path.files) { + if (file.path != null && context.mounted) { + context + .read() + .add(AIPromptInputEvent.attachFile(file.path!, file.name)); + } + } + }, + ); + } + + Widget _sendButton() { + return PromptInputSendButton( + state: sendButtonState, + onSendPressed: onSendPressed, + onStopStreaming: onStopStreaming, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/file_attachment_list.dart b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/file_attachment_list.dart new file mode 100644 index 0000000000..cd68205506 --- /dev/null +++ b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/file_attachment_list.dart @@ -0,0 +1,162 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/ai/service/ai_prompt_input_bloc.dart'; +import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart'; +import 'package:appflowy/plugins/ai_chat/application/chat_input_file_bloc.dart'; +import 'package:flowy_infra/theme_extension.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:styled_widget/styled_widget.dart'; + +import 'layout_define.dart'; + +class PromptInputFile extends StatelessWidget { + const PromptInputFile({ + super.key, + required this.onDeleted, + }); + + final void Function(ChatFile) onDeleted; + + @override + Widget build(BuildContext context) { + return BlocSelector>( + selector: (state) => state.attachedFiles, + builder: (context, files) { + if (files.isEmpty) { + return const SizedBox.shrink(); + } + return ListView.separated( + scrollDirection: Axis.horizontal, + padding: DesktopAIPromptSizes.attachedFilesBarPadding - + const EdgeInsets.only(top: 6), + separatorBuilder: (context, index) => const HSpace( + DesktopAIPromptSizes.attachedFilesPreviewSpacing - 6, + ), + itemCount: files.length, + itemBuilder: (context, index) => ChatFilePreview( + file: files[index], + onDeleted: () => onDeleted(files[index]), + ), + ); + }, + ); + } +} + +class ChatFilePreview extends StatefulWidget { + const ChatFilePreview({ + required this.file, + required this.onDeleted, + super.key, + }); + + final ChatFile file; + final VoidCallback onDeleted; + + @override + State createState() => _ChatFilePreviewState(); +} + +class _ChatFilePreviewState extends State { + bool isHover = false; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => ChatInputFileBloc(file: widget.file), + child: BlocBuilder( + builder: (context, state) { + return MouseRegion( + onEnter: (_) => setHover(true), + onExit: (_) => setHover(false), + child: Stack( + children: [ + Container( + margin: const EdgeInsetsDirectional.only(top: 6, end: 6), + constraints: const BoxConstraints(maxWidth: 240), + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).dividerColor, + ), + borderRadius: BorderRadius.circular(8), + ), + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + decoration: BoxDecoration( + color: AFThemeExtension.of(context).tint1, + borderRadius: BorderRadius.circular(8), + ), + height: 32, + width: 32, + child: Center( + child: FlowySvg( + FlowySvgs.page_m, + size: const Size.square(16), + color: Theme.of(context).hintColor, + ), + ), + ), + const HSpace(8), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FlowyText( + widget.file.fileName, + fontSize: 12.0, + ), + FlowyText( + widget.file.fileType.name, + color: Theme.of(context).hintColor, + fontSize: 12.0, + ), + ], + ), + ), + ], + ), + ), + if (isHover) + _CloseButton( + onTap: widget.onDeleted, + ).positioned(top: 0, right: 0), + ], + ), + ); + }, + ), + ); + } + + void setHover(bool value) { + if (value != isHover) { + setState(() => isHover = value); + } + } +} + +class _CloseButton extends StatelessWidget { + const _CloseButton({required this.onTap}); + + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: onTap, + child: FlowySvg( + FlowySvgs.ai_close_filled_s, + color: AFThemeExtension.of(context).greyHover, + size: const Size.square(16), + ), + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/layout_define.dart b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/layout_define.dart new file mode 100644 index 0000000000..e5c7e54522 --- /dev/null +++ b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/layout_define.dart @@ -0,0 +1,42 @@ +import 'package:flutter/widgets.dart'; + +class DesktopAIPromptSizes { + const DesktopAIPromptSizes._(); + + static const attachedFilesBarPadding = + EdgeInsets.only(left: 8.0, top: 8.0, right: 8.0); + static const attachedFilesPreviewHeight = 48.0; + static const attachedFilesPreviewSpacing = 12.0; + + static const predefinedFormatButtonHeight = 28.0; + static const predefinedFormatIconHeight = 16.0; + + static const textFieldMinHeight = 36.0; + static const textFieldContentPadding = + EdgeInsetsDirectional.fromSTEB(14.0, 8.0, 14.0, 8.0); + + static const actionBarButtonSize = 28.0; + static const actionBarIconSize = 16.0; + static const actionBarSendButtonSize = 32.0; + static const actionBarSendButtonIconSize = 24.0; +} + +class MobileAIPromptSizes { + const MobileAIPromptSizes._(); + + static const attachedFilesBarHeight = 68.0; + static const attachedFilesBarPadding = + EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0, bottom: 4.0); + static const attachedFilesPreviewHeight = 56.0; + static const attachedFilesPreviewSpacing = 8.0; + + static const predefinedFormatButtonHeight = 32.0; + static const predefinedFormatIconHeight = 20.0; + + static const textFieldMinHeight = 32.0; + static const textFieldContentPadding = + EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0); + + static const mentionIconSize = 20.0; + static const sendButtonSize = 32.0; +} diff --git a/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/mention_page_bottom_sheet.dart b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/mention_page_bottom_sheet.dart new file mode 100644 index 0000000000..6e17f311f3 --- /dev/null +++ b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/mention_page_bottom_sheet.dart @@ -0,0 +1,204 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/mobile/presentation/base/flowy_search_text_field.dart'; +import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart'; +import 'package:appflowy/plugins/base/drag_handler.dart'; +import 'package:appflowy/workspace/application/view/view_service.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; + +import 'mention_page_menu.dart'; + +Future showPageSelectorSheet( + BuildContext context, { + required bool Function(ViewPB view) filter, +}) async { + return showMobileBottomSheet( + context, + backgroundColor: Theme.of(context).colorScheme.surface, + maxChildSize: 0.98, + enableDraggableScrollable: true, + scrollableWidgetBuilder: (context, scrollController) { + return Expanded( + child: _MobilePageSelectorBody( + filter: filter, + scrollController: scrollController, + ), + ); + }, + builder: (context) => const SizedBox.shrink(), + ); +} + +class _MobilePageSelectorBody extends StatefulWidget { + const _MobilePageSelectorBody({ + this.filter, + this.scrollController, + }); + + final bool Function(ViewPB view)? filter; + final ScrollController? scrollController; + + @override + State<_MobilePageSelectorBody> createState() => + _MobilePageSelectorBodyState(); +} + +class _MobilePageSelectorBodyState extends State<_MobilePageSelectorBody> { + final textController = TextEditingController(); + late final Future> _viewsFuture = _fetchViews(); + + @override + void dispose() { + textController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return CustomScrollView( + controller: widget.scrollController, + shrinkWrap: true, + slivers: [ + SliverPersistentHeader( + pinned: true, + delegate: _Header( + child: ColoredBox( + color: Theme.of(context).cardColor, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const DragHandle(), + SizedBox( + height: 44.0, + child: Center( + child: FlowyText.medium( + LocaleKeys.document_mobilePageSelector_title.tr(), + fontSize: 16.0, + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 8.0, + ), + child: SizedBox( + height: 44.0, + child: FlowySearchTextField( + controller: textController, + onChanged: (_) => setState(() {}), + ), + ), + ), + const Divider(height: 0.5, thickness: 0.5), + ], + ), + ), + ), + ), + FutureBuilder( + future: _viewsFuture, + builder: (_, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const SliverToBoxAdapter( + child: CircularProgressIndicator.adaptive(), + ); + } + + if (snapshot.hasError || snapshot.data == null) { + return SliverToBoxAdapter( + child: FlowyText( + LocaleKeys.document_mobilePageSelector_failedToLoad.tr(), + ), + ); + } + + final views = snapshot.data! + .where((v) => widget.filter?.call(v) ?? true) + .toList(); + + final filtered = views.where( + (v) => + textController.text.isEmpty || + v.name + .toLowerCase() + .contains(textController.text.toLowerCase()), + ); + + if (filtered.isEmpty) { + return SliverToBoxAdapter( + child: FlowyText( + LocaleKeys.document_mobilePageSelector_noPagesFound.tr(), + ), + ); + } + + return SliverPadding( + padding: + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + final view = filtered.elementAt(index); + return InkWell( + onTap: () => Navigator.of(context).pop(view), + borderRadius: BorderRadius.circular(12), + splashColor: Colors.transparent, + child: Container( + height: 44, + padding: const EdgeInsets.all(4.0), + child: Row( + children: [ + MentionViewIcon(view: view), + const HSpace(8), + Expanded( + child: MentionViewTitleAndAncestors(view: view), + ), + ], + ), + ), + ); + }, + childCount: filtered.length, + ), + ), + ); + }, + ), + ], + ); + } + + Future> _fetchViews() async => + (await ViewBackendService.getAllViews()).toNullable()?.items ?? []; +} + +class _Header extends SliverPersistentHeaderDelegate { + const _Header({ + required this.child, + }); + + final Widget child; + + @override + Widget build( + BuildContext context, + double shrinkOffset, + bool overlapsContent, + ) { + return child; + } + + @override + double get maxExtent => 120.5; + + @override + double get minExtent => 120.5; + + @override + bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) { + return false; + } +} diff --git a/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/mention_page_menu.dart b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/mention_page_menu.dart new file mode 100644 index 0000000000..ae2dbe5f26 --- /dev/null +++ b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/mention_page_menu.dart @@ -0,0 +1,435 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/ai_chat/application/chat_input_control_cubit.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart'; +import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; +import 'package:appflowy/workspace/application/view/view_ext.dart'; +import 'package:appflowy/workspace/application/view_title/view_title_bar_bloc.dart'; +import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; +import 'package:easy_localization/easy_localization.dart' hide TextDirection; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flowy_infra_ui/style_widget/hover.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:scroll_to_index/scroll_to_index.dart'; + +const double _itemHeight = 44.0; +const double _noPageHeight = 20.0; +const double _fixedWidth = 360.0; +const double _maxHeight = 328.0; + +class PromptInputAnchor { + PromptInputAnchor(this.anchorKey, this.layerLink); + + final GlobalKey> anchorKey; + final LayerLink layerLink; +} + +class PromptInputMentionPageMenu extends StatefulWidget { + const PromptInputMentionPageMenu({ + super.key, + required this.anchor, + required this.textController, + required this.onPageSelected, + }); + + final PromptInputAnchor anchor; + final TextEditingController textController; + final void Function(ViewPB view) onPageSelected; + + @override + State createState() => + _PromptInputMentionPageMenuState(); +} + +class _PromptInputMentionPageMenuState + extends State { + @override + void initState() { + super.initState(); + Future.delayed(Duration.zero, () { + if (mounted) { + context.read().refreshViews(); + } + }); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Stack( + children: [ + CompositedTransformFollower( + link: widget.anchor.layerLink, + showWhenUnlinked: false, + offset: Offset(getPopupOffsetX(), 0.0), + followerAnchor: Alignment.bottomLeft, + child: Container( + constraints: const BoxConstraints( + minWidth: _fixedWidth, + maxWidth: _fixedWidth, + maxHeight: _maxHeight, + ), + decoration: BoxDecoration( + color: Theme.of(context).cardColor, + borderRadius: BorderRadius.circular(6.0), + boxShadow: const [ + BoxShadow( + color: Color(0x0A1F2329), + blurRadius: 24, + offset: Offset(0, 8), + spreadRadius: 8, + ), + BoxShadow( + color: Color(0x0A1F2329), + blurRadius: 12, + offset: Offset(0, 6), + ), + BoxShadow( + color: Color(0x0F1F2329), + blurRadius: 8, + offset: Offset(0, 4), + spreadRadius: -8, + ), + ], + ), + child: TextFieldTapRegion( + child: PromptInputMentionPageList( + onPageSelected: widget.onPageSelected, + ), + ), + ), + ), + ], + ); + }, + ); + } + + double getPopupOffsetX() { + if (widget.anchor.anchorKey.currentContext == null) { + return 0.0; + } + + final cubit = context.read(); + if (cubit.filterStartPosition == -1) { + return 0.0; + } + + final textPosition = TextPosition(offset: cubit.filterEndPosition); + final renderBox = + widget.anchor.anchorKey.currentContext?.findRenderObject() as RenderBox; + + final textPainter = TextPainter( + text: TextSpan(text: cubit.formatIntputText(widget.textController.text)), + textDirection: TextDirection.ltr, + ); + textPainter.layout( + minWidth: renderBox.size.width, + maxWidth: renderBox.size.width, + ); + + final caretOffset = textPainter.getOffsetForCaret(textPosition, Rect.zero); + final boxes = textPainter.getBoxesForSelection( + TextSelection( + baseOffset: textPosition.offset, + extentOffset: textPosition.offset, + ), + ); + + if (boxes.isNotEmpty) { + return boxes.last.right; + } + + return caretOffset.dx; + } +} + +class PromptInputMentionPageList extends StatefulWidget { + const PromptInputMentionPageList({ + super.key, + required this.onPageSelected, + }); + + final void Function(ViewPB view) onPageSelected; + + @override + State createState() => + _PromptInputMentionPageListState(); +} + +class _PromptInputMentionPageListState + extends State { + final autoScrollController = SimpleAutoScrollController( + suggestedRowHeight: _itemHeight, + beginGetter: (rect) => rect.top + 8.0, + endGetter: (rect) => rect.bottom - 8.0, + ); + + @override + void dispose() { + autoScrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return BlocConsumer( + listenWhen: (previous, current) { + return previous.maybeWhen( + ready: (_, pFocusedViewIndex) => current.maybeWhen( + ready: (_, cFocusedViewIndex) => + pFocusedViewIndex != cFocusedViewIndex, + orElse: () => false, + ), + orElse: () => false, + ); + }, + listener: (context, state) { + state.maybeWhen( + ready: (views, focusedViewIndex) { + if (focusedViewIndex == -1 || !autoScrollController.hasClients) { + return; + } + if (autoScrollController.isAutoScrolling) { + autoScrollController.position + .jumpTo(autoScrollController.position.pixels); + } + autoScrollController.scrollToIndex( + focusedViewIndex, + duration: const Duration(milliseconds: 200), + preferPosition: AutoScrollPosition.begin, + ); + }, + orElse: () {}, + ); + }, + builder: (context, state) { + return state.maybeWhen( + loading: () { + return const Padding( + padding: EdgeInsets.all(8.0), + child: SizedBox( + height: _noPageHeight, + child: Center( + child: CircularProgressIndicator.adaptive(), + ), + ), + ); + }, + ready: (views, focusedViewIndex) { + if (views.isEmpty) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: SizedBox( + height: _noPageHeight, + child: Center( + child: FlowyText( + LocaleKeys.chat_inputActionNoPages.tr(), + ), + ), + ), + ); + } + + return ListView.builder( + shrinkWrap: true, + controller: autoScrollController, + padding: const EdgeInsets.all(8.0), + itemCount: views.length, + itemBuilder: (context, index) { + final view = views[index]; + return AutoScrollTag( + key: ValueKey("chat_mention_page_item_${view.id}"), + index: index, + controller: autoScrollController, + child: _ChatMentionPageItem( + view: view, + onTap: () => widget.onPageSelected(view), + isSelected: focusedViewIndex == index, + ), + ); + }, + ); + }, + orElse: () => const SizedBox.shrink(), + ); + }, + ); + } +} + +class _ChatMentionPageItem extends StatelessWidget { + const _ChatMentionPageItem({ + required this.view, + required this.isSelected, + required this.onTap, + }); + + final ViewPB view; + final bool isSelected; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return FlowyTooltip( + message: view.name, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: onTap, + behavior: HitTestBehavior.opaque, + child: FlowyHover( + isSelected: () => isSelected, + child: Container( + height: _itemHeight, + padding: const EdgeInsets.all(4.0), + child: Row( + children: [ + MentionViewIcon(view: view), + const HSpace(8.0), + Expanded(child: MentionViewTitleAndAncestors(view: view)), + ], + ), + ), + ), + ), + ), + ); + } +} + +class MentionViewIcon extends StatelessWidget { + const MentionViewIcon({ + super.key, + required this.view, + }); + + final ViewPB view; + + @override + Widget build(BuildContext context) { + final spaceIcon = view.buildSpaceIconSvg(context); + + if (view.icon.value.isNotEmpty) { + return SizedBox( + width: 16.0, + child: RawEmojiIconWidget( + emoji: view.icon.toEmojiIconData(), + emojiSize: 14, + ), + ); + } + + if (view.isSpace == true && spaceIcon != null) { + return SpaceIcon( + dimension: 16.0, + svgSize: 9.68, + space: view, + cornerRadius: 4, + ); + } + + return FlowySvg( + view.layout.icon, + size: const Size.square(16), + color: Theme.of(context).hintColor, + ); + } +} + +class MentionViewTitleAndAncestors extends StatelessWidget { + const MentionViewTitleAndAncestors({ + super.key, + required this.view, + }); + + final ViewPB view; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => ViewTitleBarBloc(view: view), + child: BlocBuilder( + builder: (context, state) { + final nonEmptyName = view.name.isEmpty + ? LocaleKeys.document_title_placeholder.tr() + : view.name; + + final ancestorList = _getViewAncestorList(state.ancestors); + + if (state.ancestors.isEmpty || ancestorList.trim().isEmpty) { + return FlowyText( + nonEmptyName, + fontSize: 14.0, + overflow: TextOverflow.ellipsis, + ); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FlowyText( + nonEmptyName, + fontSize: 14.0, + figmaLineHeight: 20.0, + overflow: TextOverflow.ellipsis, + ), + FlowyText( + ancestorList, + fontSize: 12.0, + figmaLineHeight: 16.0, + color: Theme.of(context).hintColor, + overflow: TextOverflow.ellipsis, + ), + ], + ); + }, + ), + ); + } + + /// see workspace/presentation/widgets/view_title_bar.dart, upon which this + /// function was based. This version doesn't include the current view in the + /// result, and returns a string rather than a list of widgets + String _getViewAncestorList( + List views, + ) { + const lowerBound = 2; + final upperBound = views.length - 2; + bool hasAddedEllipsis = false; + String result = ""; + + if (views.length <= 1) { + return ""; + } + + // ignore the workspace name, use section name instead in the future + // skip the workspace view + for (var i = 1; i < views.length - 1; i++) { + final view = views[i]; + + if (i >= lowerBound && i < upperBound) { + if (!hasAddedEllipsis) { + hasAddedEllipsis = true; + result += "… / "; + } + continue; + } + + final nonEmptyName = view.name.isEmpty + ? LocaleKeys.document_title_placeholder.tr() + : view.name; + + result += nonEmptyName; + + if (i != views.length - 2) { + // if not the last one, add a divider + result += " / "; + } + } + return result; + } +} diff --git a/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/mentioned_page_text_span.dart b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/mentioned_page_text_span.dart new file mode 100644 index 0000000000..7b519226a3 --- /dev/null +++ b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/mentioned_page_text_span.dart @@ -0,0 +1,78 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/ai_chat/application/chat_input_control_cubit.dart'; +import 'package:collection/collection.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:extended_text_library/extended_text_library.dart'; +import 'package:flutter/material.dart'; + +class PromptInputTextSpanBuilder extends SpecialTextSpanBuilder { + PromptInputTextSpanBuilder({ + required this.inputControlCubit, + this.specialTextStyle, + }); + + final ChatInputControlCubit inputControlCubit; + final TextStyle? specialTextStyle; + + @override + SpecialText? createSpecialText( + String flag, { + TextStyle? textStyle, + SpecialTextGestureTapCallback? onTap, + int? index, + }) { + if (flag == '') { + return null; + } + + if (!isStart(flag, AtText.flag)) { + return null; + } + + // index is at the end of the start flag, so the start index should be index - (flag.length - 1) + return AtText( + inputControlCubit, + specialTextStyle ?? textStyle, + onTap, + // scrubbing over text is kinda funky + start: index! - (AtText.flag.length - 1), + ); + } +} + +class AtText extends SpecialText { + AtText( + this.inputControlCubit, + TextStyle? textStyle, + SpecialTextGestureTapCallback? onTap, { + this.start, + }) : super(flag, '', textStyle, onTap: onTap); + + static const String flag = '@'; + + final int? start; + final ChatInputControlCubit inputControlCubit; + + @override + bool isEnd(String value) => inputControlCubit.selectedViewIds.contains(value); + + @override + InlineSpan finishText() { + final String actualText = toString(); + + final viewName = inputControlCubit.allViews + .firstWhereOrNull((view) => view.id == actualText.substring(1)) + ?.name ?? + ""; + final nonEmptyName = viewName.isEmpty + ? LocaleKeys.document_title_placeholder.tr() + : viewName; + + return SpecialTextSpan( + text: "@$nonEmptyName", + actualText: actualText, + start: start!, + style: textStyle, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/predefined_format_buttons.dart b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/predefined_format_buttons.dart new file mode 100644 index 0000000000..403b978905 --- /dev/null +++ b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/predefined_format_buttons.dart @@ -0,0 +1,215 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flowy_infra_ui/style_widget/hover.dart'; +import 'package:flutter/material.dart'; +import 'package:universal_platform/universal_platform.dart'; + +import '../../service/ai_entities.dart'; +import 'layout_define.dart'; + +class PromptInputDesktopToggleFormatButton extends StatelessWidget { + const PromptInputDesktopToggleFormatButton({ + super.key, + required this.showFormatBar, + required this.onTap, + }); + + final bool showFormatBar; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return FlowyIconButton( + tooltipText: showFormatBar + ? LocaleKeys.chat_changeFormat_defaultDescription.tr() + : LocaleKeys.chat_changeFormat_blankDescription.tr(), + width: 28.0, + onPressed: onTap, + icon: showFormatBar + ? const FlowySvg( + FlowySvgs.m_aa_text_s, + size: Size.square(16.0), + color: Color(0xFF666D76), + ) + : const FlowySvg( + FlowySvgs.ai_text_image_s, + size: Size(21.0, 16.0), + color: Color(0xFF666D76), + ), + ); + } +} + +class ChangeFormatBar extends StatelessWidget { + const ChangeFormatBar({ + super.key, + required this.predefinedFormat, + required this.spacing, + required this.onSelectPredefinedFormat, + this.showImageFormats = true, + }); + + final PredefinedFormat? predefinedFormat; + final double spacing; + final void Function(PredefinedFormat) onSelectPredefinedFormat; + final bool showImageFormats; + + @override + Widget build(BuildContext context) { + final showTextFormats = predefinedFormat?.imageFormat.hasText ?? true; + return SizedBox( + height: DesktopAIPromptSizes.predefinedFormatButtonHeight, + child: SeparatedRow( + mainAxisSize: MainAxisSize.min, + separatorBuilder: () => HSpace(spacing), + children: [ + if (showImageFormats) ...[ + _buildFormatButton(context, ImageFormat.text), + _buildFormatButton(context, ImageFormat.textAndImage), + _buildFormatButton(context, ImageFormat.image), + ], + if (showImageFormats && showTextFormats) _buildDivider(), + if (showTextFormats) ...[ + _buildTextFormatButton(context, TextFormat.paragraph), + _buildTextFormatButton(context, TextFormat.bulletList), + _buildTextFormatButton(context, TextFormat.numberedList), + _buildTextFormatButton(context, TextFormat.table), + ], + ], + ), + ); + } + + Widget _buildFormatButton(BuildContext context, ImageFormat format) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + if (predefinedFormat != null && + format == predefinedFormat!.imageFormat) { + return; + } + if (format.hasText) { + final textFormat = + predefinedFormat?.textFormat ?? TextFormat.paragraph; + onSelectPredefinedFormat( + PredefinedFormat(imageFormat: format, textFormat: textFormat), + ); + } else { + onSelectPredefinedFormat( + PredefinedFormat(imageFormat: format, textFormat: null), + ); + } + }, + child: FlowyTooltip( + message: format.i18n, + preferBelow: false, + child: SizedBox.square( + dimension: _buttonSize, + child: FlowyHover( + isSelected: () => format == predefinedFormat?.imageFormat, + child: Center( + child: FlowySvg( + format.icon, + size: format == ImageFormat.textAndImage + ? Size(21.0 / 16.0 * _iconSize, _iconSize) + : Size.square(_iconSize), + ), + ), + ), + ), + ), + ); + } + + Widget _buildDivider() { + return VerticalDivider( + indent: 6.0, + endIndent: 6.0, + width: 1.0 + spacing * 2, + ); + } + + Widget _buildTextFormatButton( + BuildContext context, + TextFormat format, + ) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + if (predefinedFormat != null && + format == predefinedFormat!.textFormat) { + return; + } + onSelectPredefinedFormat( + PredefinedFormat( + imageFormat: predefinedFormat?.imageFormat ?? ImageFormat.text, + textFormat: format, + ), + ); + }, + child: FlowyTooltip( + message: format.i18n, + preferBelow: false, + child: SizedBox.square( + dimension: _buttonSize, + child: FlowyHover( + isSelected: () => format == predefinedFormat?.textFormat, + child: Center( + child: FlowySvg( + format.icon, + size: Size.square(_iconSize), + ), + ), + ), + ), + ), + ); + } + + double get _buttonSize { + return UniversalPlatform.isMobile + ? MobileAIPromptSizes.predefinedFormatButtonHeight + : DesktopAIPromptSizes.predefinedFormatButtonHeight; + } + + double get _iconSize { + return UniversalPlatform.isMobile + ? MobileAIPromptSizes.predefinedFormatIconHeight + : DesktopAIPromptSizes.predefinedFormatIconHeight; + } +} + +class PromptInputMobileToggleFormatButton extends StatelessWidget { + const PromptInputMobileToggleFormatButton({ + super.key, + required this.showFormatBar, + required this.onTap, + }); + + final bool showFormatBar; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return SizedBox.square( + dimension: 32.0, + child: FlowyButton( + radius: const BorderRadius.all(Radius.circular(8.0)), + margin: EdgeInsets.zero, + expandText: false, + text: showFormatBar + ? const FlowySvg( + FlowySvgs.m_aa_text_s, + size: Size.square(20.0), + ) + : const FlowySvg( + FlowySvgs.ai_text_image_s, + size: Size(26.25, 20.0), + ), + onTap: onTap, + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/select_model_menu.dart b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/select_model_menu.dart new file mode 100644 index 0000000000..a611d84310 --- /dev/null +++ b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/select_model_menu.dart @@ -0,0 +1,264 @@ +import 'package:appflowy/ai/ai.dart'; +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy_backend/protobuf/flowy-ai/protobuf.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flowy_infra_ui/style_widget/hover.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class SelectModelMenu extends StatefulWidget { + const SelectModelMenu({ + super.key, + required this.aiModelStateNotifier, + }); + + final AIModelStateNotifier aiModelStateNotifier; + + @override + State createState() => _SelectModelMenuState(); +} + +class _SelectModelMenuState extends State { + final popoverController = PopoverController(); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => SelectModelBloc( + aiModelStateNotifier: widget.aiModelStateNotifier, + ), + child: BlocBuilder( + builder: (context, state) { + return AppFlowyPopover( + offset: Offset(-12.0, 0.0), + constraints: BoxConstraints(maxWidth: 250, maxHeight: 600), + direction: PopoverDirection.topWithLeftAligned, + margin: EdgeInsets.zero, + controller: popoverController, + popupBuilder: (popoverContext) { + return SelectModelPopoverContent( + models: state.models, + selectedModel: state.selectedModel, + onSelectModel: (model) { + if (model != state.selectedModel) { + context + .read() + .add(SelectModelEvent.selectModel(model)); + } + popoverController.close(); + }, + ); + }, + child: _CurrentModelButton( + model: state.selectedModel, + onTap: () { + if (state.selectedModel != null) { + popoverController.show(); + } + }, + ), + ); + }, + ), + ); + } +} + +class SelectModelPopoverContent extends StatelessWidget { + const SelectModelPopoverContent({ + super.key, + required this.models, + required this.selectedModel, + this.onSelectModel, + }); + + final List models; + final AIModelPB? selectedModel; + final void Function(AIModelPB)? onSelectModel; + + @override + Widget build(BuildContext context) { + if (models.isEmpty) { + return const SizedBox.shrink(); + } + + // separate models into local and cloud models + final localModels = models.where((model) => model.isLocal).toList(); + final cloudModels = models.where((model) => !model.isLocal).toList(); + + return Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (localModels.isNotEmpty) ...[ + _ModelSectionHeader( + title: LocaleKeys.chat_switchModel_localModel.tr(), + ), + const VSpace(4.0), + ], + ...localModels.map( + (model) => _ModelItem( + model: model, + isSelected: model == selectedModel, + onTap: () => onSelectModel?.call(model), + ), + ), + if (cloudModels.isNotEmpty && localModels.isNotEmpty) ...[ + const VSpace(8.0), + _ModelSectionHeader( + title: LocaleKeys.chat_switchModel_cloudModel.tr(), + ), + const VSpace(4.0), + ], + ...cloudModels.map( + (model) => _ModelItem( + model: model, + isSelected: model == selectedModel, + onTap: () => onSelectModel?.call(model), + ), + ), + ], + ), + ); + } +} + +class _ModelSectionHeader extends StatelessWidget { + const _ModelSectionHeader({ + required this.title, + }); + + final String title; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(top: 4, bottom: 2), + child: FlowyText( + title, + fontSize: 12, + figmaLineHeight: 16, + color: Theme.of(context).hintColor, + fontWeight: FontWeight.w500, + ), + ); + } +} + +class _ModelItem extends StatelessWidget { + const _ModelItem({ + required this.model, + required this.isSelected, + required this.onTap, + }); + + final AIModelPB model; + final bool isSelected; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return ConstrainedBox( + constraints: const BoxConstraints(minHeight: 32), + child: FlowyButton( + onTap: onTap, + margin: EdgeInsets.symmetric(horizontal: 8.0, vertical: 6.0), + text: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FlowyText( + model.i18n, + figmaLineHeight: 20, + overflow: TextOverflow.ellipsis, + ), + if (model.desc.isNotEmpty) + FlowyText( + model.desc, + fontSize: 12, + figmaLineHeight: 16, + color: Theme.of(context).hintColor, + overflow: TextOverflow.ellipsis, + ), + ], + ), + rightIcon: isSelected + ? FlowySvg( + FlowySvgs.check_s, + size: const Size.square(20), + color: Theme.of(context).colorScheme.primary, + ) + : null, + ), + ); + } +} + +class _CurrentModelButton extends StatelessWidget { + const _CurrentModelButton({ + required this.model, + required this.onTap, + }); + + final AIModelPB? model; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return FlowyTooltip( + message: LocaleKeys.chat_switchModel_label.tr(), + child: GestureDetector( + onTap: onTap, + behavior: HitTestBehavior.opaque, + child: SizedBox( + height: DesktopAIPromptSizes.actionBarButtonSize, + child: AnimatedSize( + duration: const Duration(milliseconds: 50), + curve: Curves.easeInOut, + alignment: AlignmentDirectional.centerStart, + child: FlowyHover( + style: const HoverStyle( + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + child: Padding( + padding: const EdgeInsetsDirectional.all(4.0), + child: Row( + children: [ + Padding( + // TODO: remove this after change icon to 20px + padding: EdgeInsets.all(2), + child: FlowySvg( + FlowySvgs.ai_sparks_s, + color: Theme.of(context).hintColor, + size: Size.square(16), + ), + ), + if (model != null && !model!.isDefault) + Padding( + padding: EdgeInsetsDirectional.only(end: 2.0), + child: FlowyText( + model!.i18n, + fontSize: 12, + figmaLineHeight: 16, + color: Theme.of(context).hintColor, + overflow: TextOverflow.ellipsis, + ), + ), + FlowySvg( + FlowySvgs.ai_source_drop_down_s, + color: Theme.of(context).hintColor, + size: const Size.square(8), + ), + ], + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/select_sources_bottom_sheet.dart b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/select_sources_bottom_sheet.dart new file mode 100644 index 0000000000..1f1b2ddf4c --- /dev/null +++ b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/select_sources_bottom_sheet.dart @@ -0,0 +1,259 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/mobile/presentation/base/flowy_search_text_field.dart'; +import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; +import 'package:appflowy/plugins/ai_chat/application/chat_select_sources_cubit.dart'; +import 'package:appflowy/plugins/base/drag_handler.dart'; +import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart'; +import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; +import 'package:appflowy/workspace/application/view/view_ext.dart'; +import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'select_sources_menu.dart'; + +class PromptInputMobileSelectSourcesButton extends StatefulWidget { + const PromptInputMobileSelectSourcesButton({ + super.key, + required this.selectedSourcesNotifier, + required this.onUpdateSelectedSources, + }); + + final ValueNotifier> selectedSourcesNotifier; + final void Function(List) onUpdateSelectedSources; + + @override + State createState() => + _PromptInputMobileSelectSourcesButtonState(); +} + +class _PromptInputMobileSelectSourcesButtonState + extends State { + late final cubit = ChatSettingsCubit(); + + @override + void initState() { + super.initState(); + widget.selectedSourcesNotifier.addListener(onSelectedSourcesChanged); + WidgetsBinding.instance.addPostFrameCallback((_) { + onSelectedSourcesChanged(); + }); + } + + @override + void dispose() { + widget.selectedSourcesNotifier.removeListener(onSelectedSourcesChanged); + cubit.close(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + final userProfile = context.read().userProfile; + final workspaceId = state.currentWorkspace?.workspaceId ?? ''; + return MultiBlocProvider( + providers: [ + BlocProvider( + key: ValueKey(workspaceId), + create: (context) => SpaceBloc( + userProfile: userProfile, + workspaceId: workspaceId, + )..add(const SpaceEvent.initial(openFirstPage: false)), + ), + BlocProvider.value( + value: cubit, + ), + ], + child: BlocBuilder( + builder: (context, state) { + return FlowyButton( + margin: const EdgeInsetsDirectional.fromSTEB(4, 6, 2, 6), + expandText: false, + text: Row( + mainAxisSize: MainAxisSize.min, + children: [ + FlowySvg( + FlowySvgs.ai_page_s, + color: Theme.of(context).iconTheme.color, + size: const Size.square(20.0), + ), + FlowySvg( + FlowySvgs.ai_source_drop_down_s, + color: Theme.of(context).hintColor, + size: const Size.square(10), + ), + ], + ), + onTap: () async { + context + .read() + .refreshSources(state.spaces, state.currentSpace); + await showMobileBottomSheet( + context, + backgroundColor: Theme.of(context).colorScheme.surface, + maxChildSize: 0.98, + enableDraggableScrollable: true, + scrollableWidgetBuilder: (_, scrollController) { + return Expanded( + child: BlocProvider.value( + value: cubit, + child: _MobileSelectSourcesSheetBody( + scrollController: scrollController, + ), + ), + ); + }, + builder: (context) => const SizedBox.shrink(), + ); + if (context.mounted) { + widget.onUpdateSelectedSources(cubit.selectedSourceIds); + } + }, + ); + }, + ), + ); + }, + ); + } + + void onSelectedSourcesChanged() { + cubit + ..updateSelectedSources(widget.selectedSourcesNotifier.value) + ..updateSelectedStatus(); + } +} + +class _MobileSelectSourcesSheetBody extends StatefulWidget { + const _MobileSelectSourcesSheetBody({ + required this.scrollController, + }); + + final ScrollController scrollController; + + @override + State<_MobileSelectSourcesSheetBody> createState() => + _MobileSelectSourcesSheetBodyState(); +} + +class _MobileSelectSourcesSheetBodyState + extends State<_MobileSelectSourcesSheetBody> { + final textController = TextEditingController(); + + @override + void dispose() { + textController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return CustomScrollView( + controller: widget.scrollController, + shrinkWrap: true, + slivers: [ + SliverPersistentHeader( + pinned: true, + delegate: _Header( + child: ColoredBox( + color: Theme.of(context).cardColor, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const DragHandle(), + SizedBox( + height: 44.0, + child: Center( + child: FlowyText.medium( + LocaleKeys.chat_selectSources.tr(), + fontSize: 16.0, + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 8.0, + ), + child: SizedBox( + height: 44.0, + child: FlowySearchTextField( + controller: textController, + onChanged: (value) => context + .read() + .updateFilter(value), + ), + ), + ), + const Divider(height: 0.5, thickness: 0.5), + ], + ), + ), + ), + ), + BlocBuilder( + builder: (context, state) { + final sources = state.visibleSources + .where((e) => e.ignoreStatus != IgnoreViewType.hide); + return SliverList( + delegate: SliverChildBuilderDelegate( + childCount: sources.length, + (context, index) { + final source = sources.elementAt(index); + return ChatSourceTreeItem( + key: ValueKey( + 'visible_select_sources_tree_item_${source.view.id}', + ), + chatSource: source, + level: 0, + isDescendentOfSpace: source.view.isSpace, + isSelectedSection: false, + onSelected: (chatSource) { + context + .read() + .toggleSelectedStatus(chatSource); + }, + height: 40.0, + ); + }, + ), + ); + }, + ), + ], + ); + } +} + +class _Header extends SliverPersistentHeaderDelegate { + const _Header({ + required this.child, + }); + + final Widget child; + + @override + Widget build( + BuildContext context, + double shrinkOffset, + bool overlapsContent, + ) { + return child; + } + + @override + double get maxExtent => 120.5; + + @override + double get minExtent => 120.5; + + @override + bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) { + return false; + } +} diff --git a/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/select_sources_menu.dart b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/select_sources_menu.dart new file mode 100644 index 0000000000..51357e6a0b --- /dev/null +++ b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/select_sources_menu.dart @@ -0,0 +1,588 @@ +import 'dart:math'; + +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/ai_chat/application/chat_select_sources_cubit.dart'; +import 'package:appflowy/plugins/document/application/document_bloc.dart'; +import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart'; +import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; +import 'package:appflowy/workspace/application/view/view_ext.dart'; +import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart'; +import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/theme_extension.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flowy_infra_ui/style_widget/hover.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'layout_define.dart'; +import 'mention_page_menu.dart'; + +class PromptInputDesktopSelectSourcesButton extends StatefulWidget { + const PromptInputDesktopSelectSourcesButton({ + super.key, + required this.selectedSourcesNotifier, + required this.onUpdateSelectedSources, + }); + + final ValueNotifier> selectedSourcesNotifier; + final void Function(List) onUpdateSelectedSources; + + @override + State createState() => + _PromptInputDesktopSelectSourcesButtonState(); +} + +class _PromptInputDesktopSelectSourcesButtonState + extends State { + late final cubit = ChatSettingsCubit(); + final popoverController = PopoverController(); + + @override + void initState() { + super.initState(); + widget.selectedSourcesNotifier.addListener(onSelectedSourcesChanged); + WidgetsBinding.instance.addPostFrameCallback((_) { + onSelectedSourcesChanged(); + }); + } + + @override + void dispose() { + widget.selectedSourcesNotifier.removeListener(onSelectedSourcesChanged); + cubit.close(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final userWorkspaceBloc = context.read(); + final userProfile = userWorkspaceBloc.userProfile; + final workspaceId = + userWorkspaceBloc.state.currentWorkspace?.workspaceId ?? ''; + + return MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => SpaceBloc( + userProfile: userProfile, + workspaceId: workspaceId, + )..add(const SpaceEvent.initial(openFirstPage: false)), + ), + BlocProvider.value( + value: cubit, + ), + ], + child: BlocBuilder( + builder: (context, state) { + return AppFlowyPopover( + constraints: BoxConstraints.loose(const Size(320, 380)), + offset: const Offset(0.0, -10.0), + direction: PopoverDirection.topWithCenterAligned, + margin: EdgeInsets.zero, + controller: popoverController, + onOpen: () { + context + .read() + .refreshSources(state.spaces, state.currentSpace); + }, + onClose: () { + widget.onUpdateSelectedSources(cubit.selectedSourceIds); + context + .read() + .refreshSources(state.spaces, state.currentSpace); + }, + popupBuilder: (_) { + return BlocProvider.value( + value: context.read(), + child: const _PopoverContent(), + ); + }, + child: _IndicatorButton( + selectedSourcesNotifier: widget.selectedSourcesNotifier, + onTap: () => popoverController.show(), + ), + ); + }, + ), + ); + } + + void onSelectedSourcesChanged() { + cubit + ..updateSelectedSources(widget.selectedSourcesNotifier.value) + ..updateSelectedStatus(); + } +} + +class _IndicatorButton extends StatelessWidget { + const _IndicatorButton({ + required this.selectedSourcesNotifier, + required this.onTap, + }); + + final ValueNotifier> selectedSourcesNotifier; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + behavior: HitTestBehavior.opaque, + child: SizedBox( + height: DesktopAIPromptSizes.actionBarButtonSize, + child: FlowyHover( + style: const HoverStyle( + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + child: Padding( + padding: const EdgeInsetsDirectional.fromSTEB(6, 6, 4, 6), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + FlowySvg( + FlowySvgs.ai_page_s, + color: Theme.of(context).hintColor, + ), + const HSpace(2.0), + ValueListenableBuilder( + valueListenable: selectedSourcesNotifier, + builder: (context, selectedSourceIds, _) { + final documentId = + context.read()?.documentId; + final label = documentId != null && + selectedSourceIds.length == 1 && + selectedSourceIds[0] == documentId + ? LocaleKeys.chat_currentPage.tr() + : selectedSourceIds.length.toString(); + return FlowyText( + label, + fontSize: 12, + figmaLineHeight: 16, + color: Theme.of(context).hintColor, + ); + }, + ), + const HSpace(2.0), + FlowySvg( + FlowySvgs.ai_source_drop_down_s, + color: Theme.of(context).hintColor, + size: const Size.square(8), + ), + ], + ), + ), + ), + ), + ); + } +} + +class _PopoverContent extends StatelessWidget { + const _PopoverContent(); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(8, 12, 8, 8), + child: SpaceSearchField( + width: 600, + onSearch: (context, value) => + context.read().updateFilter(value), + ), + ), + _buildDivider(), + Flexible( + child: ListView( + shrinkWrap: true, + padding: const EdgeInsets.fromLTRB(8, 4, 8, 12), + children: [ + ..._buildSelectedSources(context, state), + if (state.selectedSources.isNotEmpty && + state.visibleSources.isNotEmpty) + Padding( + padding: const EdgeInsets.symmetric(vertical: 4.0), + child: _buildDivider(), + ), + ..._buildVisibleSources(context, state), + ], + ), + ), + ], + ); + }, + ); + } + + Widget _buildDivider() { + return const Divider( + height: 1.0, + thickness: 1.0, + indent: 8.0, + endIndent: 8.0, + ); + } + + Iterable _buildSelectedSources( + BuildContext context, + ChatSettingsState state, + ) { + return state.selectedSources + .where((e) => e.ignoreStatus != IgnoreViewType.hide) + .map( + (e) => ChatSourceTreeItem( + key: ValueKey( + 'selected_select_sources_tree_item_${e.view.id}', + ), + chatSource: e, + level: 0, + isDescendentOfSpace: e.view.isSpace, + isSelectedSection: true, + onSelected: (chatSource) { + context + .read() + .toggleSelectedStatus(chatSource); + }, + height: 30.0, + ), + ); + } + + Iterable _buildVisibleSources( + BuildContext context, + ChatSettingsState state, + ) { + return state.visibleSources + .where((e) => e.ignoreStatus != IgnoreViewType.hide) + .map( + (e) => ChatSourceTreeItem( + key: ValueKey( + 'visible_select_sources_tree_item_${e.view.id}', + ), + chatSource: e, + level: 0, + isDescendentOfSpace: e.view.isSpace, + isSelectedSection: false, + onSelected: (chatSource) { + context + .read() + .toggleSelectedStatus(chatSource); + }, + height: 30.0, + ), + ); + } +} + +class ChatSourceTreeItem extends StatefulWidget { + const ChatSourceTreeItem({ + super.key, + required this.chatSource, + required this.level, + required this.isDescendentOfSpace, + required this.isSelectedSection, + required this.onSelected, + this.onAdd, + required this.height, + this.showSaveButton = false, + this.showCheckbox = true, + }); + + final ChatSource chatSource; + + /// nested level of the view item + final int level; + + final bool isDescendentOfSpace; + + final bool isSelectedSection; + + final void Function(ChatSource chatSource) onSelected; + + final void Function(ChatSource chatSource)? onAdd; + + final bool showSaveButton; + + final double height; + + final bool showCheckbox; + + @override + State createState() => _ChatSourceTreeItemState(); +} + +class _ChatSourceTreeItemState extends State { + @override + Widget build(BuildContext context) { + final child = SizedBox( + height: widget.height, + child: ChatSourceTreeItemInner( + chatSource: widget.chatSource, + level: widget.level, + isDescendentOfSpace: widget.isDescendentOfSpace, + isSelectedSection: widget.isSelectedSection, + showCheckbox: widget.showCheckbox, + showSaveButton: widget.showSaveButton, + onSelected: widget.onSelected, + onAdd: widget.onAdd, + ), + ); + + final disabledEnabledChild = + widget.chatSource.ignoreStatus == IgnoreViewType.disable + ? FlowyTooltip( + message: widget.showCheckbox + ? switch (widget.chatSource.view.layout) { + ViewLayoutPB.Document => + LocaleKeys.chat_sourcesLimitReached.tr(), + _ => LocaleKeys.chat_sourceUnsupported.tr(), + } + : "", + child: Opacity( + opacity: 0.5, + child: MouseRegion( + cursor: SystemMouseCursors.forbidden, + child: IgnorePointer(child: child), + ), + ), + ) + : child; + + return ValueListenableBuilder( + valueListenable: widget.chatSource.isExpandedNotifier, + builder: (context, isExpanded, child) { + // filter the child views that should be ignored + final childViews = widget.chatSource.children + .where((e) => e.ignoreStatus != IgnoreViewType.hide) + .toList(); + + if (!isExpanded || childViews.isEmpty) { + return disabledEnabledChild; + } + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + disabledEnabledChild, + ...childViews.map( + (childSource) => ChatSourceTreeItem( + key: ValueKey( + 'select_sources_tree_item_${childSource.view.id}', + ), + chatSource: childSource, + level: widget.level + 1, + isDescendentOfSpace: widget.isDescendentOfSpace, + isSelectedSection: widget.isSelectedSection, + onSelected: widget.onSelected, + height: widget.height, + showCheckbox: widget.showCheckbox, + showSaveButton: widget.showSaveButton, + onAdd: widget.onAdd, + ), + ), + ], + ); + }, + ); + } +} + +class ChatSourceTreeItemInner extends StatelessWidget { + const ChatSourceTreeItemInner({ + super.key, + required this.chatSource, + required this.level, + required this.isDescendentOfSpace, + required this.isSelectedSection, + required this.showCheckbox, + required this.showSaveButton, + this.onSelected, + this.onAdd, + }); + + final ChatSource chatSource; + final int level; + final bool isDescendentOfSpace; + final bool isSelectedSection; + final bool showCheckbox; + final bool showSaveButton; + final void Function(ChatSource)? onSelected; + final void Function(ChatSource)? onAdd; + + @override + Widget build(BuildContext context) { + return GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () { + if (!isSelectedSection) { + onSelected?.call(chatSource); + } + }, + child: FlowyHover( + cursor: isSelectedSection ? SystemMouseCursors.basic : null, + style: HoverStyle( + hoverColor: isSelectedSection + ? Colors.transparent + : AFThemeExtension.of(context).lightGreyHover, + ), + builder: (context, onHover) { + final isSaveButtonVisible = + showSaveButton && !chatSource.view.isSpace; + final isAddButtonVisible = onAdd != null; + return Row( + children: [ + const HSpace(4.0), + HSpace(max(20.0 * level - (isDescendentOfSpace ? 2 : 0), 0)), + // builds the >, ^ or · button + ToggleIsExpandedButton( + chatSource: chatSource, + isSelectedSection: isSelectedSection, + ), + const HSpace(2.0), + // checkbox + if (!chatSource.view.isSpace && showCheckbox) ...[ + SourceSelectedStatusCheckbox( + chatSource: chatSource, + ), + const HSpace(4.0), + ], + // icon + MentionViewIcon( + view: chatSource.view, + ), + const HSpace(6.0), + // title + Expanded( + child: FlowyText( + chatSource.view.nameOrDefault, + overflow: TextOverflow.ellipsis, + fontSize: 14.0, + figmaLineHeight: 18.0, + ), + ), + if (onHover && (isSaveButtonVisible || isAddButtonVisible)) ...[ + const HSpace(4.0), + if (isSaveButtonVisible) + FlowyIconButton( + tooltipText: LocaleKeys.chat_addToPageButton.tr(), + width: 24, + icon: FlowySvg( + FlowySvgs.ai_add_to_page_s, + size: const Size.square(16), + color: Theme.of(context).hintColor, + ), + onPressed: () => onSelected?.call(chatSource), + ), + if (isSaveButtonVisible && isAddButtonVisible) + const HSpace(4.0), + if (isAddButtonVisible) + FlowyIconButton( + tooltipText: LocaleKeys.chat_addToNewPage.tr(), + width: 24, + icon: FlowySvg( + FlowySvgs.add_less_padding_s, + size: const Size.square(16), + color: Theme.of(context).hintColor, + ), + onPressed: () => onAdd?.call(chatSource), + ), + const HSpace(4.0), + ], + ], + ); + }, + ), + ); + } +} + +class ToggleIsExpandedButton extends StatelessWidget { + const ToggleIsExpandedButton({ + super.key, + required this.chatSource, + required this.isSelectedSection, + }); + + final ChatSource chatSource; + final bool isSelectedSection; + + @override + Widget build(BuildContext context) { + if (isReferencedDatabaseView(chatSource.view, chatSource.parentView)) { + return const _DotIconWidget(); + } + + if (chatSource.children.isEmpty) { + return const SizedBox.square(dimension: 16.0); + } + + return FlowyHover( + child: GestureDetector( + child: ValueListenableBuilder( + valueListenable: chatSource.isExpandedNotifier, + builder: (context, value, _) => FlowySvg( + value + ? FlowySvgs.view_item_expand_s + : FlowySvgs.view_item_unexpand_s, + size: const Size.square(16.0), + ), + ), + onTap: () => context + .read() + .toggleIsExpanded(chatSource, isSelectedSection), + ), + ); + } +} + +class _DotIconWidget extends StatelessWidget { + const _DotIconWidget(); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(6.0), + child: Container( + width: 4, + height: 4, + decoration: BoxDecoration( + color: Theme.of(context).iconTheme.color, + borderRadius: BorderRadius.circular(2), + ), + ), + ); + } +} + +class SourceSelectedStatusCheckbox extends StatelessWidget { + const SourceSelectedStatusCheckbox({ + super.key, + required this.chatSource, + }); + + final ChatSource chatSource; + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: chatSource.selectedStatusNotifier, + builder: (context, selectedStatus, _) => FlowySvg( + switch (selectedStatus) { + SourceSelectedStatus.unselected => FlowySvgs.uncheck_s, + SourceSelectedStatus.selected => FlowySvgs.check_filled_s, + SourceSelectedStatus.partiallySelected => FlowySvgs.check_partial_s, + }, + size: const Size.square(18.0), + blendMode: null, + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/send_button.dart b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/send_button.dart new file mode 100644 index 0000000000..cca6e65f63 --- /dev/null +++ b/frontend/appflowy_flutter/lib/ai/widgets/prompt_input/send_button.dart @@ -0,0 +1,89 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:universal_platform/universal_platform.dart'; + +import 'layout_define.dart'; + +enum SendButtonState { enabled, streaming, disabled } + +class PromptInputSendButton extends StatelessWidget { + const PromptInputSendButton({ + super.key, + required this.state, + required this.onSendPressed, + required this.onStopStreaming, + }); + + final SendButtonState state; + final VoidCallback onSendPressed; + final VoidCallback onStopStreaming; + + @override + Widget build(BuildContext context) { + return FlowyIconButton( + width: _buttonSize, + richTooltipText: switch (state) { + SendButtonState.streaming => TextSpan( + children: [ + TextSpan( + text: '${LocaleKeys.chat_stopTooltip.tr()} ', + style: context.tooltipTextStyle(), + ), + TextSpan( + text: 'ESC', + style: context + .tooltipTextStyle() + ?.copyWith(color: Theme.of(context).hintColor), + ), + ], + ), + _ => null, + }, + icon: switch (state) { + SendButtonState.enabled => FlowySvg( + FlowySvgs.ai_send_filled_s, + size: Size.square(_iconSize), + color: Theme.of(context).colorScheme.primary, + ), + SendButtonState.disabled => FlowySvg( + FlowySvgs.ai_send_filled_s, + size: Size.square(_iconSize), + color: Theme.of(context).disabledColor, + ), + SendButtonState.streaming => FlowySvg( + FlowySvgs.ai_stop_filled_s, + size: Size.square(_iconSize), + color: Theme.of(context).colorScheme.primary, + ), + }, + onPressed: () { + switch (state) { + case SendButtonState.enabled: + onSendPressed(); + break; + case SendButtonState.streaming: + onStopStreaming(); + break; + case SendButtonState.disabled: + break; + } + }, + hoverColor: Colors.transparent, + ); + } + + double get _buttonSize { + return UniversalPlatform.isMobile + ? MobileAIPromptSizes.sendButtonSize + : DesktopAIPromptSizes.actionBarSendButtonSize; + } + + double get _iconSize { + return UniversalPlatform.isMobile + ? MobileAIPromptSizes.sendButtonSize + : DesktopAIPromptSizes.actionBarSendButtonIconSize; + } +} diff --git a/frontend/appflowy_flutter/lib/core/config/kv_keys.dart b/frontend/appflowy_flutter/lib/core/config/kv_keys.dart index a80d08b76e..aefd5e5d36 100644 --- a/frontend/appflowy_flutter/lib/core/config/kv_keys.dart +++ b/frontend/appflowy_flutter/lib/core/config/kv_keys.dart @@ -58,6 +58,7 @@ class KVKeys { static const String kCloudType = 'kCloudType'; static const String kAppflowyCloudBaseURL = 'kAppFlowyCloudBaseURL'; + static const String kAppFlowyBaseShareDomain = 'kAppFlowyBaseShareDomain'; static const String kAppFlowyEnableSyncTrace = 'kAppFlowyEnableSyncTrace'; /// The key for saving the text scale factor. @@ -114,4 +115,9 @@ class KVKeys { /// /// The value is a json string of [RecentIcons] static const String recentIcons = 'kRecentIcons'; + + /// The key for saving compact mode ids for node or databse view + /// + /// The value is a json list of id + static const String compactModeIds = 'compactModeIds'; } diff --git a/frontend/appflowy_flutter/lib/core/helpers/url_launcher.dart b/frontend/appflowy_flutter/lib/core/helpers/url_launcher.dart index 2b0bc7b345..fd8aa03dfe 100644 --- a/frontend/appflowy_flutter/lib/core/helpers/url_launcher.dart +++ b/frontend/appflowy_flutter/lib/core/helpers/url_launcher.dart @@ -10,6 +10,7 @@ import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:open_filex/open_filex.dart'; import 'package:string_validator/string_validator.dart'; +import 'package:universal_platform/universal_platform.dart'; import 'package:url_launcher/url_launcher.dart' as launcher; typedef OnFailureCallback = void Function(Uri uri); @@ -38,17 +39,32 @@ Future afLaunchUri( ); } - // try to launch the uri directly - bool result; - try { - result = await launcher.launchUrl( - uri, - mode: mode, - webOnlyWindowName: webOnlyWindowName, - ); - } on PlatformException catch (e) { - Log.error('Failed to open uri: $e'); - return false; + // on Linux, add http scheme to the url if it is not present + if (UniversalPlatform.isLinux && !isURL(url, {'require_protocol': true})) { + uri = Uri.parse('https://$url'); + } + + /// opening an incorrect link will cause a system error dialog to pop up on macOS + /// only use [canLaunchUrl] on macOS + /// and there is an known issue with url_launcher on Linux where it fails to launch + /// see https://github.com/flutter/flutter/issues/88463 + bool result = true; + if (UniversalPlatform.isMacOS) { + result = await launcher.canLaunchUrl(uri); + } + + if (result) { + try { + // try to launch the uri directly + result = await launcher.launchUrl( + uri, + mode: mode, + webOnlyWindowName: webOnlyWindowName, + ); + } on PlatformException catch (e) { + Log.error('Failed to open uri: $e'); + return false; + } } // if the uri is not a valid url, try to launch it with http scheme @@ -127,7 +143,6 @@ Future _afLaunchLocalUri( }; if (context != null && context.mounted) { showToastNotification( - context, message: message, type: result.type == ResultType.done ? ToastificationType.success diff --git a/frontend/appflowy_flutter/lib/env/backend_env.dart b/frontend/appflowy_flutter/lib/env/backend_env.dart index a3f45f9f60..eb8a61d037 100644 --- a/frontend/appflowy_flutter/lib/env/backend_env.dart +++ b/frontend/appflowy_flutter/lib/env/backend_env.dart @@ -1,6 +1,8 @@ // ignore_for_file: non_constant_identifier_names +import 'package:appflowy/plugins/shared/share/constants.dart'; import 'package:json_annotation/json_annotation.dart'; + part 'backend_env.g.dart'; @JsonSerializable() @@ -40,6 +42,7 @@ class AppFlowyCloudConfiguration { required this.ws_base_url, required this.gotrue_url, required this.enable_sync_trace, + required this.base_web_domain, }); factory AppFlowyCloudConfiguration.fromJson(Map json) => @@ -50,6 +53,13 @@ class AppFlowyCloudConfiguration { final String gotrue_url; final bool enable_sync_trace; + /// The base domain is used in + /// + /// - Share URL + /// - Publish URL + /// - Copy Link To Block + final String base_web_domain; + Map toJson() => _$AppFlowyCloudConfigurationToJson(this); static AppFlowyCloudConfiguration defaultConfig() { @@ -58,6 +68,7 @@ class AppFlowyCloudConfiguration { ws_base_url: '', gotrue_url: '', enable_sync_trace: false, + base_web_domain: ShareConstants.defaultBaseWebDomain, ); } diff --git a/frontend/appflowy_flutter/lib/env/cloud_env.dart b/frontend/appflowy_flutter/lib/env/cloud_env.dart index 86b9636a93..15f3ada42e 100644 --- a/frontend/appflowy_flutter/lib/env/cloud_env.dart +++ b/frontend/appflowy_flutter/lib/env/cloud_env.dart @@ -2,6 +2,7 @@ import 'package:appflowy/core/config/kv.dart'; import 'package:appflowy/core/config/kv_keys.dart'; import 'package:appflowy/env/backend_env.dart'; import 'package:appflowy/env/env.dart'; +import 'package:appflowy/plugins/shared/share/constants.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy_backend/log.dart'; @@ -99,6 +100,10 @@ bool get isAuthEnabled { return false; } +bool get isLocalAuthEnabled { + return currentCloudType().isLocal; +} + /// Determines if AppFlowy Cloud is enabled. bool get isAppFlowyCloudEnabled { return currentCloudType().isAppFlowyCloudEnabled; @@ -155,6 +160,13 @@ Future _setAppFlowyCloudUrl(String? url) async { await getIt().set(KVKeys.kAppflowyCloudBaseURL, url ?? ''); } +Future useBaseWebDomain(String? url) async { + await getIt().set( + KVKeys.kAppFlowyBaseShareDomain, + url ?? ShareConstants.defaultBaseWebDomain, + ); +} + Future useSelfHostedAppFlowyCloudWithURL(String url) async { await _setAuthenticatorType(AuthenticatorType.appflowyCloudSelfHost); await _setAppFlowyCloudUrl(url); @@ -172,7 +184,7 @@ Future useLocalServer() async { await _setAuthenticatorType(AuthenticatorType.local); } -/// Use getIt() to get the shared environment. +// Use getIt() to get the shared environment. class AppFlowyCloudSharedEnv { AppFlowyCloudSharedEnv({ required AuthenticatorType authenticatorType, @@ -213,6 +225,7 @@ class AppFlowyCloudSharedEnv { ws_base_url: await _getAppFlowyCloudWSUrl(Env.afCloudUrl), gotrue_url: await _getAppFlowyCloudGotrueUrl(Env.afCloudUrl), enable_sync_trace: false, + base_web_domain: Env.baseWebDomain, ); return AppFlowyCloudSharedEnv( @@ -233,16 +246,19 @@ Future configurationFromUri( Uri baseUri, String baseUrl, AuthenticatorType authenticatorType, + String baseShareDomain, ) async { // In development mode, the app is configured to access the AppFlowy cloud server directly through specific ports. // This setup bypasses the need for Nginx, meaning that the AppFlowy cloud should be running without an Nginx server // in the development environment. + // If you modify following code, please update the corresponding documentation in the appflowy billing. if (authenticatorType == AuthenticatorType.appflowyCloudDevelop) { return AppFlowyCloudConfiguration( base_url: "$baseUrl:8000", ws_base_url: "ws://${baseUri.host}:8000/ws/v1", gotrue_url: "$baseUrl:9999", enable_sync_trace: true, + base_web_domain: ShareConstants.testBaseWebDomain, ); } else { return AppFlowyCloudConfiguration( @@ -250,6 +266,9 @@ Future configurationFromUri( ws_base_url: await _getAppFlowyCloudWSUrl(baseUrl), gotrue_url: await _getAppFlowyCloudGotrueUrl(baseUrl), enable_sync_trace: await getSyncLogEnabled(), + base_web_domain: authenticatorType == AuthenticatorType.appflowyCloud + ? ShareConstants.defaultBaseWebDomain + : baseShareDomain, ); } } @@ -258,10 +277,16 @@ Future getAppFlowyCloudConfig( AuthenticatorType authenticatorType, ) async { final baseURL = await getAppFlowyCloudUrl(); + final baseShareDomain = await getAppFlowyShareDomain(); try { final uri = Uri.parse(baseURL); - return await configurationFromUri(uri, baseURL, authenticatorType); + return await configurationFromUri( + uri, + baseURL, + authenticatorType, + baseShareDomain, + ); } catch (e) { Log.error("Failed to parse AppFlowy Cloud URL: $e"); return AppFlowyCloudConfiguration.defaultConfig(); @@ -274,6 +299,12 @@ Future getAppFlowyCloudUrl() async { return result ?? kAppflowyCloudUrl; } +Future getAppFlowyShareDomain() async { + final result = + await getIt().get(KVKeys.kAppFlowyBaseShareDomain); + return result ?? ShareConstants.defaultBaseWebDomain; +} + Future getSyncLogEnabled() async { final result = await getIt().get(KVKeys.kAppFlowyEnableSyncTrace); diff --git a/frontend/appflowy_flutter/lib/env/env.dart b/frontend/appflowy_flutter/lib/env/env.dart index cfd9837944..18434f9aa6 100644 --- a/frontend/appflowy_flutter/lib/env/env.dart +++ b/frontend/appflowy_flutter/lib/env/env.dart @@ -1,5 +1,6 @@ // lib/env/env.dart import 'package:appflowy/env/cloud_env.dart'; +import 'package:appflowy/plugins/shared/share/constants.dart'; import 'package:envied/envied.dart'; part 'env.g.dart'; @@ -43,4 +44,11 @@ abstract class Env { defaultValue: '', ) static const String sentryDsn = _Env.sentryDsn; + + @EnviedField( + obfuscate: false, + varName: 'BASE_WEB_DOMAIN', + defaultValue: ShareConstants.defaultBaseWebDomain, + ) + static const String baseWebDomain = _Env.baseWebDomain; } diff --git a/frontend/appflowy_flutter/lib/flutter/af_dropdown_menu.dart b/frontend/appflowy_flutter/lib/flutter/af_dropdown_menu.dart index 1cc846339b..157be012b1 100644 --- a/frontend/appflowy_flutter/lib/flutter/af_dropdown_menu.dart +++ b/frontend/appflowy_flutter/lib/flutter/af_dropdown_menu.dart @@ -16,6 +16,8 @@ const double _kMinimumWidth = 112.0; const double _kDefaultHorizontalPadding = 12.0; +typedef CompareFunction = bool Function(T? left, T? right); + // Navigation shortcuts to move the selected menu items up or down. final Map _kMenuTraversalShortcuts = { @@ -86,6 +88,7 @@ class AFDropdownMenu extends StatefulWidget { this.requestFocusOnTap, this.expandedInsets, this.searchCallback, + this.selectOptionCompare, required this.dropdownMenuEntries, }); @@ -267,6 +270,11 @@ class AFDropdownMenu extends StatefulWidget { /// which contains the contents of the text input field. final SearchCallback? searchCallback; + /// Defines the compare function for the menu items. + /// + /// Defaults to null. If this is null, the menu items will be sorted by the label. + final CompareFunction? selectOptionCompare; + @override State> createState() => _AFDropdownMenuState(); } @@ -301,7 +309,16 @@ class _AFDropdownMenuState extends State> { filteredEntries.any((DropdownMenuEntry entry) => entry.enabled); final int index = filteredEntries.indexWhere( - (DropdownMenuEntry entry) => entry.value == widget.initialSelection, + (DropdownMenuEntry entry) { + if (widget.selectOptionCompare != null) { + return widget.selectOptionCompare!( + entry.value, + widget.initialSelection, + ); + } else { + return entry.value == widget.initialSelection; + } + }, ); if (index != -1) { _textEditingController.value = TextEditingValue( @@ -502,11 +519,11 @@ class _AFDropdownMenuState extends State> { // Simulate the focused state because the text field should always be focused // during traversal. If the menu item has a custom foreground color, the "focused" - // color will also change to foregroundColor.withOpacity(0.12). + // color will also change to foregroundColor.withValues(alpha: 0.12). effectiveStyle = entry.enabled && i == focusedIndex ? effectiveStyle.copyWith( backgroundColor: WidgetStatePropertyAll( - focusedBackgroundColor.withOpacity(0.12), + focusedBackgroundColor.withValues(alpha: 0.12), ), ) : effectiveStyle; diff --git a/frontend/appflowy_flutter/lib/mobile/application/mobile_router.dart b/frontend/appflowy_flutter/lib/mobile/application/mobile_router.dart index 8fc32ab5db..aa02495a49 100644 --- a/frontend/appflowy_flutter/lib/mobile/application/mobile_router.dart +++ b/frontend/appflowy_flutter/lib/mobile/application/mobile_router.dart @@ -21,6 +21,7 @@ extension MobileRouter on BuildContext { bool showMoreButton = true, String? fixedTitle, String? blockId, + List? tabs, }) async { // set the current view before pushing the new view getIt().latestOpenView = view; @@ -37,6 +38,9 @@ extension MobileRouter on BuildContext { queryParameters[MobileDocumentScreen.viewBlockId] = blockId; } } + if (tabs != null) { + queryParameters[MobileDocumentScreen.viewSelectTabs] = tabs.join('-'); + } final uri = Uri( path: view.routeName, diff --git a/frontend/appflowy_flutter/lib/mobile/application/user_profile/user_profile_bloc.dart b/frontend/appflowy_flutter/lib/mobile/application/user_profile/user_profile_bloc.dart index 1480cc02e9..0527316860 100644 --- a/frontend/appflowy_flutter/lib/mobile/application/user_profile/user_profile_bloc.dart +++ b/frontend/appflowy_flutter/lib/mobile/application/user_profile/user_profile_bloc.dart @@ -19,14 +19,13 @@ class UserProfileBloc extends Bloc { Future _initialize(Emitter emit) async { emit(const UserProfileState.loading()); - - final workspaceOrFailure = + final latestOrFailure = await FolderEventGetCurrentWorkspaceSetting().send(); final userOrFailure = await getIt().getUser(); - final workspaceSetting = workspaceOrFailure.fold( - (workspaceSettingPB) => workspaceSettingPB, + final latest = latestOrFailure.fold( + (latestPB) => latestPB, (error) => null, ); @@ -35,13 +34,13 @@ class UserProfileBloc extends Bloc { (error) => null, ); - if (workspaceSetting == null || userProfile == null) { + if (latest == null || userProfile == null) { return emit(const UserProfileState.workspaceFailure()); } emit( UserProfileState.success( - workspaceSettings: workspaceSetting, + workspaceSettings: latest, userProfile: userProfile, ), ); @@ -59,7 +58,7 @@ class UserProfileState with _$UserProfileState { const factory UserProfileState.loading() = _Loading; const factory UserProfileState.workspaceFailure() = _WorkspaceFailure; const factory UserProfileState.success({ - required WorkspaceSettingPB workspaceSettings, + required WorkspaceLatestPB workspaceSettings, required UserProfilePB userProfile, }) = _Success; } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart index 6e0a77169b..318b06394a 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart @@ -1,3 +1,4 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/application/base/mobile_view_page_bloc.dart'; import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart'; @@ -7,9 +8,11 @@ import 'package:appflowy/mobile/presentation/presentation.dart'; import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_state_container.dart'; import 'package:appflowy/plugins/document/application/prelude.dart'; import 'package:appflowy/plugins/document/presentation/document_collaborators.dart'; +import 'package:appflowy/plugins/document/presentation/editor_notification.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart'; import 'package:appflowy/shared/feature_flags.dart'; import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/startup/plugin/plugin.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/user/application/reminder/reminder_bloc.dart'; @@ -17,6 +20,9 @@ import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart'; import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; import 'package:appflowy/workspace/application/view/view_bloc.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; +import 'package:appflowy/workspace/application/view/view_lock_status_bloc.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; +import 'package:appflowy/workspace/presentation/widgets/view_title_bar.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; @@ -34,6 +40,7 @@ class MobileViewPage extends StatefulWidget { this.fixedTitle, this.showMoreButton = true, this.blockId, + this.tabs = const [PickerTabType.emoji, PickerTabType.icon], }); /// view id @@ -43,6 +50,7 @@ class MobileViewPage extends StatefulWidget { final Map? arguments; final bool showMoreButton; final String? blockId; + final List tabs; // only used in row page final String? fixedTitle; @@ -88,7 +96,7 @@ class _MobileViewPageState extends State { final body = _buildBody(context, state); if (view == null) { - return _buildApp(context, null, body); + return SizedBox.shrink(); } return MultiBlocProvider( @@ -119,6 +127,11 @@ class _MobileViewPageState extends State { create: (_) => DocumentPageStyleBloc(view: view) ..add(const DocumentPageStyleEvent.initial()), ), + if (view.layout.isDocumentView || view.layout.isDatabaseView) + BlocProvider( + create: (_) => ViewLockStatusBloc(view: view) + ..add(const ViewLockStatusEvent.initial()), + ), ], child: Builder( builder: (context) { @@ -149,6 +162,7 @@ class _MobileViewPageState extends State { title: title, appBarOpacity: _appBarOpacity, actions: actions, + view: view, ) : FlowyAppBar(title: title, actions: actions); final body = isDocument @@ -193,6 +207,7 @@ class _MobileViewPageState extends State { data: { MobileDocumentScreen.viewFixedTitle: widget.fixedTitle, MobileDocumentScreen.viewBlockId: widget.blockId, + MobileDocumentScreen.viewSelectTabs: widget.tabs, }, ); }, @@ -218,6 +233,8 @@ class _MobileViewPageState extends State { final isImmersiveMode = context.read().state.isImmersiveMode; + final isLocked = + context.read()?.state.isLocked ?? false; final actions = []; if (FeatureFlag.syncDocument.isOn) { @@ -236,12 +253,13 @@ class _MobileViewPageState extends State { } } - if (view.layout.isDocumentView) { + if (view.layout.isDocumentView && !isLocked) { actions.addAll([ MobileViewPageLayoutButton( view: view, isImmersiveMode: isImmersiveMode, appBarOpacity: _appBarOpacity, + tabs: widget.tabs, ), ]); } @@ -265,25 +283,137 @@ class _MobileViewPageState extends State { Widget _buildTitle(BuildContext context, ViewPB? view) { final icon = view?.icon; - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (icon != null) ...[ - EmojiIconWidget( - emoji: icon.toEmojiIconData(), - emojiSize: 15, + return ValueListenableBuilder( + valueListenable: _appBarOpacity, + builder: (_, value, child) { + if (value < 0.99) { + return Padding( + padding: const EdgeInsets.only(left: 6.0), + child: _buildLockStatus(context, view), + ); + } + + final name = + widget.fixedTitle ?? view?.nameOrDefault ?? widget.title ?? ''; + + return Opacity( + opacity: value, + child: Row( + children: [ + if (icon != null && icon.value.isNotEmpty) ...[ + RawEmojiIconWidget( + emoji: icon.toEmojiIconData(), + emojiSize: 15, + ), + const HSpace(4), + ], + Flexible( + child: FlowyText.medium( + name, + fontSize: 15.0, + overflow: TextOverflow.ellipsis, + figmaLineHeight: 18.0, + ), + ), + const HSpace(4.0), + _buildLockStatusIcon(context, view), + ], ), - const HSpace(4), - ], - Expanded( - child: FlowyText.medium( - widget.fixedTitle ?? view?.name ?? widget.title ?? '', - fontSize: 15.0, - overflow: TextOverflow.ellipsis, - figmaLineHeight: 18.0, - ), - ), - ], + ); + }, + ); + } + + Widget _buildLockStatus(BuildContext context, ViewPB? view) { + if (view == null || view.layout == ViewLayoutPB.Chat) { + return const SizedBox.shrink(); + } + + return BlocConsumer( + listenWhen: (previous, current) => + previous.isLoadingLockStatus == current.isLoadingLockStatus && + current.isLoadingLockStatus == false, + listener: (context, state) { + if (state.isLocked) { + showToastNotification( + message: LocaleKeys.lockPage_pageLockedToast.tr(), + ); + + EditorNotification.exitEditing().post(); + } + }, + builder: (context, state) { + if (state.isLocked) { + return LockedPageStatus(); + } else if (!state.isLocked && state.lockCounter > 0) { + return ReLockedPageStatus(); + } + return const SizedBox.shrink(); + }, + ); + } + + Widget _buildLockStatusIcon(BuildContext context, ViewPB? view) { + if (view == null || view.layout == ViewLayoutPB.Chat) { + return const SizedBox.shrink(); + } + + return BlocConsumer( + listenWhen: (previous, current) => + previous.isLoadingLockStatus == current.isLoadingLockStatus && + current.isLoadingLockStatus == false, + listener: (context, state) { + if (state.isLocked) { + showToastNotification( + message: LocaleKeys.lockPage_pageLockedToast.tr(), + ); + } + }, + builder: (context, state) { + if (state.isLocked) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + context.read().add( + const ViewLockStatusEvent.unlock(), + ); + }, + child: Padding( + padding: const EdgeInsets.only( + top: 4.0, + right: 8, + bottom: 4.0, + ), + child: FlowySvg( + FlowySvgs.lock_page_fill_s, + blendMode: null, + ), + ), + ); + } else if (!state.isLocked && state.lockCounter > 0) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + context.read().add( + const ViewLockStatusEvent.lock(), + ); + }, + child: Padding( + padding: const EdgeInsets.only( + top: 4.0, + right: 8, + bottom: 4.0, + ), + child: FlowySvg( + FlowySvgs.unlock_page_s, + color: Color(0xFF8F959E), + blendMode: null, + ), + ), + ); + } + return const SizedBox.shrink(); + }, ); } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/base/view_page/app_bar_buttons.dart b/frontend/appflowy_flutter/lib/mobile/presentation/base/view_page/app_bar_buttons.dart index a228ac0229..a91fbf577b 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/base/view_page/app_bar_buttons.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/base/view_page/app_bar_buttons.dart @@ -9,8 +9,10 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; import 'package:appflowy/plugins/document/presentation/editor_notification.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/page_style_bottom_sheet.dart'; import 'package:appflowy/plugins/shared/share/share_bloc.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart'; import 'package:appflowy/workspace/application/view/prelude.dart'; +import 'package:appflowy/workspace/application/view/view_lock_status_bloc.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; @@ -27,12 +29,13 @@ class MobileViewPageImmersiveAppBar extends StatelessWidget required this.appBarOpacity, required this.title, required this.actions, + required this.view, }); final ValueListenable appBarOpacity; final Widget title; final List actions; - + final ViewPB? view; @override final Size preferredSize; @@ -42,9 +45,9 @@ class MobileViewPageImmersiveAppBar extends StatelessWidget valueListenable: appBarOpacity, builder: (_, opacity, __) => FlowyAppBar( backgroundColor: - AppBarTheme.of(context).backgroundColor?.withOpacity(opacity), + AppBarTheme.of(context).backgroundColor?.withValues(alpha: opacity), showDivider: false, - title: Opacity(opacity: opacity >= 0.99 ? 1.0 : 0, child: title), + title: _buildTitle(context, opacity: opacity), leadingWidth: 44, leading: Padding( padding: const EdgeInsets.only(top: 4.0, bottom: 4.0, left: 12.0), @@ -55,6 +58,13 @@ class MobileViewPageImmersiveAppBar extends StatelessWidget ); } + Widget _buildTitle( + BuildContext context, { + required double opacity, + }) { + return title; + } + Widget _buildAppBarBackButton(BuildContext context) { return AppBarButton( padding: EdgeInsets.zero, @@ -101,6 +111,12 @@ class MobileViewPageMoreButton extends StatelessWidget { BlocProvider.value(value: context.read()), BlocProvider.value(value: context.read()), BlocProvider.value(value: context.read()), + BlocProvider( + create: (context) => ViewLockStatusBloc(view: view) + ..add( + ViewLockStatusEvent.initial(), + ), + ), ], child: MobileViewPageMoreBottomSheet(view: view), ), @@ -123,9 +139,11 @@ class MobileViewPageLayoutButton extends StatelessWidget { required this.view, required this.isImmersiveMode, required this.appBarOpacity, + required this.tabs, }); final ViewPB view; + final List tabs; final bool isImmersiveMode; final ValueListenable appBarOpacity; @@ -156,6 +174,7 @@ class MobileViewPageLayoutButton extends StatelessWidget { ], child: PageStyleBottomSheet( view: context.read().state.view, + tabs: tabs, ), ), ); @@ -220,7 +239,7 @@ class _ImmersiveAppBarButton extends StatelessWidget { child = DecoratedBox( decoration: BoxDecoration( borderRadius: BorderRadius.circular(dimension / 2.0), - color: Colors.black.withOpacity(0.2), + color: Colors.black.withValues(alpha: 0.2), ), child: child, ); diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/base/view_page/more_bottom_sheet.dart b/frontend/appflowy_flutter/lib/mobile/presentation/base/view_page/more_bottom_sheet.dart index 25541ed7d4..be134e0a92 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/base/view_page/more_bottom_sheet.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/base/view_page/more_bottom_sheet.dart @@ -14,6 +14,7 @@ import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/util/string_extension.dart'; import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart'; import 'package:appflowy/workspace/application/view/prelude.dart'; +import 'package:appflowy/workspace/application/view/view_lock_status_bloc.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; @@ -43,7 +44,8 @@ class MobileViewPageMoreBottomSheet extends StatelessWidget { }, child: ViewPageBottomSheet( view: view, - onAction: (action) async => _onAction(context, action), + onAction: (action, {arguments}) async => + _onAction(context, action, arguments), onRename: (name) { _onRename(context, name); context.pop(); @@ -56,6 +58,7 @@ class MobileViewPageMoreBottomSheet extends StatelessWidget { Future _onAction( BuildContext context, MobileViewBottomSheetBodyAction action, + Map? arguments, ) async { switch (action) { case MobileViewBottomSheetBodyAction.duplicate: @@ -63,7 +66,7 @@ class MobileViewPageMoreBottomSheet extends StatelessWidget { break; case MobileViewBottomSheetBodyAction.delete: context.read().add(const ViewEvent.delete()); - context.pop(); + Navigator.of(context).pop(); break; case MobileViewBottomSheetBodyAction.addToFavorites: _addFavorite(context); @@ -107,12 +110,32 @@ class MobileViewPageMoreBottomSheet extends StatelessWidget { break; case MobileViewBottomSheetBodyAction.updatePathName: _updatePathName(context); + case MobileViewBottomSheetBodyAction.lockPage: + final isLocked = + arguments?[MobileViewBottomSheetBodyActionArguments.isLockedKey] ?? + false; + await _lockPage(context, isLocked: isLocked); + // context.pop(); + break; case MobileViewBottomSheetBodyAction.rename: // no need to implement, rename is handled by the onRename callback. throw UnimplementedError(); } } + Future _lockPage( + BuildContext context, { + required bool isLocked, + }) async { + if (isLocked) { + context.read().add(const ViewLockStatusEvent.lock()); + } else { + context + .read() + .add(const ViewLockStatusEvent.unlock()); + } + } + Future _publish(BuildContext context) async { final id = context.read().view.id; final lastPublishName = context.read().state.pathName; @@ -138,7 +161,6 @@ class MobileViewPageMoreBottomSheet extends StatelessWidget { context.pop(); showToastNotification( - context, message: LocaleKeys.button_duplicateSuccessfully.tr(), ); } @@ -147,7 +169,6 @@ class MobileViewPageMoreBottomSheet extends StatelessWidget { _toggleFavorite(context); showToastNotification( - context, message: LocaleKeys.button_favoriteSuccessfully.tr(), ); } @@ -156,7 +177,6 @@ class MobileViewPageMoreBottomSheet extends StatelessWidget { _toggleFavorite(context); showToastNotification( - context, message: LocaleKeys.button_unfavoriteSuccessfully.tr(), ); } @@ -179,8 +199,7 @@ class MobileViewPageMoreBottomSheet extends StatelessWidget { ), ); showToastNotification( - context, - message: LocaleKeys.grid_url_copy.tr(), + message: LocaleKeys.message_copy_success.tr(), ); } } @@ -211,12 +230,10 @@ class MobileViewPageMoreBottomSheet extends StatelessWidget { ), ); showToastNotification( - context, message: LocaleKeys.shareAction_copyLinkSuccess.tr(), ); } else { showToastNotification( - context, message: LocaleKeys.shareAction_copyLinkToBlockFailed.tr(), type: ToastificationType.error, ); @@ -300,11 +317,9 @@ class MobileViewPageMoreBottomSheet extends StatelessWidget { if (state.publishResult != null) { state.publishResult!.fold( (value) => showToastNotification( - context, message: LocaleKeys.publish_publishSuccessfully.tr(), ), (error) => showToastNotification( - context, message: '${LocaleKeys.publish_publishFailed.tr()}: ${error.code}', type: ToastificationType.error, ), @@ -312,11 +327,9 @@ class MobileViewPageMoreBottomSheet extends StatelessWidget { } else if (state.unpublishResult != null) { state.unpublishResult!.fold( (value) => showToastNotification( - context, message: LocaleKeys.publish_unpublishSuccessfully.tr(), ), (error) => showToastNotification( - context, message: LocaleKeys.publish_unpublishFailed.tr(), description: error.msg, type: ToastificationType.error, @@ -326,7 +339,6 @@ class MobileViewPageMoreBottomSheet extends StatelessWidget { state.updatePathNameResult!.onSuccess( (value) { showToastNotification( - context, message: LocaleKeys.settings_sites_success_updatePathNameSuccess.tr(), ); diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item.dart index c1129af79d..86021ea938 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item.dart @@ -65,7 +65,6 @@ class _MobileViewItemBottomSheetState extends State { Navigator.pop(context); context.read().add(const ViewEvent.duplicate()); showToastNotification( - context, message: LocaleKeys.button_duplicateSuccessfully.tr(), ); break; @@ -84,7 +83,6 @@ class _MobileViewItemBottomSheetState extends State { .read() .add(FavoriteEvent.toggle(widget.view)); showToastNotification( - context, message: !widget.view.isFavorite ? LocaleKeys.button_favoriteSuccessfully.tr() : LocaleKeys.button_unfavoriteSuccessfully.tr(), @@ -146,7 +144,6 @@ class _MobileViewItemBottomSheetState extends State { Navigator.pop(context); showToastNotification( - context, message: LocaleKeys.sideBar_removeSuccess.tr(), ); }, diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item_body.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item_body.dart index a078521aec..0ca60fe40b 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item_body.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_item_body.dart @@ -1,8 +1,10 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/widgets/widgets.dart'; +import 'package:appflowy/workspace/application/view/view_lock_status_bloc.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; enum MobileViewItemBottomSheetBodyAction { rename, @@ -40,6 +42,8 @@ class MobileViewItemBottomSheetBody extends StatelessWidget { BuildContext context, MobileViewItemBottomSheetBodyAction action, ) { + final isLocked = + context.read()?.state.isLocked ?? false; switch (action) { case MobileViewItemBottomSheetBodyAction.rename: return FlowyOptionTile.text( @@ -49,6 +53,7 @@ class MobileViewItemBottomSheetBody extends StatelessWidget { FlowySvgs.view_item_rename_s, size: Size.square(18), ), + enable: !isLocked, showTopBorder: false, showBottomBorder: false, onTap: () => onAction( @@ -94,6 +99,7 @@ class MobileViewItemBottomSheetBody extends StatelessWidget { size: const Size.square(18), color: Theme.of(context).colorScheme.error, ), + enable: !isLocked, showTopBorder: false, showBottomBorder: false, onTap: () => onAction( diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_page.dart index 5b379967ef..47ab37505e 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_page.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_page.dart @@ -4,9 +4,12 @@ import 'package:appflowy/mobile/application/base/mobile_view_page_bloc.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart'; import 'package:appflowy/plugins/shared/share/share_bloc.dart'; +import 'package:appflowy/workspace/application/view/view_ext.dart'; +import 'package:appflowy/workspace/application/view/view_lock_status_bloc.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -25,11 +28,27 @@ enum MobileViewBottomSheetBodyAction { visitSite, copyShareLink, updatePathName, + lockPage; + + static const disableInLockedView = [ + undo, + redo, + rename, + delete, + ]; +} + +class MobileViewBottomSheetBodyActionArguments { + static const isLockedKey = 'is_locked'; } typedef MobileViewBottomSheetBodyActionCallback = void Function( MobileViewBottomSheetBodyAction action, -); + // for the [MobileViewBottomSheetBodyAction.lockPage] action, + // it will pass the [isLocked] value to the callback. + { + Map? arguments, +}); class ViewPageBottomSheet extends StatefulWidget { const ViewPageBottomSheet({ @@ -56,7 +75,7 @@ class _ViewPageBottomSheetState extends State { case MobileBottomSheetType.view: return MobileViewBottomSheetBody( view: widget.view, - onAction: (action) { + onAction: (action, {arguments}) { switch (action) { case MobileViewBottomSheetBodyAction.rename: setState(() { @@ -64,7 +83,7 @@ class _ViewPageBottomSheetState extends State { }); break; default: - widget.onAction(action); + widget.onAction(action, arguments: arguments); } }, ); @@ -93,6 +112,8 @@ class MobileViewBottomSheetBody extends StatelessWidget { @override Widget build(BuildContext context) { final isFavorite = view.isFavorite; + final isLocked = + context.watch()?.state.isLocked ?? false; return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ @@ -100,6 +121,7 @@ class MobileViewBottomSheetBody extends StatelessWidget { text: LocaleKeys.button_rename.tr(), icon: FlowySvgs.view_item_rename_s, iconSize: const Size.square(18), + enable: !isLocked, onTap: () => onAction( MobileViewBottomSheetBodyAction.rename, ), @@ -118,6 +140,28 @@ class MobileViewBottomSheetBody extends StatelessWidget { ), ), _divider(), + if (view.layout.isDatabaseView || view.layout.isDocumentView) ...[ + MobileQuickActionButton( + text: LocaleKeys.disclosureAction_lockPage.tr(), + icon: FlowySvgs.lock_page_s, + iconSize: const Size.square(18), + rightIconBuilder: (context) => _LockPageRightIconBuilder( + onAction: onAction, + ), + onTap: () { + final isLocked = + context.read()?.state.isLocked ?? false; + onAction( + MobileViewBottomSheetBodyAction.lockPage, + arguments: { + MobileViewBottomSheetBodyActionArguments.isLockedKey: + !isLocked, + }, + ); + }, + ), + _divider(), + ], MobileQuickActionButton( text: LocaleKeys.button_duplicate.tr(), icon: FlowySvgs.duplicate_s, @@ -138,12 +182,14 @@ class MobileViewBottomSheetBody extends StatelessWidget { ), _divider(), ..._buildPublishActions(context), + MobileQuickActionButton( text: LocaleKeys.button_delete.tr(), textColor: Theme.of(context).colorScheme.error, icon: FlowySvgs.trash_s, iconColor: Theme.of(context).colorScheme.error, iconSize: const Size.square(18), + enable: !isLocked, onTap: () => onAction( MobileViewBottomSheetBodyAction.delete, ), @@ -157,7 +203,7 @@ class MobileViewBottomSheetBody extends StatelessWidget { final userProfile = context.read().state.userProfilePB; // the publish feature is only available for AppFlowy Cloud if (userProfile == null || - userProfile.authenticator != AuthenticatorPB.AppFlowyCloud) { + userProfile.workspaceAuthType != AuthTypePB.Server) { return []; } @@ -206,8 +252,38 @@ class MobileViewBottomSheetBody extends StatelessWidget { } } - Widget _divider() => const Divider( - height: 8.5, - thickness: 0.5, - ); + Widget _divider() => const MobileQuickActionDivider(); +} + +class _LockPageRightIconBuilder extends StatelessWidget { + const _LockPageRightIconBuilder({ + required this.onAction, + }); + + final MobileViewBottomSheetBodyActionCallback onAction; + + @override + Widget build(BuildContext context) { + final isLocked = + context.watch()?.state.isLocked ?? false; + return SizedBox( + width: 46, + height: 30, + child: FittedBox( + fit: BoxFit.fill, + child: CupertinoSwitch( + value: isLocked, + activeTrackColor: Theme.of(context).colorScheme.primary, + onChanged: (value) { + onAction( + MobileViewBottomSheetBodyAction.lockPage, + arguments: { + MobileViewBottomSheetBodyActionArguments.isLockedKey: value, + }, + ); + }, + ), + ), + ); + } } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/default_mobile_action_pane.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/default_mobile_action_pane.dart index faf02707b7..d4b4292443 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/default_mobile_action_pane.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/default_mobile_action_pane.dart @@ -8,6 +8,7 @@ import 'package:appflowy/workspace/application/recent/recent_views_bloc.dart'; import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart'; import 'package:appflowy/workspace/application/view/view_bloc.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; +import 'package:appflowy/workspace/application/view/view_lock_status_bloc.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -44,7 +45,6 @@ enum MobilePaneActionType { size: 24.0, onPressed: (context) { showToastNotification( - context, message: LocaleKeys.button_unfavoriteSuccessfully.tr(), ); @@ -60,7 +60,6 @@ enum MobilePaneActionType { size: 24.0, onPressed: (context) { showToastNotification( - context, message: LocaleKeys.button_favoriteSuccessfully.tr(), ); @@ -131,6 +130,11 @@ enum MobilePaneActionType { BlocProvider.value(value: favoriteBloc), if (recentViewsBloc != null) BlocProvider.value(value: recentViewsBloc), + BlocProvider( + create: (_) => + ViewLockStatusBloc(view: viewBloc.state.view) + ..add(const ViewLockStatusEvent.initial()), + ), ], child: BlocBuilder( builder: (context, state) { diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart index 999813d63e..a0fa5dc6aa 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart @@ -74,7 +74,7 @@ Future showMobileBottomSheet( backgroundColor ??= Theme.of(context).brightness == Brightness.light ? const Color(0xFFF7F8FB) : const Color(0xFF23262B); - barrierColor ??= Colors.black.withOpacity(0.3); + barrierColor ??= Colors.black.withValues(alpha: 0.3); return showModalBottomSheet( context: context, diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/show_transition_bottom_sheet.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/show_transition_bottom_sheet.dart index 0ff2a6634a..b29817251a 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/show_transition_bottom_sheet.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/show_transition_bottom_sheet.dart @@ -329,7 +329,7 @@ class CupertinoSheetBottomRouteTransition extends StatelessWidget { (Theme.of(context).brightness == Brightness.dark ? Colors.grey : Colors.black) - .withOpacity(secondaryAnimation.value * 0.1), + .withValues(alpha: secondaryAnimation.value * 0.1), BlendMode.srcOver, ), child: child, diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/board/mobile_board_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/board/mobile_board_page.dart index 2885f37bbd..29841dd22a 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/board/mobile_board_page.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/board/mobile_board_page.dart @@ -1,5 +1,3 @@ -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/database/board/board.dart'; @@ -13,12 +11,14 @@ import 'package:appflowy/plugins/database/widgets/cell/card_cell_style_maps/mobi import 'package:appflowy/shared/flowy_error_page.dart'; import 'package:appflowy/util/field_type_extension.dart'; import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; +import 'package:appflowy/workspace/application/view/view_lock_status_bloc.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; import 'package:appflowy_board/appflowy_board.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; @@ -143,6 +143,8 @@ class _BoardContentState extends State<_BoardContent> { return state.maybeMap( orElse: () => const SizedBox.shrink(), ready: (state) { + final isLocked = + context.watch()?.state.isLocked ?? false; final showCreateGroupButton = context .read() .groupingFieldType @@ -160,15 +162,20 @@ class _BoardContentState extends State<_BoardContent> { padding: config.groupHeaderPadding, ) : const HSpace(16), - trailing: showCreateGroupButton + trailing: showCreateGroupButton && !isLocked ? const MobileBoardTrailing() : const HSpace(16), - headerBuilder: (_, groupData) => BlocProvider.value( - value: context.read(), - child: GroupCardHeader( - groupData: groupData, - ), - ), + headerBuilder: (_, groupData) { + final isLocked = + context.read()?.state.isLocked ?? + false; + return IgnorePointer( + ignoring: isLocked, + child: GroupCardHeader( + groupData: groupData, + ), + ); + }, footerBuilder: _buildFooter, cardBuilder: (_, column, columnItem) => _buildCard( context: context, @@ -184,34 +191,39 @@ class _BoardContentState extends State<_BoardContent> { } Widget _buildFooter(BuildContext context, AppFlowyGroupData columnData) { + final isLocked = + context.read()?.state.isLocked ?? false; final style = Theme.of(context); return SizedBox( height: 42, width: double.infinity, - child: TextButton.icon( - style: TextButton.styleFrom( - padding: const EdgeInsets.only(left: 8), - alignment: Alignment.centerLeft, - ), - icon: FlowySvg( - FlowySvgs.add_m, - color: style.colorScheme.onSurface, - ), - label: Text( - LocaleKeys.board_column_createNewCard.tr(), - style: style.textTheme.bodyMedium?.copyWith( + child: IgnorePointer( + ignoring: isLocked, + child: TextButton.icon( + style: TextButton.styleFrom( + padding: const EdgeInsets.only(left: 8), + alignment: Alignment.centerLeft, + ), + icon: FlowySvg( + FlowySvgs.add_m, color: style.colorScheme.onSurface, ), - ), - onPressed: () => context.read().add( - BoardEvent.createRow( - columnData.id, - OrderObjectPositionTypePB.End, - null, - null, - ), + label: Text( + LocaleKeys.board_column_createNewCard.tr(), + style: style.textTheme.bodyMedium?.copyWith( + color: style.colorScheme.onSurface, ), + ), + onPressed: () => context.read().add( + BoardEvent.createRow( + columnData.id, + OrderObjectPositionTypePB.End, + null, + null, + ), + ), + ), ), ); } @@ -231,6 +243,8 @@ class _BoardContentState extends State<_BoardContent> { CardCellBuilder(databaseController: boardBloc.databaseController); final groupItemId = groupItem.row.id + groupData.group.groupId; + final isLocked = + context.read()?.state.isLocked ?? false; return Container( key: ValueKey(groupItemId), @@ -238,31 +252,34 @@ class _BoardContentState extends State<_BoardContent> { decoration: _makeBoxDecoration(context), child: BlocProvider.value( value: boardBloc, - child: RowCard( - fieldController: boardBloc.fieldController, - rowMeta: rowMeta, - viewId: boardBloc.viewId, - rowCache: boardBloc.rowCache, - groupingFieldId: groupItem.fieldInfo.id, - isEditing: false, - cellBuilder: cellBuilder, - onTap: (context) { - context.push( - MobileRowDetailPage.routeName, - extra: { - MobileRowDetailPage.argRowId: rowMeta.id, - MobileRowDetailPage.argDatabaseController: - context.read().databaseController, - }, - ); - }, - onStartEditing: () {}, - onEndEditing: () {}, - styleConfiguration: RowCardStyleConfiguration( - cellStyleMap: mobileBoardCardCellStyleMap(context), - showAccessory: false, + child: IgnorePointer( + ignoring: isLocked, + child: RowCard( + fieldController: boardBloc.fieldController, + rowMeta: rowMeta, + viewId: boardBloc.viewId, + rowCache: boardBloc.rowCache, + groupingFieldId: groupItem.fieldInfo.id, + isEditing: false, + cellBuilder: cellBuilder, + onTap: (context) { + context.push( + MobileRowDetailPage.routeName, + extra: { + MobileRowDetailPage.argRowId: rowMeta.id, + MobileRowDetailPage.argDatabaseController: + context.read().databaseController, + }, + ); + }, + onStartEditing: () {}, + onEndEditing: () {}, + styleConfiguration: RowCardStyleConfiguration( + cellStyleMap: mobileBoardCardCellStyleMap(context), + showAccessory: false, + ), + userProfile: boardBloc.userProfile, ), - userProfile: boardBloc.userProfile, ), ), ); @@ -276,14 +293,20 @@ class _BoardContentState extends State<_BoardContent> { border: themeMode == ThemeMode.light ? Border.fromBorderSide( BorderSide( - color: Theme.of(context).colorScheme.outline.withOpacity(0.5), + color: Theme.of(context) + .colorScheme + .outline + .withValues(alpha: 0.5), ), ) : null, boxShadow: themeMode == ThemeMode.light ? [ BoxShadow( - color: Theme.of(context).colorScheme.outline.withOpacity(0.5), + color: Theme.of(context) + .colorScheme + .outline + .withValues(alpha: 0.5), blurRadius: 4, offset: const Offset(0, 2), ), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/board/widgets/group_card_header.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/board/widgets/group_card_header.dart index ebc492de09..8d1e91b708 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/board/widgets/group_card_header.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/board/widgets/group_card_header.dart @@ -7,7 +7,6 @@ import 'package:appflowy/util/field_type_extension.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_board/appflowy_board.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; @@ -113,12 +112,8 @@ class _GroupCardHeaderState extends State { context, showDragHandle: true, backgroundColor: Theme.of(context).colorScheme.surface, - builder: (_) => SeparatedColumn( + builder: (_) => Column( crossAxisAlignment: CrossAxisAlignment.stretch, - separatorBuilder: () => const Divider( - height: 8.5, - thickness: 0.5, - ), children: [ MobileQuickActionButton( text: LocaleKeys.board_column_renameColumn.tr(), @@ -132,6 +127,7 @@ class _GroupCardHeaderState extends State { context.pop(); }, ), + const MobileQuickActionDivider(), MobileQuickActionButton( text: LocaleKeys.board_column_hideColumn.tr(), icon: FlowySvgs.hide_s, diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart index 0982354e36..5896c51b9b 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart @@ -1,5 +1,3 @@ -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/base/app_bar/app_bar.dart'; @@ -29,6 +27,7 @@ import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:go_router/go_router.dart'; @@ -59,7 +58,9 @@ class _MobileRowDetailPageState extends State { late final PageController _pageController; String get viewId => widget.databaseController.viewId; + RowCache get rowCache => widget.databaseController.rowCache; + FieldController get fieldController => widget.databaseController.fieldController; @@ -149,7 +150,7 @@ class _MobileRowDetailPageState extends State { icon: FlowySvgs.duplicate_s, text: LocaleKeys.button_duplicate.tr(), ), - const Divider(height: 8.5, thickness: 0.5), + const MobileQuickActionDivider(), MobileQuickActionButton( onTap: () => showMobileBottomSheet( context, @@ -201,7 +202,7 @@ class _MobileRowDetailPageState extends State { icon: FlowySvgs.add_cover_s, text: 'Add cover', ), - const Divider(height: 8.5, thickness: 0.5), + const MobileQuickActionDivider(), MobileQuickActionButton( onTap: () => _performAction(viewId, _bloc.state.currentRowId, true), text: LocaleKeys.button_delete.tr(), @@ -209,7 +210,6 @@ class _MobileRowDetailPageState extends State { icon: FlowySvgs.trash_s, iconColor: Theme.of(context).colorScheme.error, ), - const Divider(height: 8.5, thickness: 0.5), ], ), ); @@ -381,7 +381,9 @@ class MobileRowDetailPageContentState late final EditableCellBuilder cellBuilder; String get viewId => widget.databaseController.viewId; + RowCache get rowCache => widget.databaseController.rowCache; + FieldController get fieldController => widget.databaseController.fieldController; ValueNotifier primaryFieldId = ValueNotifier(''); @@ -543,6 +545,7 @@ class _TitleSkin extends IEditableTextCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, TextCellBloc bloc, FocusNode focusNode, TextEditingController textEditingController, diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/row_page_button.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/row_page_button.dart index d7ac40d66a..b0f21188cd 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/row_page_button.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/widgets/row_page_button.dart @@ -8,6 +8,7 @@ import 'package:appflowy/plugins/database/application/cell/cell_controller.dart' import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart'; import 'package:appflowy/plugins/database/application/database_controller.dart'; import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/workspace/application/view/prelude.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy_backend/log.dart'; @@ -102,7 +103,7 @@ class _OpenRowPageButtonState extends State { Log.info('Open row page(${widget.documentId})'); if (view == null) { - showToastNotification(context, message: 'Failed to open row page'); + showToastNotification(message: 'Failed to open row page'); // reload the view again unawaited(_preloadView(context)); Log.error('Failed to open row page(${widget.documentId})'); @@ -116,6 +117,7 @@ class _OpenRowPageButtonState extends State { addInRecent: false, showMoreButton: false, fixedTitle: fieldName, + tabs: [PickerTabType.emoji.name], ); } } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_edit_field_screen.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_edit_field_screen.dart index a51e3561f8..75b52de414 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_edit_field_screen.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_edit_field_screen.dart @@ -1,5 +1,3 @@ -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/base/app_bar/app_bar.dart'; import 'package:appflowy/mobile/presentation/database/field/mobile_full_field_editor.dart'; @@ -8,6 +6,7 @@ import 'package:appflowy/plugins/database/domain/field_backend_service.dart'; import 'package:appflowy/plugins/database/domain/field_service.dart'; import 'package:appflowy/plugins/database/widgets/setting/field_visibility_extension.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; class MobileEditPropertyScreen extends StatefulWidget { @@ -49,7 +48,7 @@ class _MobileEditPropertyScreenState extends State { final fieldId = widget.field.id; return PopScope( - onPopInvoked: (didPop) { + onPopInvokedWithResult: (didPop, _) { if (!didPop) { context.pop(_fieldOptionValues); } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_view_list.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_view_list.dart index 763da36918..f7ad313412 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_view_list.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_view_list.dart @@ -5,6 +5,8 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/widgets/widgets.dart'; import 'package:appflowy/plugins/database/application/database_controller.dart'; import 'package:appflowy/plugins/database/application/tab_bar_bloc.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart'; +import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; import 'package:appflowy/workspace/application/view/view_bloc.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; @@ -163,9 +165,20 @@ class MobileDatabaseViewListButton extends StatelessWidget { } Widget _buildViewIconButton(BuildContext context, ViewPB view) { + final iconData = view.icon.toEmojiIconData(); + Widget icon; + if (iconData.isEmpty || iconData.type != FlowyIconType.icon) { + icon = view.defaultIcon(); + } else { + icon = RawEmojiIconWidget( + emoji: iconData, + emojiSize: 14.0, + enableColor: false, + ); + } return SizedBox.square( dimension: 20.0, - child: view.defaultIcon(), + child: icon, ); } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_view_quick_actions.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_view_quick_actions.dart index 652c93496e..a133739a9d 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_view_quick_actions.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/view/database_view_quick_actions.dart @@ -1,11 +1,16 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/show_transition_bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart'; import 'package:appflowy/plugins/database/application/database_controller.dart'; +import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/workspace/application/view/view_bloc.dart'; +import 'package:appflowy/workspace/application/view/view_service.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/theme_extension.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; @@ -48,7 +53,46 @@ class MobileDatabaseViewQuickActions extends StatelessWidget { context.pop(); } }), - _divider(), + const MobileQuickActionDivider(), + _actionButton( + context, + _Action.changeIcon, + () { + showMobileBottomSheet( + context, + showDragHandle: true, + showDivider: false, + showHeader: true, + title: LocaleKeys.titleBar_pageIcon.tr(), + backgroundColor: AFThemeExtension.of(context).background, + enableDraggableScrollable: true, + minChildSize: 0.6, + initialChildSize: 0.61, + scrollableWidgetBuilder: (_, controller) { + return Expanded( + child: FlowyIconEmojiPicker( + tabs: const [PickerTabType.icon], + enableBackgroundColorSelection: false, + onSelectedEmoji: (r) { + ViewBackendService.updateViewIcon( + view: view, + viewIcon: r.data, + ); + Navigator.pop(context); + }, + ), + ); + }, + builder: (_) => const SizedBox.shrink(), + ).then((_) { + if (context.mounted) { + Navigator.pop(context); + } + }); + }, + !isInline, + ), + const MobileQuickActionDivider(), _actionButton( context, _Action.duplicate, @@ -58,7 +102,7 @@ class MobileDatabaseViewQuickActions extends StatelessWidget { }, !isInline, ), - _divider(), + const MobileQuickActionDivider(), _actionButton( context, _Action.delete, @@ -68,7 +112,6 @@ class MobileDatabaseViewQuickActions extends StatelessWidget { }, !isInline, ), - _divider(), ], ); } @@ -88,20 +131,20 @@ class MobileDatabaseViewQuickActions extends StatelessWidget { enable: enable, ); } - - Widget _divider() => const Divider(height: 8.5, thickness: 0.5); } enum _Action { edit, - duplicate, - delete; + changeIcon, + delete, + duplicate; String get label { return switch (this) { edit => LocaleKeys.grid_settings_editView.tr(), duplicate => LocaleKeys.button_duplicate.tr(), delete => LocaleKeys.button_delete.tr(), + changeIcon => LocaleKeys.disclosureAction_changeIcon.tr(), }; } @@ -110,6 +153,7 @@ enum _Action { edit => FlowySvgs.view_item_rename_s, duplicate => FlowySvgs.duplicate_s, delete => FlowySvgs.trash_s, + changeIcon => FlowySvgs.change_icon_s, }; } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/editor/mobile_editor_screen.dart b/frontend/appflowy_flutter/lib/mobile/presentation/editor/mobile_editor_screen.dart index ab056d174e..373558a480 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/editor/mobile_editor_screen.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/editor/mobile_editor_screen.dart @@ -1,4 +1,5 @@ import 'package:appflowy/mobile/presentation/base/mobile_view_page.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:flutter/material.dart'; @@ -10,6 +11,7 @@ class MobileDocumentScreen extends StatelessWidget { this.showMoreButton = true, this.fixedTitle, this.blockId, + this.tabs = const [PickerTabType.emoji, PickerTabType.icon], }); /// view id @@ -18,6 +20,7 @@ class MobileDocumentScreen extends StatelessWidget { final bool showMoreButton; final String? fixedTitle; final String? blockId; + final List tabs; static const routeName = '/docs'; static const viewId = 'id'; @@ -25,6 +28,7 @@ class MobileDocumentScreen extends StatelessWidget { static const viewShowMoreButton = 'show_more_button'; static const viewFixedTitle = 'fixed_title'; static const viewBlockId = 'block_id'; + static const viewSelectTabs = 'select_tabs'; @override Widget build(BuildContext context) { @@ -35,6 +39,7 @@ class MobileDocumentScreen extends StatelessWidget { showMoreButton: showMoreButton, fixedTitle: fixedTitle, blockId: blockId, + tabs: tabs, ); } } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/favorite/mobile_favorite_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/favorite/mobile_favorite_page.dart index e6d2d895b1..0e7a7cb4c6 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/favorite/mobile_favorite_page.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/favorite/mobile_favorite_page.dart @@ -31,9 +31,9 @@ class MobileFavoriteScreen extends StatelessWidget { return const Center(child: CircularProgressIndicator.adaptive()); } - final workspaceSetting = snapshots.data?[0].fold( - (workspaceSettingPB) { - return workspaceSettingPB as WorkspaceSettingPB?; + final latest = snapshots.data?[0].fold( + (latest) { + return latest as WorkspaceLatestPB?; }, (error) => null, ); @@ -46,7 +46,7 @@ class MobileFavoriteScreen extends StatelessWidget { // In the unlikely case either of the above is null, eg. // when a workspace is already open this can happen. - if (workspaceSetting == null || userProfile == null) { + if (latest == null || userProfile == null) { return const WorkspaceFailedScreen(); } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page.dart index f90d7ab34a..345a4591d1 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page.dart @@ -2,7 +2,7 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/home/mobile_home_page_header.dart'; import 'package:appflowy/mobile/presentation/home/tab/mobile_space_tab.dart'; import 'package:appflowy/mobile/presentation/home/tab/space_order_bloc.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart'; +import 'package:appflowy/shared/loading.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/user/application/auth/auth_service.dart'; import 'package:appflowy/user/application/reminder/reminder_bloc.dart'; @@ -44,9 +44,9 @@ class MobileHomeScreen extends StatelessWidget { return const Center(child: CircularProgressIndicator.adaptive()); } - final workspaceSetting = snapshots.data?[0].fold( - (workspaceSettingPB) { - return workspaceSettingPB as WorkspaceSettingPB?; + final workspaceLatest = snapshots.data?[0].fold( + (workspaceLatestPB) { + return workspaceLatestPB as WorkspaceLatestPB?; }, (error) => null, ); @@ -59,7 +59,7 @@ class MobileHomeScreen extends StatelessWidget { // In the unlikely case either of the above is null, eg. // when a workspace is already open this can happen. - if (workspaceSetting == null || userProfile == null) { + if (workspaceLatest == null || userProfile == null) { return const WorkspaceFailedScreen(); } @@ -78,7 +78,7 @@ class MobileHomeScreen extends StatelessWidget { value: userProfile, child: MobileHomePage( userProfile: userProfile, - workspaceSetting: workspaceSetting, + workspaceLatest: workspaceLatest, ), ), ), @@ -95,11 +95,11 @@ class MobileHomePage extends StatefulWidget { const MobileHomePage({ super.key, required this.userProfile, - required this.workspaceSetting, + required this.workspaceLatest, }); final UserProfilePB userProfile; - final WorkspaceSettingPB workspaceSetting; + final WorkspaceLatestPB workspaceLatest; @override State createState() => _MobileHomePageState(); @@ -145,7 +145,7 @@ class _MobileHomePageState extends State { void _onLatestViewChange() async { final id = getIt().latestOpenView?.id; - if (id == null) { + if (id == null || id.isEmpty) { return; } await FolderEventSetLatestView(ViewIdPB(value: id)).send(); @@ -329,7 +329,7 @@ class _HomePageState extends State<_HomePage> { } if (message != null) { - showToastNotification(context, message: message, type: toastType); + showToastNotification(message: message, type: toastType); } } } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page_header.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page_header.dart index 27e1d3d341..113f12e543 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page_header.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_page_header.dart @@ -5,6 +5,7 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/home/workspaces/workspace_menu_bottom_sheet.dart'; import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart'; import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/util/built_in_svgs.dart'; import 'package:appflowy/workspace/application/user/settings_user_bloc.dart'; @@ -193,6 +194,7 @@ class _MobileWorkspace extends StatelessWidget { context.read().add( UserWorkspaceEvent.openWorkspace( workspace.workspaceId, + workspace.workspaceAuthType, ), ); }, @@ -234,6 +236,7 @@ class _UserIcon extends StatelessWidget { queryParameters: { MobileEmojiPickerScreen.pageTitle: LocaleKeys.titleBar_userIcon.tr(), + MobileEmojiPickerScreen.selectTabs: [PickerTabType.emoji.name], }, ).toString(), ); diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_setting_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_setting_page.dart index 1e0ddb5a51..a01df20549 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_setting_page.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_setting_page.dart @@ -3,16 +3,19 @@ import 'package:appflowy/env/env.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/base/app_bar/app_bar.dart'; import 'package:appflowy/mobile/presentation/presentation.dart'; +import 'package:appflowy/mobile/presentation/setting/ai/ai_settings_group.dart'; import 'package:appflowy/mobile/presentation/setting/cloud/cloud_setting_group.dart'; import 'package:appflowy/mobile/presentation/setting/user_session_setting_group.dart'; import 'package:appflowy/mobile/presentation/setting/workspace/workspace_setting_group.dart'; import 'package:appflowy/mobile/presentation/widgets/widgets.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/user/application/auth/auth_service.dart'; +import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; class MobileHomeSettingPage extends StatefulWidget { const MobileHomeSettingPage({ @@ -68,31 +71,42 @@ class _MobileHomeSettingPageState extends State { } Widget _buildSettingsWidget(UserProfilePB userProfile) { - // show the third-party sign in buttons if user logged in with local session and auth is enabled. - - final showThirdPartyLogin = - userProfile.authenticator == AuthenticatorPB.Local && isAuthEnabled; - return SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - children: [ - PersonalInfoSettingGroup( - userProfile: userProfile, + return BlocProvider( + create: (context) => UserWorkspaceBloc(userProfile: userProfile) + ..add(const UserWorkspaceEvent.initial()), + child: BlocBuilder( + builder: (context, state) { + final currentWorkspaceId = state.currentWorkspace?.workspaceId ?? ''; + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + PersonalInfoSettingGroup( + userProfile: userProfile, + ), + const WorkspaceSettingGroup(), + const AppearanceSettingGroup(), + const LanguageSettingGroup(), + if (Env.enableCustomCloud) const CloudSettingGroup(), + if (isAuthEnabled) + AiSettingsGroup( + key: ValueKey(currentWorkspaceId), + userProfile: userProfile, + workspaceId: currentWorkspaceId, + ), + const SupportSettingGroup(), + const AboutSettingGroup(), + UserSessionSettingGroup( + userProfile: userProfile, + showThirdPartyLogin: false, + ), + const VSpace(20), + ], + ), ), - const WorkspaceSettingGroup(), - const AppearanceSettingGroup(), - const LanguageSettingGroup(), - if (Env.enableCustomCloud) const CloudSettingGroup(), - const SupportSettingGroup(), - const AboutSettingGroup(), - UserSessionSettingGroup( - userProfile: userProfile, - showThirdPartyLogin: showThirdPartyLogin, - ), - const VSpace(20), - ], - ), + ); + }, ), ); } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_trash_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_trash_page.dart index 73a5381d42..73da7594a7 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_trash_page.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/mobile_home_trash_page.dart @@ -212,7 +212,7 @@ class _DeletedFilesListView extends StatelessWidget { ?.copyWith(color: theme.colorScheme.onSurface), ), horizontalTitleGap: 0, - tileColor: theme.colorScheme.onSurface.withOpacity(0.1), + tileColor: theme.colorScheme.onSurface.withValues(alpha: 0.1), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/recent_folder/mobile_recent_view.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/recent_folder/mobile_recent_view.dart index b60d354ede..966b1ac61a 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/recent_folder/mobile_recent_view.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/recent_folder/mobile_recent_view.dart @@ -3,7 +3,7 @@ import 'dart:io'; import 'package:appflowy/mobile/application/mobile_router.dart'; import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart'; import 'package:appflowy/mobile/application/recent/recent_view_bloc.dart'; -import 'package:appflowy/plugins/base/emoji/emoji_text.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy/shared/appflowy_network_image.dart'; import 'package:appflowy/shared/flowy_gradient_colors.dart'; @@ -110,10 +110,7 @@ class MobileRecentView extends StatelessWidget { return Padding( padding: const EdgeInsets.only(left: 8.0), child: state.icon.isNotEmpty - ? EmojiText( - emoji: state.icon.emoji, - fontSize: 30.0, - ) + ? RawEmojiIconWidget(emoji: state.icon, emojiSize: 30) : SizedBox.square( dimension: 32.0, child: view.defaultIcon(), @@ -137,7 +134,8 @@ class _RecentCover extends StatelessWidget { Widget build(BuildContext context) { final placeholder = Container( // random color, update it once we have a better placeholder - color: Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.2), + color: + Theme.of(context).colorScheme.onSurfaceVariant.withValues(alpha: 0.2), ); final value = this.value; if (value == null) { diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/section_folder/mobile_home_section_folder.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/section_folder/mobile_home_section_folder.dart index d610b4452b..0079ed319a 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/section_folder/mobile_home_section_folder.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/section_folder/mobile_home_section_folder.dart @@ -3,6 +3,7 @@ import 'package:appflowy/mobile/application/mobile_router.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/home/section_folder/mobile_home_section_folder_header.dart'; import 'package:appflowy/mobile/presentation/page_item/mobile_view_item.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart'; import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart'; import 'package:appflowy/workspace/application/view/view_bloc.dart'; @@ -101,7 +102,14 @@ class _Pages extends StatelessWidget { level: 0, leftPadding: HomeSpaceViewSizes.leftPadding, isFeedback: false, - onSelected: context.pushView, + onSelected: (v) => context.pushView( + v, + tabs: [ + PickerTabType.emoji, + PickerTabType.icon, + PickerTabType.custom, + ].map((e) => e.name).toList(), + ), endActionPane: (context) { final view = context.read().state.view; return buildEndActionPane( diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/setting/settings_popup_menu.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/setting/settings_popup_menu.dart index cbbda8362a..659473a6b1 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/setting/settings_popup_menu.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/setting/settings_popup_menu.dart @@ -16,6 +16,7 @@ enum _MobileSettingsPopupMenuItem { members, trash, help, + helpAndDocumentation, } class HomePageSettingsPopupMenu extends StatelessWidget { @@ -47,7 +48,7 @@ class HomePageSettingsPopupMenu extends StatelessWidget { text: LocaleKeys.settings_popupMenuItem_settings.tr(), ), // only show the member items in cloud mode - if (userProfile.authenticator == AuthenticatorPB.AppFlowyCloud) ...[ + if (userProfile.workspaceAuthType == AuthTypePB.Server) ...[ const PopupMenuDivider(height: 0.5), _buildItem( value: _MobileSettingsPopupMenuItem.members, @@ -62,10 +63,16 @@ class HomePageSettingsPopupMenu extends StatelessWidget { text: LocaleKeys.settings_popupMenuItem_trash.tr(), ), const PopupMenuDivider(height: 0.5), + _buildItem( + value: _MobileSettingsPopupMenuItem.helpAndDocumentation, + svg: FlowySvgs.help_and_documentation_s, + text: LocaleKeys.settings_popupMenuItem_helpAndDocumentation.tr(), + ), + const PopupMenuDivider(height: 0.5), _buildItem( value: _MobileSettingsPopupMenuItem.help, svg: FlowySvgs.message_support_s, - text: LocaleKeys.settings_popupMenuItem_helpAndSupport.tr(), + text: LocaleKeys.settings_popupMenuItem_getSupport.tr(), ), ], onSelected: (_MobileSettingsPopupMenuItem value) { @@ -82,6 +89,9 @@ class HomePageSettingsPopupMenu extends StatelessWidget { case _MobileSettingsPopupMenuItem.help: _openHelpPage(context); break; + case _MobileSettingsPopupMenuItem.helpAndDocumentation: + _openHelpAndDocumentationPage(context); + break; } }, child: const Padding( @@ -123,6 +133,10 @@ class HomePageSettingsPopupMenu extends StatelessWidget { void _openSettingsPage(BuildContext context) { context.push(MobileHomeSettingPage.routeName); } + + void _openHelpAndDocumentationPage(BuildContext context) { + afLaunchUrlString('https://appflowy.com/guide'); + } } class _PopupButton extends StatelessWidget { diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/mobile_page_card.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/mobile_page_card.dart index 99bd4de494..87ce41d5b6 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/mobile_page_card.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/shared/mobile_page_card.dart @@ -11,6 +11,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emo import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy/shared/appflowy_network_image.dart'; import 'package:appflowy/shared/flowy_gradient_colors.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/util/string_extension.dart'; import 'package:appflowy/util/theme_extension.dart'; import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; @@ -81,7 +82,14 @@ class MobileViewPage extends StatelessWidget { spaceRatio: 4, ), child: AnimatedGestureDetector( - onTapUp: () => context.pushView(view), + onTapUp: () => context.pushView( + view, + tabs: [ + PickerTabType.emoji, + PickerTabType.icon, + PickerTabType.custom, + ].map((e) => e.name).toList(), + ), child: Row( mainAxisSize: MainAxisSize.min, children: [ @@ -140,7 +148,8 @@ class MobileViewPage extends StatelessWidget { final iconUrl = userProfile?.iconUrl; if (iconUrl == null || iconUrl.isEmpty || - view.createdBy != userProfile?.id) { + view.createdBy != userProfile?.id || + !isURL(iconUrl)) { return const SizedBox.shrink(); } @@ -178,16 +187,18 @@ class MobileViewPage extends StatelessWidget { overflow: TextOverflow.ellipsis, text: TextSpan( children: [ - WidgetSpan( - child: SizedBox( - width: 20, - child: EmojiIconWidget( - emoji: icon, - emojiSize: 17.0, + if (icon.isNotEmpty) ...[ + WidgetSpan( + child: SizedBox( + width: 20, + child: RawEmojiIconWidget( + emoji: icon, + emojiSize: 18.0, + ), ), ), - ), - if (icon.isNotEmpty) const WidgetSpan(child: HSpace(2.0)), + const WidgetSpan(child: HSpace(8.0)), + ], TextSpan( text: name, style: Theme.of(context).textTheme.bodyMedium!.copyWith( @@ -204,7 +215,7 @@ class MobileViewPage extends StatelessWidget { Widget _buildAuthor(BuildContext context, RecentViewState state) { return FlowyText.regular( // view.createdBy.toString(), - 'Lucas', + '', fontSize: 12.0, color: Theme.of(context).hintColor, overflow: TextOverflow.ellipsis, @@ -214,7 +225,7 @@ class MobileViewPage extends StatelessWidget { Widget _buildLastViewed(BuildContext context) { final textColor = Theme.of(context).isLightMode ? const Color(0x7F171717) - : Colors.white.withOpacity(0.45); + : Colors.white.withValues(alpha: 0.45); if (timestamp == null) { return const SizedBox.shrink(); } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space.dart index a6545ce053..3bb62a92c8 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space.dart @@ -4,6 +4,7 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/home/space/mobile_space_header.dart'; import 'package:appflowy/mobile/presentation/home/space/mobile_space_menu.dart'; import 'package:appflowy/mobile/presentation/page_item/mobile_view_item.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/shared/list_extension.dart'; import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart'; import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart'; @@ -97,7 +98,7 @@ class MobileSpace extends StatelessWidget { Navigator.of(sheetContext).pop(); context.read().add( SpaceEvent.createPage( - name: layout.defaultName, + name: '', layout: layout, index: 0, openAfterCreate: true, @@ -151,7 +152,14 @@ class _Pages extends StatelessWidget { level: 0, leftPadding: HomeSpaceViewSizes.leftPadding, isFeedback: false, - onSelected: context.pushView, + onSelected: (v) => context.pushView( + v, + tabs: [ + PickerTabType.emoji, + PickerTabType.icon, + PickerTabType.custom, + ].map((e) => e.name).toList(), + ), endActionPane: (context) { final view = context.read().state.view; final actions = [ diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space_menu.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space_menu.dart index 0197f34940..485e07a28c 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space_menu.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/space/mobile_space_menu.dart @@ -339,7 +339,6 @@ class _SpaceMenuItemTrailingState extends State { context.read().add(const SpaceEvent.duplicate()); showToastNotification( - context, message: LocaleKeys.space_success_duplicateSpace.tr(), ); @@ -374,7 +373,6 @@ class _SpaceMenuItemTrailingState extends State { .add(SpaceEvent.rename(space: widget.space, name: name)); showToastNotification( - context, message: LocaleKeys.space_success_renameSpace.tr(), ); }, @@ -424,7 +422,6 @@ class _SpaceMenuItemTrailingState extends State { ); showToastNotification( - context, message: LocaleKeys.space_success_updateSpace.tr(), ); @@ -457,7 +454,6 @@ class _SpaceMenuItemTrailingState extends State { context.read().add(SpaceEvent.delete(widget.space)); showToastNotification( - context, message: LocaleKeys.space_success_deleteSpace.tr(), ); diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/ai_bubble_button.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/ai_bubble_button.dart index b3f10bbbd2..cc4176e0ef 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/ai_bubble_button.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/ai_bubble_button.dart @@ -42,7 +42,7 @@ class FloatingAIEntry extends StatelessWidget { blurRadius: 20, spreadRadius: 1, offset: const Offset(0, 4), - color: Colors.black.withOpacity(0.05), + color: Colors.black.withValues(alpha: 0.05), ), ], ); @@ -51,8 +51,8 @@ class FloatingAIEntry extends StatelessWidget { BoxDecoration _buildWrapperDecoration(BuildContext context) { final outlineColor = Theme.of(context).colorScheme.outline; final borderColor = Theme.of(context).isLightMode - ? outlineColor.withOpacity(0.7) - : outlineColor.withOpacity(0.3); + ? outlineColor.withValues(alpha: 0.7) + : outlineColor.withValues(alpha: 0.3); return BoxDecoration( borderRadius: BorderRadius.circular(30), color: Theme.of(context).colorScheme.surface, diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/mobile_space_tab.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/mobile_space_tab.dart index 9d78e39645..56f5f3e6ab 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/mobile_space_tab.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/tab/mobile_space_tab.dart @@ -6,11 +6,11 @@ import 'package:appflowy/mobile/presentation/home/tab/_tab_bar.dart'; import 'package:appflowy/mobile/presentation/home/tab/space_order_bloc.dart'; import 'package:appflowy/mobile/presentation/presentation.dart'; import 'package:appflowy/mobile/presentation/setting/workspace/invite_members_screen.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart'; import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart'; import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart'; import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; -import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; @@ -72,7 +72,14 @@ class _MobileSpaceTabState extends State listener: (context, state) { final lastCreatedPage = state.lastCreatedPage; if (lastCreatedPage != null) { - context.pushView(lastCreatedPage); + context.pushView( + lastCreatedPage, + tabs: [ + PickerTabType.emoji, + PickerTabType.icon, + PickerTabType.custom, + ].map((e) => e.name).toList(), + ); } }, ), @@ -82,7 +89,14 @@ class _MobileSpaceTabState extends State listener: (context, state) { final lastCreatedPage = state.lastCreatedRootView; if (lastCreatedPage != null) { - context.pushView(lastCreatedPage); + context.pushView( + lastCreatedPage, + tabs: [ + PickerTabType.emoji, + PickerTabType.icon, + PickerTabType.custom, + ].map((e) => e.name).toList(), + ); } }, ), @@ -153,8 +167,7 @@ class _MobileSpaceTabState extends State children: [ MobileHomeSpace(userProfile: widget.userProfile), // only show ai chat button for cloud user - if (widget.userProfile.authenticator == - AuthenticatorPB.AppFlowyCloud) + if (widget.userProfile.workspaceAuthType == AuthTypePB.Server) Positioned( bottom: MediaQuery.of(context).padding.bottom + 16, left: 20, @@ -165,8 +178,6 @@ class _MobileSpaceTabState extends State ); case MobileSpaceTabType.favorites: return MobileFavoriteSpace(userProfile: widget.userProfile); - default: - throw Exception('Unknown tab type: $tab'); } }).toList(); } @@ -180,7 +191,7 @@ class _MobileSpaceTabState extends State if (context.read().state.spaces.isNotEmpty) { context.read().add( SpaceEvent.createPage( - name: layout.defaultName, + name: '', layout: layout, openAfterCreate: true, ), @@ -189,7 +200,7 @@ class _MobileSpaceTabState extends State // only support create document in section context.read().add( SidebarSectionsEvent.createRootViewInSection( - name: layout.defaultName, + name: '', index: 0, viewSection: FolderSpaceType.public.toViewSectionPB, ), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/workspaces/workspace_menu_bottom_sheet.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/workspaces/workspace_menu_bottom_sheet.dart index 9a5bd8c511..d306f48964 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/workspaces/workspace_menu_bottom_sheet.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/workspaces/workspace_menu_bottom_sheet.dart @@ -123,6 +123,7 @@ class _CreateWorkspaceButton extends StatelessWidget { context.read().add( UserWorkspaceEvent.createWorkspace( name, + AuthTypePB.Server, ), ); }, @@ -139,7 +140,7 @@ class _CreateWorkspaceButton extends StatelessWidget { decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), border: Border.all( - color: const Color(0x01717171).withOpacity(0.12), + color: const Color(0x01717171).withValues(alpha: 0.12), width: 0.8, ), ), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/home/workspaces/workspace_more_options.dart b/frontend/appflowy_flutter/lib/mobile/presentation/home/workspaces/workspace_more_options.dart index 5f6066930f..bb6f6207f6 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/home/workspaces/workspace_more_options.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/home/workspaces/workspace_more_options.dart @@ -101,8 +101,6 @@ class WorkspaceMenuMoreOptions extends StatelessWidget { WorkspaceMenuMoreOption.leave, ), ); - default: - return const Placeholder(); } } } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/inline_actions/mobile_inline_actions_handler.dart b/frontend/appflowy_flutter/lib/mobile/presentation/inline_actions/mobile_inline_actions_handler.dart new file mode 100644 index 0000000000..74d5e56bce --- /dev/null +++ b/frontend/appflowy_flutter/lib/mobile/presentation/inline_actions/mobile_inline_actions_handler.dart @@ -0,0 +1,253 @@ +import 'dart:async'; + +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/inline_actions/inline_actions_menu.dart'; +import 'package:appflowy/plugins/inline_actions/inline_actions_result.dart'; +import 'package:appflowy/plugins/inline_actions/inline_actions_service.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:collection/collection.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flutter/material.dart'; + +import 'mobile_inline_actions_menu_group.dart'; + +extension _StartWithsSort on List { + void sortByStartsWithKeyword(String search) => sort( + (a, b) { + final aCount = a.startsWithKeywords + ?.where( + (key) => search.toLowerCase().startsWith(key), + ) + .length ?? + 0; + + final bCount = b.startsWithKeywords + ?.where( + (key) => search.toLowerCase().startsWith(key), + ) + .length ?? + 0; + + if (aCount > bCount) { + return -1; + } else if (bCount > aCount) { + return 1; + } + + return 0; + }, + ); +} + +const _invalidSearchesAmount = 10; + +class MobileInlineActionsHandler extends StatefulWidget { + const MobileInlineActionsHandler({ + super.key, + required this.results, + required this.editorState, + required this.menuService, + required this.onDismiss, + required this.style, + required this.service, + this.startCharAmount = 1, + this.startOffset = 0, + this.cancelBySpaceHandler, + }); + + final List results; + final EditorState editorState; + final InlineActionsMenuService menuService; + final VoidCallback onDismiss; + final InlineActionsMenuStyle style; + final int startCharAmount; + final InlineActionsService service; + final bool Function()? cancelBySpaceHandler; + final int startOffset; + + @override + State createState() => + _MobileInlineActionsHandlerState(); +} + +class _MobileInlineActionsHandlerState + extends State { + final _focusNode = + FocusNode(debugLabel: 'mobile_inline_actions_menu_handler'); + + late List results = widget.results; + int invalidCounter = 0; + late int startOffset; + + String _search = ''; + + set search(String search) { + _search = search; + _doSearch(); + } + + Future _doSearch() async { + final List newResults = []; + for (final handler in widget.service.handlers) { + final group = await handler.search(_search); + + if (group.results.isNotEmpty) { + newResults.add(group); + } + } + + invalidCounter = results.every((group) => group.results.isEmpty) + ? invalidCounter + 1 + : 0; + + if (invalidCounter >= _invalidSearchesAmount) { + widget.onDismiss(); + + // Workaround to bring focus back to editor + await editorState.updateSelectionWithReason(editorState.selection); + + return; + } + + _resetSelection(); + + newResults.sortByStartsWithKeyword(_search); + setState(() => results = newResults); + } + + void _resetSelection() { + _selectedGroup = 0; + _selectedIndex = 0; + } + + int _selectedGroup = 0; + int _selectedIndex = 0; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback( + (_) => _focusNode.requestFocus(), + ); + + startOffset = editorState.selection?.endIndex ?? 0; + keepEditorFocusNotifier.increase(); + editorState.selectionNotifier.addListener(onSelectionChanged); + } + + @override + void dispose() { + editorState.selectionNotifier.removeListener(onSelectionChanged); + _focusNode.dispose(); + keepEditorFocusNotifier.decrease(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final width = editorState.renderBox!.size.width - 24 * 2; + return Focus( + focusNode: _focusNode, + child: Container( + constraints: BoxConstraints( + maxHeight: 192, + minWidth: width, + maxWidth: width, + ), + margin: EdgeInsets.symmetric(horizontal: 24.0), + decoration: BoxDecoration( + color: widget.style.backgroundColor, + borderRadius: BorderRadius.circular(6.0), + boxShadow: [ + BoxShadow( + blurRadius: 5, + spreadRadius: 1, + color: Colors.black.withValues(alpha: 0.1), + ), + ], + ), + child: noResults + ? SizedBox( + width: 150, + child: FlowyText.regular( + LocaleKeys.inlineActions_noResults.tr(), + ), + ) + : SingleChildScrollView( + physics: const ClampingScrollPhysics(), + child: Material( + color: Colors.transparent, + child: Padding( + padding: EdgeInsets.all(6.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: results + .where((g) => g.results.isNotEmpty) + .mapIndexed( + (index, group) => MobileInlineActionsGroup( + result: group, + editorState: editorState, + menuService: menuService, + style: widget.style, + onSelected: widget.onDismiss, + startOffset: startOffset - widget.startCharAmount, + endOffset: + _search.length + widget.startCharAmount, + isLastGroup: index == results.length - 1, + isGroupSelected: _selectedGroup == index, + selectedIndex: _selectedIndex, + onPreSelect: (int value) { + setState(() { + _selectedGroup = index; + _selectedIndex = value; + }); + }, + ), + ) + .toList(), + ), + ), + ), + ), + ), + ); + } + + bool get noResults => + results.isEmpty || results.every((e) => e.results.isEmpty); + + int get groupLength => results.length; + + int lengthOfGroup(int index) => + results.length > index ? results[index].results.length : -1; + + InlineActionsMenuItem handlerOf(int groupIndex, int handlerIndex) => + results[groupIndex].results[handlerIndex]; + + EditorState get editorState => widget.editorState; + + InlineActionsMenuService get menuService => widget.menuService; + + void onSelectionChanged() { + final selection = editorState.selection; + if (selection == null) { + menuService.dismiss(); + return; + } + if (!selection.isCollapsed) { + menuService.dismiss(); + return; + } + final startOffset = widget.startOffset; + final endOffset = selection.end.offset; + if (endOffset < startOffset) { + menuService.dismiss(); + return; + } + final node = editorState.getNodeAtPath(selection.start.path); + final text = node?.delta?.toPlainText() ?? ''; + final search = text.substring(startOffset, endOffset); + this.search = search; + } +} diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/inline_actions/mobile_inline_actions_menu.dart b/frontend/appflowy_flutter/lib/mobile/presentation/inline_actions/mobile_inline_actions_menu.dart new file mode 100644 index 0000000000..6166671391 --- /dev/null +++ b/frontend/appflowy_flutter/lib/mobile/presentation/inline_actions/mobile_inline_actions_menu.dart @@ -0,0 +1,151 @@ +import 'dart:async'; + +import 'package:appflowy/plugins/inline_actions/inline_actions_menu.dart'; +import 'package:appflowy/plugins/inline_actions/inline_actions_result.dart'; +import 'package:appflowy/plugins/inline_actions/inline_actions_service.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter/material.dart'; + +import 'mobile_inline_actions_handler.dart'; + +class MobileInlineActionsMenu extends InlineActionsMenuService { + MobileInlineActionsMenu({ + required this.context, + required this.editorState, + required this.initialResults, + required this.style, + required this.service, + this.startCharAmount = 1, + this.cancelBySpaceHandler, + }); + + final BuildContext context; + final EditorState editorState; + final List initialResults; + final bool Function()? cancelBySpaceHandler; + final InlineActionsService service; + + @override + final InlineActionsMenuStyle style; + + final int startCharAmount; + + OverlayEntry? _menuEntry; + + @override + void dismiss() { + if (_menuEntry != null) { + editorState.service.keyboardService?.enable(); + editorState.service.scrollService?.enable(); + } + + _menuEntry?.remove(); + _menuEntry = null; + } + + @override + Future show() { + final completer = Completer(); + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + _show(); + completer.complete(); + }); + return completer.future; + } + + void _show() { + final selectionRects = editorState.selectionRects(); + if (selectionRects.isEmpty) { + return; + } + + const double menuHeight = 192.0; + const Offset menuOffset = Offset(0, 10); + final Offset editorOffset = + editorState.renderBox?.localToGlobal(Offset.zero) ?? Offset.zero; + final Size editorSize = editorState.renderBox!.size; + + // Default to opening the overlay below + Alignment alignment = Alignment.topLeft; + + final firstRect = selectionRects.first; + Offset offset = firstRect.bottomRight + menuOffset; + + // Show above + if (offset.dy + menuHeight >= editorOffset.dy + editorSize.height) { + offset = firstRect.topRight - menuOffset; + alignment = Alignment.bottomLeft; + + offset = Offset( + offset.dx, + MediaQuery.of(context).size.height - offset.dy, + ); + } + + final (left, top, right, bottom) = _getPosition(alignment, offset); + + _menuEntry = OverlayEntry( + builder: (context) => SizedBox( + width: editorSize.width, + height: editorSize.height, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: dismiss, + child: Stack( + children: [ + Positioned( + top: top, + bottom: bottom, + left: left, + right: right, + child: MobileInlineActionsHandler( + service: service, + results: initialResults, + editorState: editorState, + menuService: this, + onDismiss: dismiss, + style: style, + startCharAmount: startCharAmount, + cancelBySpaceHandler: cancelBySpaceHandler, + startOffset: editorState.selection?.start.offset ?? 0, + ), + ), + ], + ), + ), + ), + ); + + Overlay.of(context).insert(_menuEntry!); + + editorState.service.keyboardService?.disable(showCursor: true); + editorState.service.scrollService?.disable(); + } + + (double? left, double? top, double? right, double? bottom) _getPosition( + Alignment alignment, + Offset offset, + ) { + double? left, top, right, bottom; + switch (alignment) { + case Alignment.topLeft: + left = 0; + top = offset.dy; + break; + case Alignment.bottomLeft: + left = 0; + bottom = offset.dy; + break; + case Alignment.topRight: + right = offset.dx; + top = offset.dy; + break; + case Alignment.bottomRight: + right = offset.dx; + bottom = offset.dy; + break; + } + + return (left, top, right, bottom); + } +} diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/inline_actions/mobile_inline_actions_menu_group.dart b/frontend/appflowy_flutter/lib/mobile/presentation/inline_actions/mobile_inline_actions_menu_group.dart new file mode 100644 index 0000000000..f340319254 --- /dev/null +++ b/frontend/appflowy_flutter/lib/mobile/presentation/inline_actions/mobile_inline_actions_menu_group.dart @@ -0,0 +1,152 @@ +import 'package:appflowy/plugins/inline_actions/inline_actions_menu.dart'; +import 'package:appflowy/plugins/inline_actions/inline_actions_result.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:collection/collection.dart'; +import 'package:flowy_infra_ui/style_widget/button.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flutter/material.dart'; + +class MobileInlineActionsGroup extends StatelessWidget { + const MobileInlineActionsGroup({ + super.key, + required this.result, + required this.editorState, + required this.menuService, + required this.style, + required this.onSelected, + required this.startOffset, + required this.endOffset, + required this.onPreSelect, + this.isLastGroup = false, + this.isGroupSelected = false, + this.selectedIndex = 0, + }); + + final InlineActionsResult result; + final EditorState editorState; + final InlineActionsMenuService menuService; + final InlineActionsMenuStyle style; + final VoidCallback onSelected; + final ValueChanged onPreSelect; + final int startOffset; + final int endOffset; + + final bool isLastGroup; + final bool isGroupSelected; + final int selectedIndex; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (result.title != null) ...[ + SizedBox( + height: 36, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Align( + alignment: Alignment.centerLeft, + child: FlowyText.medium( + result.title!, + color: style.groupTextColor, + fontSize: 12, + ), + ), + ), + ), + ], + ...result.results.mapIndexed( + (index, item) => GestureDetector( + onTapDown: (e) { + onPreSelect.call(index); + }, + child: MobileInlineActionsWidget( + item: item, + editorState: editorState, + menuService: menuService, + isSelected: isGroupSelected && index == selectedIndex, + style: style, + onSelected: onSelected, + startOffset: startOffset, + endOffset: endOffset, + ), + ), + ), + ], + ); + } +} + +class MobileInlineActionsWidget extends StatelessWidget { + const MobileInlineActionsWidget({ + super.key, + required this.item, + required this.editorState, + required this.menuService, + required this.isSelected, + required this.style, + required this.onSelected, + required this.startOffset, + required this.endOffset, + }); + + final InlineActionsMenuItem item; + final EditorState editorState; + final InlineActionsMenuService menuService; + final bool isSelected; + final InlineActionsMenuStyle style; + final VoidCallback onSelected; + final int startOffset; + final int endOffset; + + @override + Widget build(BuildContext context) { + final hasIcon = item.iconBuilder != null; + return Container( + height: 36, + decoration: BoxDecoration( + color: isSelected ? style.menuItemSelectedColor : null, + borderRadius: BorderRadius.circular(6.0), + ), + child: FlowyButton( + expand: true, + isSelected: isSelected, + text: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Align( + alignment: Alignment.centerLeft, + child: Row( + children: [ + if (hasIcon) ...[ + item.iconBuilder!.call(isSelected), + SizedBox(width: 12), + ], + Flexible( + child: FlowyText.regular( + item.label, + figmaLineHeight: 18, + overflow: TextOverflow.ellipsis, + fontSize: 16, + color: style.menuItemSelectedTextColor, + ), + ), + ], + ), + ), + ), + onTap: () => _onPressed(context), + ), + ); + } + + void _onPressed(BuildContext context) { + onSelected(); + item.onSelected?.call( + context, + editorState, + menuService, + (startOffset, endOffset), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart b/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart index 4d8bc16103..3c6adb8627 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/mobile_bottom_navigation_bar.dart @@ -332,7 +332,6 @@ class _NotificationNavigationBar extends StatelessWidget { } showToastNotification( - context, message: LocaleKeys .settings_notifications_markAsReadNotifications_allSuccess .tr(), @@ -350,7 +349,6 @@ class _NotificationNavigationBar extends StatelessWidget { } showToastNotification( - context, message: LocaleKeys.settings_notifications_archiveNotifications_allSuccess .tr(), ); @@ -365,14 +363,14 @@ class _NotificationNavigationBar extends StatelessWidget { extension on BuildContext { Color get backgroundColor { return Theme.of(this).isLightMode - ? Colors.white.withOpacity(0.95) - : const Color(0xFF23262B).withOpacity(0.95); + ? Colors.white.withValues(alpha: 0.95) + : const Color(0xFF23262B).withValues(alpha: 0.95); } Color get borderColor { return Theme.of(this).isLightMode ? const Color(0x141F2329) - : const Color(0xFF23262B).withOpacity(0.5); + : const Color(0xFF23262B).withValues(alpha: 0.5); } Border? get border { diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/notifications/mobile_notifications_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/notifications/mobile_notifications_page.dart index a8055b8ba2..33c2eb3905 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/notifications/mobile_notifications_page.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/notifications/mobile_notifications_page.dart @@ -50,9 +50,9 @@ class _MobileNotificationsScreenState extends State orElse: () => const Center(child: CircularProgressIndicator.adaptive()), workspaceFailure: () => const WorkspaceFailedScreen(), - success: (workspaceSetting, userProfile) => + success: (workspaceLatest, userProfile) => _NotificationScreenContent( - workspaceSetting: workspaceSetting, + workspaceLatest: workspaceLatest, userProfile: userProfile, controller: controller, reminderBloc: reminderBloc, @@ -66,13 +66,13 @@ class _MobileNotificationsScreenState extends State class _NotificationScreenContent extends StatelessWidget { const _NotificationScreenContent({ - required this.workspaceSetting, + required this.workspaceLatest, required this.userProfile, required this.controller, required this.reminderBloc, }); - final WorkspaceSettingPB workspaceSetting; + final WorkspaceLatestPB workspaceLatest; final UserProfilePB userProfile; final TabController controller; final ReminderBloc reminderBloc; @@ -84,7 +84,7 @@ class _NotificationScreenContent extends StatelessWidget { ..add( SidebarSectionsEvent.initial( userProfile, - workspaceSetting.workspaceId, + workspaceLatest.workspaceId, ), ), child: BlocBuilder( diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/color.dart b/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/color.dart index 8a59336378..e11e91ada5 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/color.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/color.dart @@ -6,6 +6,6 @@ extension NotificationItemColors on BuildContext { if (Theme.of(this).isLightMode) { return const Color(0xFF171717); } - return const Color(0xFFffffff).withOpacity(0.8); + return const Color(0xFFffffff).withValues(alpha: 0.8); } } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/settings_popup_menu.dart b/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/settings_popup_menu.dart index dfa277f2ef..e694f9932d 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/settings_popup_menu.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/settings_popup_menu.dart @@ -108,7 +108,6 @@ class NotificationSettingsPopupMenu extends StatelessWidget { void _onMarkAllAsRead(BuildContext context) { showToastNotification( - context, message: LocaleKeys .settings_notifications_markAsReadNotifications_allSuccess .tr(), @@ -119,7 +118,6 @@ class NotificationSettingsPopupMenu extends StatelessWidget { void _onArchiveAll(BuildContext context) { showToastNotification( - context, message: LocaleKeys.settings_notifications_archiveNotifications_allSuccess .tr(), ); @@ -133,7 +131,6 @@ class NotificationSettingsPopupMenu extends StatelessWidget { } showToastNotification( - context, message: 'Unarchive all success (Debug Mode)', ); diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/slide_actions.dart b/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/slide_actions.dart index 85f468c76c..d1216eed98 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/slide_actions.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/slide_actions.dart @@ -31,7 +31,6 @@ enum NotificationPaneActionType { size: 24.0, onPressed: (context) { showToastNotification( - context, message: LocaleKeys .settings_notifications_markAsReadNotifications_success .tr(), @@ -55,7 +54,6 @@ enum NotificationPaneActionType { size: 24.0, onPressed: (context) { showToastNotification( - context, message: 'Unarchive notification success', ); @@ -168,7 +166,6 @@ class _NotificationMoreActions extends StatelessWidget { Navigator.of(context).pop(); showToastNotification( - context, message: LocaleKeys.settings_notifications_markAsReadNotifications_success .tr(), ); @@ -191,7 +188,6 @@ class _NotificationMoreActions extends StatelessWidget { void _onArchive(BuildContext context) { showToastNotification( - context, message: LocaleKeys.settings_notifications_archiveNotifications_success .tr() .tr(), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/tab.dart b/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/tab.dart index 7dda8f0a14..45e801e07c 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/tab.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/notifications/widgets/tab.dart @@ -74,7 +74,6 @@ class _NotificationTabState extends State if (context.mounted) { showToastNotification( - context, message: LocaleKeys.settings_notifications_refreshSuccess.tr(), ); } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/page_item/mobile_view_item.dart b/frontend/appflowy_flutter/lib/mobile/presentation/page_item/mobile_view_item.dart index ee4a371eb5..6e2611a684 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/page_item/mobile_view_item.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/page_item/mobile_view_item.dart @@ -300,7 +300,8 @@ class _SingleMobileInnerViewItemState extends State { } Widget _buildViewIcon() { - final icon = widget.view.icon.value.isNotEmpty + final iconData = widget.view.icon.toEmojiIconData(); + final icon = iconData.isNotEmpty ? EmojiIconWidget( emoji: widget.view.icon.toEmojiIconData(), emojiSize: Platform.isAndroid ? 16.0 : 18.0, diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/selection_menu/mobile_selection_menu.dart b/frontend/appflowy_flutter/lib/mobile/presentation/selection_menu/mobile_selection_menu.dart new file mode 100644 index 0000000000..f69360575a --- /dev/null +++ b/frontend/appflowy_flutter/lib/mobile/presentation/selection_menu/mobile_selection_menu.dart @@ -0,0 +1,296 @@ +import 'dart:async'; + +import 'package:appflowy/mobile/presentation/selection_menu/mobile_selection_menu_item.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter/material.dart'; + +import 'mobile_selection_menu_item_widget.dart'; +import 'mobile_selection_menu_widget.dart'; + +class MobileSelectionMenu extends SelectionMenuService { + MobileSelectionMenu({ + required this.context, + required this.editorState, + required this.selectionMenuItems, + this.deleteSlashByDefault = false, + this.deleteKeywordsByDefault = false, + this.style = MobileSelectionMenuStyle.light, + this.itemCountFilter = 0, + this.startOffset = 0, + this.singleColumn = false, + }); + + final BuildContext context; + final EditorState editorState; + final List selectionMenuItems; + final bool deleteSlashByDefault; + final bool deleteKeywordsByDefault; + final bool singleColumn; + + @override + final MobileSelectionMenuStyle style; + + OverlayEntry? _selectionMenuEntry; + Offset _offset = Offset.zero; + Alignment _alignment = Alignment.topLeft; + final int itemCountFilter; + final int startOffset; + ValueNotifier<_Position> _positionNotifier = ValueNotifier(_Position.zero); + + @override + void dismiss() { + if (_selectionMenuEntry != null) { + editorState.service.keyboardService?.enable(); + editorState.service.scrollService?.enable(); + editorState + .removeScrollViewScrolledListener(_checkPositionAfterScrolling); + _positionNotifier.dispose(); + } + + _selectionMenuEntry?.remove(); + _selectionMenuEntry = null; + } + + @override + Future show() async { + final completer = Completer(); + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + _show(); + editorState.addScrollViewScrolledListener(_checkPositionAfterScrolling); + completer.complete(); + }); + return completer.future; + } + + void _show() { + final position = _getCurrentPosition(); + if (position == null) return; + + final editorHeight = editorState.renderBox!.size.height; + final editorWidth = editorState.renderBox!.size.width; + + _positionNotifier = ValueNotifier(position); + final showAtTop = position.top != null; + _selectionMenuEntry = OverlayEntry( + builder: (context) { + return SizedBox( + width: editorWidth, + height: editorHeight, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: dismiss, + child: Stack( + children: [ + ValueListenableBuilder( + valueListenable: _positionNotifier, + builder: (context, value, _) { + return Positioned( + top: value.top, + bottom: value.bottom, + left: value.left, + right: value.right, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: MobileSelectionMenuWidget( + selectionMenuStyle: style, + singleColumn: singleColumn, + showAtTop: showAtTop, + items: selectionMenuItems + ..forEach((element) { + if (element is MobileSelectionMenuItem) { + element.deleteSlash = false; + element.deleteKeywords = + deleteKeywordsByDefault; + for (final e in element.children) { + e.deleteSlash = deleteSlashByDefault; + e.deleteKeywords = deleteKeywordsByDefault; + e.onSelected = () { + dismiss(); + }; + } + } else { + element.deleteSlash = deleteSlashByDefault; + element.deleteKeywords = + deleteKeywordsByDefault; + element.onSelected = () { + dismiss(); + }; + } + }), + maxItemInRow: 5, + editorState: editorState, + itemCountFilter: itemCountFilter, + startOffset: startOffset, + menuService: this, + onExit: () { + dismiss(); + }, + deleteSlashByDefault: deleteSlashByDefault, + ), + ), + ); + }, + ), + ], + ), + ), + ); + }, + ); + + Overlay.of(context, rootOverlay: true).insert(_selectionMenuEntry!); + + editorState.service.keyboardService?.disable(showCursor: true); + editorState.service.scrollService?.disable(); + } + + /// the workaround for: editor auto scrolling that will cause wrong position + /// of slash menu + void _checkPositionAfterScrolling() { + final position = _getCurrentPosition(); + if (position == null) return; + if (position == _positionNotifier.value) { + Future.delayed(const Duration(milliseconds: 100)).then((_) { + final position = _getCurrentPosition(); + if (position == null) return; + if (position != _positionNotifier.value) { + _positionNotifier.value = position; + } + }); + } else { + _positionNotifier.value = position; + } + } + + _Position? _getCurrentPosition() { + final selectionRects = editorState.selectionRects(); + if (selectionRects.isEmpty) { + return null; + } + final screenSize = MediaQuery.of(context).size; + calculateSelectionMenuOffset(selectionRects.first, screenSize); + final (left, top, right, bottom) = getPosition(); + return _Position(left, top, right, bottom); + } + + @override + Alignment get alignment { + return _alignment; + } + + @override + Offset get offset { + return _offset; + } + + @override + (double? left, double? top, double? right, double? bottom) getPosition() { + double? left, top, right, bottom; + switch (alignment) { + case Alignment.topLeft: + left = offset.dx; + top = offset.dy; + break; + case Alignment.bottomLeft: + left = offset.dx; + bottom = offset.dy; + break; + case Alignment.topRight: + right = offset.dx; + top = offset.dy; + break; + case Alignment.bottomRight: + right = offset.dx; + bottom = offset.dy; + break; + } + return (left, top, right, bottom); + } + + void calculateSelectionMenuOffset(Rect rect, Size screenSize) { + // Workaround: We can customize the padding through the [EditorStyle], + // but the coordinates of overlay are not properly converted currently. + // Just subtract the padding here as a result. + const menuHeight = 192.0, menuWidth = 240.0; + final editorOffset = + editorState.renderBox?.localToGlobal(Offset.zero) ?? Offset.zero; + final editorHeight = editorState.renderBox!.size.height; + final screenHeight = screenSize.height; + final editorWidth = editorState.renderBox!.size.width; + final rectHeight = rect.height; + + // show below default + _alignment = Alignment.bottomRight; + final bottomRight = rect.topLeft; + final offset = bottomRight; + final limitX = editorWidth + editorOffset.dx - menuWidth, + limitY = screenHeight - + editorHeight + + editorOffset.dy - + menuHeight - + rectHeight; + _offset = Offset( + editorWidth - offset.dx - menuWidth, + screenHeight - offset.dy - menuHeight - rectHeight, + ); + + if (offset.dy + menuHeight >= editorOffset.dy + editorHeight) { + /// show above + if (offset.dy > menuHeight) { + _offset = Offset( + _offset.dx, + offset.dy - menuHeight, + ); + _alignment = Alignment.topRight; + } else { + _offset = Offset( + _offset.dx, + limitY, + ); + } + } + + if (offset.dx + menuWidth >= editorOffset.dx + editorWidth) { + /// show left + if (offset.dx > menuWidth) { + _alignment = _alignment == Alignment.bottomRight + ? Alignment.bottomLeft + : Alignment.topLeft; + _offset = Offset( + offset.dx - menuWidth, + _offset.dy, + ); + } else { + _offset = Offset( + limitX, + _offset.dy, + ); + } + } + } +} + +class _Position { + const _Position(this.left, this.top, this.right, this.bottom); + + final double? left; + final double? top; + final double? right; + final double? bottom; + + static const _Position zero = _Position(0, 0, 0, 0); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is _Position && + runtimeType == other.runtimeType && + left == other.left && + top == other.top && + right == other.right && + bottom == other.bottom; + + @override + int get hashCode => + left.hashCode ^ top.hashCode ^ right.hashCode ^ bottom.hashCode; +} diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/selection_menu/mobile_selection_menu_item.dart b/frontend/appflowy_flutter/lib/mobile/presentation/selection_menu/mobile_selection_menu_item.dart new file mode 100644 index 0000000000..22e202816e --- /dev/null +++ b/frontend/appflowy_flutter/lib/mobile/presentation/selection_menu/mobile_selection_menu_item.dart @@ -0,0 +1,18 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; + +class MobileSelectionMenuItem extends SelectionMenuItem { + MobileSelectionMenuItem({ + required super.getName, + required super.icon, + super.keywords = const [], + required super.handler, + this.children = const [], + super.nameBuilder, + super.deleteKeywords, + super.deleteSlash, + }); + + final List children; + + bool get isNotEmpty => children.isNotEmpty; +} diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/selection_menu/mobile_selection_menu_item_widget.dart b/frontend/appflowy_flutter/lib/mobile/presentation/selection_menu/mobile_selection_menu_item_widget.dart new file mode 100644 index 0000000000..bdee8f1857 --- /dev/null +++ b/frontend/appflowy_flutter/lib/mobile/presentation/selection_menu/mobile_selection_menu_item_widget.dart @@ -0,0 +1,138 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter/material.dart'; + +import 'mobile_selection_menu_item.dart'; + +class MobileSelectionMenuItemWidget extends StatelessWidget { + const MobileSelectionMenuItemWidget({ + super.key, + required this.editorState, + required this.menuService, + required this.item, + required this.isSelected, + required this.selectionMenuStyle, + required this.onTap, + }); + + final EditorState editorState; + final SelectionMenuService menuService; + final SelectionMenuItem item; + final bool isSelected; + final MobileSelectionMenuStyle selectionMenuStyle; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + final style = selectionMenuStyle; + final showRightArrow = item is MobileSelectionMenuItem && + (item as MobileSelectionMenuItem).isNotEmpty; + return Container( + padding: const EdgeInsets.symmetric(horizontal: 6), + child: TextButton.icon( + icon: item.icon( + editorState, + false, + selectionMenuStyle, + ), + style: ButtonStyle( + alignment: Alignment.centerLeft, + overlayColor: WidgetStateProperty.all(Colors.transparent), + backgroundColor: isSelected + ? WidgetStateProperty.all( + style.selectionMenuItemSelectedColor, + ) + : WidgetStateProperty.all(Colors.transparent), + ), + label: Row( + children: [ + item.nameBuilder?.call(item.name, style, false) ?? + Text( + item.name, + textAlign: TextAlign.left, + style: TextStyle( + color: style.selectionMenuItemTextColor, + fontSize: 16.0, + ), + ), + if (showRightArrow) ...[ + Spacer(), + Icon( + Icons.keyboard_arrow_right_rounded, + color: style.selectionMenuItemRightIconColor, + ), + ], + ], + ), + onPressed: () { + onTap.call(); + item.handler( + editorState, + menuService, + context, + ); + }, + ), + ); + } +} + +class MobileSelectionMenuStyle extends SelectionMenuStyle { + const MobileSelectionMenuStyle({ + required super.selectionMenuBackgroundColor, + required super.selectionMenuItemTextColor, + required super.selectionMenuItemIconColor, + required super.selectionMenuItemSelectedTextColor, + required super.selectionMenuItemSelectedIconColor, + required super.selectionMenuItemSelectedColor, + required super.selectionMenuUnselectedLabelColor, + required super.selectionMenuDividerColor, + required super.selectionMenuLinkBorderColor, + required super.selectionMenuInvalidLinkColor, + required super.selectionMenuButtonColor, + required super.selectionMenuButtonTextColor, + required super.selectionMenuButtonIconColor, + required super.selectionMenuButtonBorderColor, + required super.selectionMenuTabIndicatorColor, + required this.selectionMenuItemRightIconColor, + }); + + final Color selectionMenuItemRightIconColor; + + static const MobileSelectionMenuStyle light = MobileSelectionMenuStyle( + selectionMenuBackgroundColor: Color(0xFFFFFFFF), + selectionMenuItemTextColor: Color(0xFF1F2225), + selectionMenuItemIconColor: Color(0xFF333333), + selectionMenuItemSelectedColor: Color(0xFFF2F5F7), + selectionMenuItemRightIconColor: Color(0xB31E2022), + selectionMenuItemSelectedTextColor: Color.fromARGB(255, 56, 91, 247), + selectionMenuItemSelectedIconColor: Color.fromARGB(255, 56, 91, 247), + selectionMenuUnselectedLabelColor: Color(0xFF333333), + selectionMenuDividerColor: Color(0xFF00BCF0), + selectionMenuLinkBorderColor: Color(0xFF00BCF0), + selectionMenuInvalidLinkColor: Color(0xFFE53935), + selectionMenuButtonColor: Color(0xFF00BCF0), + selectionMenuButtonTextColor: Color(0xFF333333), + selectionMenuButtonIconColor: Color(0xFF333333), + selectionMenuButtonBorderColor: Color(0xFF00BCF0), + selectionMenuTabIndicatorColor: Color(0xFF00BCF0), + ); + + static const MobileSelectionMenuStyle dark = MobileSelectionMenuStyle( + selectionMenuBackgroundColor: Color(0xFF424242), + selectionMenuItemTextColor: Color(0xFFFFFFFF), + selectionMenuItemIconColor: Color(0xFFFFFFFF), + selectionMenuItemSelectedColor: Color(0xFF666666), + selectionMenuItemRightIconColor: Color(0xB3FFFFFF), + selectionMenuItemSelectedTextColor: Color(0xFF131720), + selectionMenuItemSelectedIconColor: Color(0xFF131720), + selectionMenuUnselectedLabelColor: Color(0xFFBBC3CD), + selectionMenuDividerColor: Color(0xFF3A3F44), + selectionMenuLinkBorderColor: Color(0xFF3A3F44), + selectionMenuInvalidLinkColor: Color(0xFFE53935), + selectionMenuButtonColor: Color(0xFF00BCF0), + selectionMenuButtonTextColor: Color(0xFFFFFFFF), + selectionMenuButtonIconColor: Color(0xFFFFFFFF), + selectionMenuButtonBorderColor: Color(0xFF00BCF0), + selectionMenuTabIndicatorColor: Color(0xFF00BCF0), + ); +} diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/selection_menu/mobile_selection_menu_widget.dart b/frontend/appflowy_flutter/lib/mobile/presentation/selection_menu/mobile_selection_menu_widget.dart new file mode 100644 index 0000000000..d96dd224e1 --- /dev/null +++ b/frontend/appflowy_flutter/lib/mobile/presentation/selection_menu/mobile_selection_menu_widget.dart @@ -0,0 +1,392 @@ +import 'dart:math'; + +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; + +import 'mobile_selection_menu_item.dart'; +import 'mobile_selection_menu_item_widget.dart'; +import 'slash_keyboard_service_interceptor.dart'; + +class MobileSelectionMenuWidget extends StatefulWidget { + const MobileSelectionMenuWidget({ + super.key, + required this.items, + required this.itemCountFilter, + required this.maxItemInRow, + required this.menuService, + required this.editorState, + required this.onExit, + required this.selectionMenuStyle, + required this.deleteSlashByDefault, + required this.singleColumn, + required this.startOffset, + required this.showAtTop, + this.nameBuilder, + }); + + final List items; + final int itemCountFilter; + final int maxItemInRow; + + final SelectionMenuService menuService; + final EditorState editorState; + + final VoidCallback onExit; + + final MobileSelectionMenuStyle selectionMenuStyle; + + final bool deleteSlashByDefault; + final bool singleColumn; + final bool showAtTop; + final int startOffset; + + final SelectionMenuItemNameBuilder? nameBuilder; + + @override + State createState() => + _MobileSelectionMenuWidgetState(); +} + +class _MobileSelectionMenuWidgetState extends State { + final _focusNode = FocusNode(debugLabel: 'popup_list_widget'); + + List _showingItems = []; + + int _searchCounter = 0; + + EditorState get editorState => widget.editorState; + + SelectionMenuService get menuService => widget.menuService; + + String _keyword = ''; + + String get keyword => _keyword; + + int selectedIndex = 0; + + late AppFlowyKeyboardServiceInterceptor keyboardInterceptor; + + List get filterItems { + final List items = []; + for (final item in widget.items) { + if (item is MobileSelectionMenuItem) { + for (final childItem in item.children) { + items.add(childItem); + } + } else { + items.add(item); + } + } + return items; + } + + set keyword(String newKeyword) { + _keyword = newKeyword; + + // Search items according to the keyword, and calculate the length of + // the longest keyword, which is used to dismiss the selection_service. + var maxKeywordLength = 0; + + final items = newKeyword.isEmpty + ? widget.items + : filterItems + .where( + (item) => item.allKeywords.any((keyword) { + final value = keyword.contains(newKeyword.toLowerCase()); + if (value) { + maxKeywordLength = max(maxKeywordLength, keyword.length); + } + return value; + }), + ) + .toList(growable: false); + + AppFlowyEditorLog.ui.debug('$items'); + + if (keyword.length >= maxKeywordLength + 2 && + !(widget.deleteSlashByDefault && _searchCounter < 2)) { + return widget.onExit(); + } + + _showingItems = items; + refreshSelectedIndex(); + + if (_showingItems.isEmpty) { + _searchCounter++; + } else { + _searchCounter = 0; + } + } + + @override + void initState() { + super.initState(); + _showingItems = buildInitialItems(); + + keepEditorFocusNotifier.increase(); + WidgetsBinding.instance.addPostFrameCallback((_) { + _focusNode.requestFocus(); + }); + + keyboardInterceptor = SlashKeyboardServiceInterceptor( + onDelete: () async { + if (!mounted) return false; + final hasItemsChanged = !isInitialItems(); + if (keyword.isEmpty && hasItemsChanged) { + _showingItems = buildInitialItems(); + refreshSelectedIndex(); + return true; + } + return false; + }, + onEnter: () { + if (!mounted) return; + if (_showingItems.isEmpty) return; + final item = _showingItems[selectedIndex]; + if (item is MobileSelectionMenuItem) { + selectedIndex = 0; + item.onSelected?.call(); + } else { + item.handler( + editorState, + menuService, + context, + ); + } + }, + ); + editorState.service.keyboardService + ?.registerInterceptor(keyboardInterceptor); + editorState.selectionNotifier.addListener(onSelectionChanged); + } + + @override + void dispose() { + editorState.service.keyboardService + ?.unregisterInterceptor(keyboardInterceptor); + editorState.selectionNotifier.removeListener(onSelectionChanged); + _focusNode.dispose(); + keepEditorFocusNotifier.decrease(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 192, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (widget.showAtTop) Spacer(), + Focus( + focusNode: _focusNode, + child: DecoratedBox( + decoration: BoxDecoration( + color: widget.selectionMenuStyle.selectionMenuBackgroundColor, + boxShadow: [ + BoxShadow( + blurRadius: 5, + spreadRadius: 1, + color: Colors.black.withValues(alpha: 0.1), + ), + ], + borderRadius: BorderRadius.circular(6.0), + ), + child: _showingItems.isEmpty + ? _buildNoResultsWidget(context) + : _buildResultsWidget( + context, + _showingItems, + widget.itemCountFilter, + ), + ), + ), + if (!widget.showAtTop) Spacer(), + ], + ), + ); + } + + void onSelectionChanged() { + final selection = editorState.selection; + if (selection == null) { + widget.onExit(); + return; + } + if (!selection.isCollapsed) { + widget.onExit(); + return; + } + final startOffset = widget.startOffset; + final endOffset = selection.end.offset; + if (endOffset < startOffset) { + widget.onExit(); + return; + } + final node = editorState.getNodeAtPath(selection.start.path); + final text = node?.delta?.toPlainText() ?? ''; + final search = text.substring(startOffset, endOffset); + keyword = search; + } + + Widget _buildResultsWidget( + BuildContext buildContext, + List items, + int itemCountFilter, + ) { + if (widget.singleColumn) { + final List itemWidgets = []; + for (var i = 0; i < items.length; i++) { + final item = items[i]; + itemWidgets.add( + GestureDetector( + onTapDown: (e) { + setState(() { + selectedIndex = i; + }); + }, + child: MobileSelectionMenuItemWidget( + item: item, + isSelected: i == selectedIndex, + editorState: editorState, + menuService: menuService, + selectionMenuStyle: widget.selectionMenuStyle, + onTap: () { + if (item is MobileSelectionMenuItem) refreshSelectedIndex(); + }, + ), + ), + ); + } + return ConstrainedBox( + constraints: const BoxConstraints( + maxHeight: 192, + minWidth: 240, + maxWidth: 240, + ), + child: ListView( + shrinkWrap: true, + padding: EdgeInsets.zero, + children: itemWidgets, + ), + ); + } else { + final List columns = []; + List itemWidgets = []; + // apply item count filter + if (itemCountFilter > 0) { + items = items.take(itemCountFilter).toList(); + } + + for (var i = 0; i < items.length; i++) { + final item = items[i]; + if (i != 0 && i % (widget.maxItemInRow) == 0) { + columns.add( + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: itemWidgets, + ), + ); + itemWidgets = []; + } + itemWidgets.add( + MobileSelectionMenuItemWidget( + item: item, + isSelected: false, + editorState: editorState, + menuService: menuService, + selectionMenuStyle: widget.selectionMenuStyle, + onTap: () { + if (item is MobileSelectionMenuItem) refreshSelectedIndex(); + }, + ), + ); + } + if (itemWidgets.isNotEmpty) { + columns.add( + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: itemWidgets, + ), + ); + itemWidgets = []; + } + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: columns, + ); + } + } + + void refreshSelectedIndex() { + if (!mounted) return; + setState(() { + selectedIndex = 0; + }); + } + + Widget _buildNoResultsWidget(BuildContext context) { + return DecoratedBox( + decoration: BoxDecoration( + color: Theme.of(context).cardColor, + boxShadow: [ + BoxShadow( + blurRadius: 5, + spreadRadius: 1, + color: Colors.black.withValues(alpha: 0.1), + ), + ], + borderRadius: BorderRadius.circular(12.0), + ), + child: SizedBox( + width: 240, + height: 48, + child: Padding( + padding: const EdgeInsets.all(6.0), + child: Material( + color: Colors.transparent, + child: Center( + child: Text( + LocaleKeys.inlineActions_noResults.tr(), + style: TextStyle(fontSize: 18.0, color: Color(0x801F2225)), + textAlign: TextAlign.center, + ), + ), + ), + ), + ), + ); + } + + List buildInitialItems() { + final List items = []; + for (final item in widget.items) { + if (item is MobileSelectionMenuItem) { + item.onSelected = () { + if (mounted) { + setState(() { + _showingItems = item.children + .map((e) => e..onSelected = widget.onExit) + .toList(); + }); + } + }; + } + items.add(item); + } + return items; + } + + bool isInitialItems() { + if (_showingItems.length != widget.items.length) return false; + int i = 0; + for (final item in _showingItems) { + final widgetItem = widget.items[i]; + if (widgetItem.name != item.name) return false; + i++; + } + return true; + } +} diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/selection_menu/slash_keyboard_service_interceptor.dart b/frontend/appflowy_flutter/lib/mobile/presentation/selection_menu/slash_keyboard_service_interceptor.dart new file mode 100644 index 0000000000..b7d0fd6e83 --- /dev/null +++ b/frontend/appflowy_flutter/lib/mobile/presentation/selection_menu/slash_keyboard_service_interceptor.dart @@ -0,0 +1,42 @@ +import 'package:appflowy/plugins/document/presentation/editor_plugins/keyboard_interceptor/keyboard_interceptor.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +class SlashKeyboardServiceInterceptor extends EditorKeyboardInterceptor { + SlashKeyboardServiceInterceptor({ + required this.onDelete, + required this.onEnter, + }); + + final AsyncValueGetter onDelete; + final VoidCallback onEnter; + + @override + Future interceptDelete( + TextEditingDeltaDeletion deletion, + EditorState editorState, + ) async { + final intercept = await onDelete.call(); + if (intercept) { + return true; + } else { + return super.interceptDelete(deletion, editorState); + } + } + + @override + Future interceptInsert( + TextEditingDeltaInsertion insertion, + EditorState editorState, + List characterShortcutEvents, + ) async { + final text = insertion.textInserted; + if (text.contains('\n')) { + onEnter.call(); + return true; + } + return super + .interceptInsert(insertion, editorState, characterShortcutEvents); + } +} diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/about/about_setting_group.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/about/about_setting_group.dart index d4f0766626..2d5a3176cd 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/about/about_setting_group.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/about/about_setting_group.dart @@ -25,14 +25,14 @@ class AboutSettingGroup extends StatelessWidget { trailing: const Icon( Icons.chevron_right, ), - onTap: () => afLaunchUrlString('https://appflowy.io/privacy'), + onTap: () => afLaunchUrlString('https://appflowy.com/privacy'), ), MobileSettingItem( name: LocaleKeys.settings_mobile_termsAndConditions.tr(), trailing: const Icon( Icons.chevron_right, ), - onTap: () => afLaunchUrlString('https://appflowy.io/terms'), + onTap: () => afLaunchUrlString('https://appflowy.com/terms'), ), if (kDebugMode) MobileSettingItem( diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/ai/ai_settings_group.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/ai/ai_settings_group.dart new file mode 100644 index 0000000000..b43ada6e42 --- /dev/null +++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/ai/ai_settings_group.dart @@ -0,0 +1,104 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart'; +import 'package:appflowy/mobile/presentation/setting/widgets/mobile_setting_group_widget.dart'; +import 'package:appflowy/mobile/presentation/setting/widgets/mobile_setting_item_widget.dart'; +import 'package:appflowy/mobile/presentation/widgets/flowy_option_tile.dart'; +import 'package:appflowy/workspace/application/settings/ai/settings_ai_bloc.dart'; +import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:go_router/go_router.dart'; + +class AiSettingsGroup extends StatelessWidget { + const AiSettingsGroup({ + super.key, + required this.userProfile, + required this.workspaceId, + }); + + final UserProfilePB userProfile; + final String workspaceId; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return BlocProvider( + create: (context) => SettingsAIBloc( + userProfile, + workspaceId, + )..add(const SettingsAIEvent.started()), + child: BlocBuilder( + builder: (context, state) { + return MobileSettingGroup( + groupTitle: LocaleKeys.settings_aiPage_title.tr(), + settingItemList: [ + MobileSettingItem( + name: LocaleKeys.settings_aiPage_keys_llmModelType.tr(), + trailing: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 200), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: FlowyText( + state.availableModels?.selectedModel.name ?? "", + color: theme.colorScheme.onSurface, + overflow: TextOverflow.ellipsis, + ), + ), + const Icon(Icons.chevron_right), + ], + ), + ), + onTap: () => _onLLMModelTypeTap(context, state), + ), + // enable AI search if needed + // MobileSettingItem( + // name: LocaleKeys.settings_aiPage_keys_enableAISearchTitle.tr(), + // trailing: const Icon( + // Icons.chevron_right, + // ), + // onTap: () => context.push(AppFlowyCloudPage.routeName), + // ), + ], + ); + }, + ), + ); + } + + void _onLLMModelTypeTap(BuildContext context, SettingsAIState state) { + final availableModels = state.availableModels; + showMobileBottomSheet( + context, + showHeader: true, + showDragHandle: true, + showDivider: false, + title: LocaleKeys.settings_aiPage_keys_llmModelType.tr(), + builder: (_) { + return Column( + children: (availableModels?.models ?? []) + .asMap() + .entries + .map( + (entry) => FlowyOptionTile.checkbox( + text: entry.value.name, + showTopBorder: entry.key == 0, + isSelected: + availableModels?.selectedModel.name == entry.value.name, + onTap: () { + context + .read() + .add(SettingsAIEvent.selectModel(entry.value)); + context.pop(); + }, + ), + ) + .toList(), + ); + }, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/appearance_setting_group.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/appearance_setting_group.dart index abb31817e5..47e356e6c1 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/appearance_setting_group.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/appearance_setting_group.dart @@ -19,7 +19,7 @@ class AppearanceSettingGroup extends StatelessWidget { settingItemList: const [ ThemeSetting(), FontSetting(), - TextScaleSetting(), + DisplaySizeSetting(), RTLSetting(), ], ); diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/rtl_setting.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/rtl_setting.dart index e61281c5c4..5b8035f004 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/rtl_setting.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/rtl_setting.dart @@ -1,6 +1,7 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/widgets/widgets.dart'; +import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart'; import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; @@ -17,15 +18,15 @@ class RTLSetting extends StatelessWidget { @override Widget build(BuildContext context) { final theme = Theme.of(context); - final layoutDirection = - context.watch().state.layoutDirection; + final textDirection = + context.watch().state.textDirection; return MobileSettingItem( name: LocaleKeys.settings_appearance_textDirection_label.tr(), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ FlowyText( - _textDirectionLabelText(layoutDirection), + _textDirectionLabelText(textDirection), color: theme.colorScheme.onSurface, ), const Icon(Icons.chevron_right), @@ -39,30 +40,33 @@ class RTLSetting extends StatelessWidget { showDivider: false, title: LocaleKeys.settings_appearance_textDirection_label.tr(), builder: (context) { - final layoutDirection = - context.watch().state.layoutDirection; return Column( children: [ FlowyOptionTile.checkbox( text: LocaleKeys.settings_appearance_textDirection_ltr.tr(), - isSelected: layoutDirection == LayoutDirection.ltrLayout, - onTap: () { - context - .read() - .setLayoutDirection(LayoutDirection.ltrLayout); - Navigator.pop(context); - }, + isSelected: textDirection == AppFlowyTextDirection.ltr, + onTap: () => applyTextDirectionAndPop( + context, + AppFlowyTextDirection.ltr, + ), ), FlowyOptionTile.checkbox( showTopBorder: false, text: LocaleKeys.settings_appearance_textDirection_rtl.tr(), - isSelected: layoutDirection == LayoutDirection.rtlLayout, - onTap: () { - context - .read() - .setLayoutDirection(LayoutDirection.rtlLayout); - Navigator.pop(context); - }, + isSelected: textDirection == AppFlowyTextDirection.rtl, + onTap: () => applyTextDirectionAndPop( + context, + AppFlowyTextDirection.rtl, + ), + ), + FlowyOptionTile.checkbox( + showTopBorder: false, + text: LocaleKeys.settings_appearance_textDirection_auto.tr(), + isSelected: textDirection == AppFlowyTextDirection.auto, + onTap: () => applyTextDirectionAndPop( + context, + AppFlowyTextDirection.auto, + ), ), ], ); @@ -72,13 +76,25 @@ class RTLSetting extends StatelessWidget { ); } - String _textDirectionLabelText(LayoutDirection? textDirection) { + String _textDirectionLabelText(AppFlowyTextDirection textDirection) { switch (textDirection) { - case LayoutDirection.rtlLayout: + case AppFlowyTextDirection.auto: + return LocaleKeys.settings_appearance_textDirection_auto.tr(); + case AppFlowyTextDirection.rtl: return LocaleKeys.settings_appearance_textDirection_rtl.tr(); - case LayoutDirection.ltrLayout: - default: + case AppFlowyTextDirection.ltr: return LocaleKeys.settings_appearance_textDirection_ltr.tr(); } } + + void applyTextDirectionAndPop( + BuildContext context, + AppFlowyTextDirection textDirection, + ) { + context.read().setTextDirection(textDirection); + context + .read() + .syncDefaultTextDirection(textDirection.name); + Navigator.pop(context); + } } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/text_scale_setting.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/text_scale_setting.dart index 3bdb836a71..7c89185e79 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/text_scale_setting.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/appearance/text_scale_setting.dart @@ -1,39 +1,55 @@ -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; -import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; +import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/startup/tasks/app_window_size_manager.dart'; +import 'package:appflowy/workspace/presentation/home/hotkeys.dart'; import 'package:appflowy/workspace/presentation/widgets/more_view_actions/widgets/font_size_stepper.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:scaled_app/scaled_app.dart'; import '../setting.dart'; const int _divisions = 4; +const double _minMobileScaleFactor = 0.8; +const double _maxMobileScaleFactor = 1.2; -class TextScaleSetting extends StatelessWidget { - const TextScaleSetting({ +class DisplaySizeSetting extends StatefulWidget { + const DisplaySizeSetting({ super.key, }); + @override + State createState() => _DisplaySizeSettingState(); +} + +class _DisplaySizeSettingState extends State { + double scaleFactor = 1.0; + final windowSizeManager = WindowSizeManager(); + + @override + void initState() { + super.initState(); + windowSizeManager.getScaleFactor().then((v) { + if (v != scaleFactor && mounted) { + setState(() { + scaleFactor = v; + }); + } + }); + } + @override Widget build(BuildContext context) { final theme = Theme.of(context); - final textScaleFactor = - context.watch().state.textScaleFactor; return MobileSettingItem( - name: LocaleKeys.settings_appearance_fontScaleFactor.tr(), + name: LocaleKeys.settings_appearance_displaySize.tr(), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ FlowyText( - // map the text scale factor to the 0-1 - // 0.8 - 0.0 - // 0.9 - 0.5 - // 1.0 - 1.0 - ((_divisions + 1) * textScaleFactor - _divisions) - .toStringAsFixed(2), + scaleFactor.toStringAsFixed(2), color: theme.colorScheme.onSurface, ), const Icon(Icons.chevron_right), @@ -45,17 +61,15 @@ class TextScaleSetting extends StatelessWidget { showHeader: true, showDragHandle: true, showDivider: false, - title: LocaleKeys.settings_appearance_fontScaleFactor.tr(), + title: LocaleKeys.settings_appearance_displaySize.tr(), builder: (context) { return FontSizeStepper( - value: textScaleFactor, - minimumValue: 0.8, - maximumValue: 1.0, + value: scaleFactor, + minimumValue: _minMobileScaleFactor, + maximumValue: _maxMobileScaleFactor, divisions: _divisions, - onChanged: (newTextScaleFactor) { - context - .read() - .setTextScaleFactor(newTextScaleFactor); + onChanged: (newScaleFactor) async { + await _setScale(newScaleFactor); }, ); }, @@ -63,4 +77,22 @@ class TextScaleSetting extends StatelessWidget { }, ); } + + Future _setScale(double value) async { + if (FlowyRunner.currentMode == IntegrationMode.integrationTest) { + // The integration test will fail if we check the scale factor in the test. + // #0 ScaledWidgetsFlutterBinding.Eval () + // #1 ScaledWidgetsFlutterBinding.instance (package:scaled_app/scaled_app.dart:66:62) + // ignore: invalid_use_of_visible_for_testing_member + appflowyScaleFactor = value; + } else { + ScaledWidgetsFlutterBinding.instance.scaleFactor = (_) => value; + } + if (mounted) { + setState(() { + scaleFactor = value; + }); + } + await windowSizeManager.setScaleFactor(value); + } } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/cloud/appflowy_cloud_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/cloud/appflowy_cloud_page.dart index 24c50f7ae6..02d620e559 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/cloud/appflowy_cloud_page.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/cloud/appflowy_cloud_page.dart @@ -1,6 +1,7 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/base/app_bar/app_bar.dart'; import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/user/application/auth/auth_service.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/setting_cloud.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -18,6 +19,7 @@ class AppFlowyCloudPage extends StatelessWidget { ), body: SettingCloud( restartAppFlowy: () async { + await getIt().signOut(); await runAppFlowy(); }, ), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/personal_info/personal_info_setting_group.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/personal_info/personal_info_setting_group.dart index cfdf3defb0..28ebdb750e 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/personal_info/personal_info_setting_group.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/personal_info/personal_info_setting_group.dart @@ -1,5 +1,3 @@ -import 'package:flutter/material.dart'; - import 'package:appflowy/env/cloud_env.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; @@ -7,10 +5,10 @@ import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/workspace/application/user/prelude.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../widgets/widgets.dart'; - import 'personal_info.dart'; class PersonalInfoSettingGroup extends StatelessWidget { @@ -32,7 +30,7 @@ class PersonalInfoSettingGroup extends StatelessWidget { selector: (state) => state.userProfile.name, builder: (context, userName) { return MobileSettingGroup( - groupTitle: LocaleKeys.settings_mobile_personalInfo.tr(), + groupTitle: LocaleKeys.settings_accountPage_title.tr(), settingItemList: [ MobileSettingItem( name: userName, @@ -60,7 +58,7 @@ class PersonalInfoSettingGroup extends StatelessWidget { userName: userName, onSubmitted: (value) => context .read() - .add(SettingsUserEvent.updateUserName(value)), + .add(SettingsUserEvent.updateUserName(name: value)), ); }, ); diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/self_host/self_host_bottom_sheet.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/self_host/self_host_bottom_sheet.dart index ebc58290b9..dd19c2489d 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/self_host/self_host_bottom_sheet.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/self_host/self_host_bottom_sheet.dart @@ -6,13 +6,20 @@ import 'package:appflowy_backend/log.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +enum SelfHostUrlBottomSheetType { + shareDomain, + cloudURL, +} + class SelfHostUrlBottomSheet extends StatefulWidget { const SelfHostUrlBottomSheet({ super.key, required this.url, + required this.type, }); final String url; + final SelfHostUrlBottomSheetType type; @override State createState() => _SelfHostUrlBottomSheetState(); @@ -46,8 +53,10 @@ class _SelfHostUrlBottomSheetState extends State { controller: _textFieldController, keyboardType: TextInputType.text, validator: (value) { - if (value == null || value.isEmpty) { - return LocaleKeys.settings_mobile_usernameEmptyError.tr(); + if (value == null || + value.isEmpty || + validateUrl(value).isFailure) { + return LocaleKeys.settings_menu_invalidCloudURLScheme.tr(); } return null; }, @@ -74,7 +83,12 @@ class _SelfHostUrlBottomSheetState extends State { if (value.isNotEmpty) { validateUrl(value).fold( (url) async { - await useSelfHostedAppFlowyCloudWithURL(url); + switch (widget.type) { + case SelfHostUrlBottomSheetType.shareDomain: + await useBaseWebDomain(url); + case SelfHostUrlBottomSheetType.cloudURL: + await useSelfHostedAppFlowyCloudWithURL(url); + } await runAppFlowy(); }, (err) => Log.error(err), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/self_host_setting_group.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/self_host_setting_group.dart index 095214d6ef..da2e0c773e 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/self_host_setting_group.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/self_host_setting_group.dart @@ -3,6 +3,7 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/setting/self_host/self_host_bottom_sheet.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flutter/material.dart'; import 'setting.dart'; @@ -17,44 +18,106 @@ class SelfHostSettingGroup extends StatefulWidget { } class _SelfHostSettingGroupState extends State { - final future = getAppFlowyCloudUrl(); + final future = Future.wait([ + getAppFlowyCloudUrl(), + getAppFlowyShareDomain(), + ]); @override Widget build(BuildContext context) { return FutureBuilder( future: future, builder: (context, snapshot) { - if (!snapshot.hasData) { + final data = snapshot.data; + if (!snapshot.hasData || data == null || data.length != 2) { return const SizedBox.shrink(); } - final url = snapshot.data ?? ''; + final url = data[0]; + final shareDomain = data[1]; return MobileSettingGroup( - groupTitle: LocaleKeys.settings_menu_cloudAppFlowySelfHost.tr(), + groupTitle: LocaleKeys.settings_menu_cloudAppFlowy.tr(), settingItemList: [ - MobileSettingItem( - name: url, - onTap: () { - showMobileBottomSheet( - context, - showHeader: true, - title: LocaleKeys.editor_urlHint.tr(), - showCloseButton: true, - showDivider: false, - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 8.0, - ), - builder: (_) { - return SelfHostUrlBottomSheet( - url: url, - ); - }, - ); - }, - ), + _buildSelfHostField(url), + _buildShareDomainField(shareDomain), ], ); }, ); } + + Widget _buildSelfHostField(String url) { + return MobileSettingItem( + title: Padding( + padding: const EdgeInsets.only(bottom: 4.0), + child: FlowyText( + LocaleKeys.settings_menu_cloudURL.tr(), + fontSize: 12.0, + color: Theme.of(context).hintColor, + ), + ), + subtitle: FlowyText( + url, + ), + trailing: const Icon( + Icons.chevron_right, + ), + onTap: () { + showMobileBottomSheet( + context, + showHeader: true, + title: LocaleKeys.editor_urlHint.tr(), + showCloseButton: true, + showDivider: false, + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 8.0, + ), + builder: (_) { + return SelfHostUrlBottomSheet( + url: url, + type: SelfHostUrlBottomSheetType.cloudURL, + ); + }, + ); + }, + ); + } + + Widget _buildShareDomainField(String shareDomain) { + return MobileSettingItem( + title: Padding( + padding: const EdgeInsets.only(bottom: 4.0), + child: FlowyText( + LocaleKeys.settings_menu_webURL.tr(), + fontSize: 12.0, + color: Theme.of(context).hintColor, + ), + ), + subtitle: FlowyText( + shareDomain, + ), + trailing: const Icon( + Icons.chevron_right, + ), + onTap: () { + showMobileBottomSheet( + context, + showHeader: true, + title: LocaleKeys.editor_urlHint.tr(), + showCloseButton: true, + showDivider: false, + padding: const EdgeInsets.symmetric( + horizontal: 16.0, + vertical: 8.0, + ), + builder: (_) { + return SelfHostUrlBottomSheet( + url: shareDomain, + type: SelfHostUrlBottomSheetType.shareDomain, + ); + }, + ); + }, + ); + } } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/support_setting_group.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/support_setting_group.dart index 584b867736..e5e4efef77 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/support_setting_group.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/support_setting_group.dart @@ -81,7 +81,6 @@ class SupportSettingGroup extends StatelessWidget { ); if (context.mounted) { showToastNotification( - context, message: LocaleKeys.settings_files_clearCacheSuccess.tr(), ); } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/user_session_setting_group.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/user_session_setting_group.dart index b3b7cb71c5..5ca5525099 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/user_session_setting_group.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/user_session_setting_group.dart @@ -40,7 +40,7 @@ class UserSessionSettingGroup extends StatelessWidget { // delete account button // only show the delete account button in cloud mode - if (userProfile.authenticator == AuthenticatorPB.AppFlowyCloud) ...[ + if (userProfile.workspaceAuthType == AuthTypePB.Server) ...[ const VSpace(16.0), MobileLogoutButton( text: LocaleKeys.button_deleteAccount.tr(), @@ -63,8 +63,15 @@ class UserSessionSettingGroup extends StatelessWidget { ); }, builder: (context, state) { - return const ThirdPartySignInButtons( - expanded: true, + return Column( + children: [ + const ContinueWithEmailAndPassword(), + const VSpace(12.0), + const ThirdPartySignInButtons( + expanded: true, + ), + const VSpace(16.0), + ], ); }, ), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/widgets/mobile_setting_item_widget.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/widgets/mobile_setting_item_widget.dart index 6dc45c1c40..82c86065ae 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/widgets/mobile_setting_item_widget.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/widgets/mobile_setting_item_widget.dart @@ -4,41 +4,29 @@ import 'package:flutter/material.dart'; class MobileSettingItem extends StatelessWidget { const MobileSettingItem({ super.key, - required this.name, + this.name, this.padding = const EdgeInsets.only(bottom: 4), this.trailing, this.leadingIcon, + this.title, this.subtitle, this.onTap, }); - final String name; + final String? name; final EdgeInsets padding; final Widget? trailing; final Widget? leadingIcon; final Widget? subtitle; final VoidCallback? onTap; + final Widget? title; @override Widget build(BuildContext context) { return Padding( padding: padding, child: ListTile( - title: Row( - children: [ - if (leadingIcon != null) ...[ - leadingIcon!, - const HSpace(8), - ], - Expanded( - child: FlowyText.medium( - name, - fontSize: 14.0, - overflow: TextOverflow.ellipsis, - ), - ), - ], - ), + title: title ?? _buildDefaultTitle(name), subtitle: subtitle, trailing: trailing, onTap: onTap, @@ -47,4 +35,22 @@ class MobileSettingItem extends StatelessWidget { ), ); } + + Widget _buildDefaultTitle(String? name) { + return Row( + children: [ + if (leadingIcon != null) ...[ + leadingIcon!, + const HSpace(8), + ], + Expanded( + child: FlowyText.medium( + name ?? '', + fontSize: 14.0, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ); + } } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/workspace/invite_members_screen.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/workspace/invite_members_screen.dart index 2e805c5c5a..18bce0588b 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/workspace/invite_members_screen.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/workspace/invite_members_screen.dart @@ -197,11 +197,10 @@ class _InviteMemberPageState extends State<_InviteMemberPage> { final keyboardHeight = MediaQuery.of(context).viewInsets.bottom; // only show the result dialog when the action is WorkspaceMemberActionType.add - if (actionType == WorkspaceMemberActionType.add) { + if (actionType == WorkspaceMemberActionType.addByEmail) { result.fold( (s) { showToastNotification( - context, message: LocaleKeys.settings_appearance_members_addMemberSuccess.tr(), bottomPadding: keyboardHeight, @@ -218,18 +217,16 @@ class _InviteMemberPageState extends State<_InviteMemberPage> { exceededLimit = f.code == ErrorCode.WorkspaceMemberLimitExceeded; }); showToastNotification( - context, type: ToastificationType.error, bottomPadding: keyboardHeight, message: message, ); }, ); - } else if (actionType == WorkspaceMemberActionType.invite) { + } else if (actionType == WorkspaceMemberActionType.inviteByEmail) { result.fold( (s) { showToastNotification( - context, message: LocaleKeys.settings_appearance_members_inviteMemberSuccess.tr(), bottomPadding: keyboardHeight, @@ -247,18 +244,16 @@ class _InviteMemberPageState extends State<_InviteMemberPage> { exceededLimit = f.code == ErrorCode.WorkspaceMemberLimitExceeded; }); showToastNotification( - context, type: ToastificationType.error, message: message, bottomPadding: keyboardHeight, ); }, ); - } else if (actionType == WorkspaceMemberActionType.remove) { + } else if (actionType == WorkspaceMemberActionType.removeByEmail) { result.fold( (s) { showToastNotification( - context, message: LocaleKeys .settings_appearance_members_removeFromWorkspaceSuccess .tr(), @@ -267,7 +262,6 @@ class _InviteMemberPageState extends State<_InviteMemberPage> { }, (f) { showToastNotification( - context, type: ToastificationType.error, message: LocaleKeys .settings_appearance_members_removeFromWorkspaceFailed @@ -282,15 +276,15 @@ class _InviteMemberPageState extends State<_InviteMemberPage> { void _inviteMember(BuildContext context) { final email = emailController.text; if (!isEmail(email)) { - return showToastNotification( - context, + showToastNotification( type: ToastificationType.error, message: LocaleKeys.settings_appearance_members_emailInvalidError.tr(), ); + return; } context .read() - .add(WorkspaceMemberEvent.inviteWorkspaceMember(email)); + .add(WorkspaceMemberEvent.inviteWorkspaceMemberByEmail(email)); // clear the email field after inviting emailController.clear(); } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/workspace/member_list.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/workspace/member_list.dart index 501fd18ef7..b2805d5857 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/workspace/member_list.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/workspace/member_list.dart @@ -178,7 +178,7 @@ class _MemberItem extends StatelessWidget { showBottomBorder: false, onTap: () { workspaceMemberBloc.add( - WorkspaceMemberEvent.removeWorkspaceMember( + WorkspaceMemberEvent.removeWorkspaceMemberByEmail( member.email, ), ); diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart b/frontend/appflowy_flutter/lib/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart index 1c1121d7f5..191deb1e9f 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart @@ -12,6 +12,7 @@ class MobileQuickActionButton extends StatelessWidget { this.iconColor, this.iconSize, this.enable = true, + this.rightIconBuilder, }); final VoidCallback onTap; @@ -21,36 +22,37 @@ class MobileQuickActionButton extends StatelessWidget { final Color? iconColor; final Size? iconSize; final bool enable; + final WidgetBuilder? rightIconBuilder; @override Widget build(BuildContext context) { final iconSize = this.iconSize ?? const Size.square(18); - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), + return Opacity( + opacity: enable ? 1.0 : 0.5, child: InkWell( onTap: enable ? onTap : null, - borderRadius: BorderRadius.circular(12), overlayColor: enable ? null : const WidgetStatePropertyAll(Colors.transparent), splashColor: Colors.transparent, child: Container( - height: 44, - padding: const EdgeInsets.symmetric(horizontal: 12), + height: 52, + padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ FlowySvg( icon, size: iconSize, - color: enable ? iconColor : Theme.of(context).disabledColor, + color: iconColor, ), HSpace(30 - iconSize.width), Expanded( child: FlowyText.regular( text, fontSize: 16, - color: enable ? textColor : Theme.of(context).disabledColor, + color: textColor, ), ), + if (rightIconBuilder != null) rightIconBuilder!(context), ], ), ), @@ -58,3 +60,12 @@ class MobileQuickActionButton extends StatelessWidget { ); } } + +class MobileQuickActionDivider extends StatelessWidget { + const MobileQuickActionDivider({super.key}); + + @override + Widget build(BuildContext context) { + return const Divider(height: 0.5, thickness: 0.5); + } +} diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/widgets/flowy_option_tile.dart b/frontend/appflowy_flutter/lib/mobile/presentation/widgets/flowy_option_tile.dart index 4f76003e23..f38c724a22 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/widgets/flowy_option_tile.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/widgets/flowy_option_tile.dart @@ -37,6 +37,7 @@ class FlowyOptionTile extends StatelessWidget { this.backgroundColor, this.fontFamily, this.height, + this.enable = true, }); factory FlowyOptionTile.text({ @@ -49,6 +50,7 @@ class FlowyOptionTile extends StatelessWidget { Widget? trailing, VoidCallback? onTap, double? height, + bool enable = true, }) { return FlowyOptionTile._( type: FlowyOptionTileType.text, @@ -61,6 +63,7 @@ class FlowyOptionTile extends StatelessWidget { leading: leftIcon, trailing: trailing, height: height, + enable: enable, ); } @@ -77,6 +80,7 @@ class FlowyOptionTile extends StatelessWidget { Widget? trailing, String? textFieldHintText, bool autofocus = false, + bool enable = true, }) { return FlowyOptionTile._( type: FlowyOptionTileType.textField, @@ -90,6 +94,7 @@ class FlowyOptionTile extends StatelessWidget { onTextChanged: onTextChanged, onTextSubmitted: onTextSubmitted, autofocus: autofocus, + enable: enable, ); } @@ -105,6 +110,7 @@ class FlowyOptionTile extends StatelessWidget { bool showBottomBorder = true, String? fontFamily, Color? backgroundColor, + bool enable = true, }) { return FlowyOptionTile._( key: key, @@ -119,6 +125,7 @@ class FlowyOptionTile extends StatelessWidget { showTopBorder: showTopBorder, showBottomBorder: showBottomBorder, leading: leftIcon, + enable: enable, trailing: isSelected ? const FlowySvg( FlowySvgs.m_blue_check_s, @@ -136,6 +143,7 @@ class FlowyOptionTile extends StatelessWidget { bool showTopBorder = true, bool showBottomBorder = true, Widget? leftIcon, + bool enable = true, }) { return FlowyOptionTile._( type: FlowyOptionTileType.toggle, @@ -146,6 +154,7 @@ class FlowyOptionTile extends StatelessWidget { showBottomBorder: showBottomBorder, leading: leftIcon, trailing: _Toggle(value: isSelected, onChanged: onValueChanged), + enable: enable, ); } @@ -181,11 +190,13 @@ class FlowyOptionTile extends StatelessWidget { final double? height; + final bool enable; + @override Widget build(BuildContext context) { final leadingWidget = _buildLeading(); - final child = FlowyOptionDecorateBox( + Widget child = FlowyOptionDecorateBox( color: backgroundColor, showTopBorder: showTopBorder, showBottomBorder: showBottomBorder, @@ -209,12 +220,21 @@ class FlowyOptionTile extends StatelessWidget { if (type == FlowyOptionTileType.checkbox || type == FlowyOptionTileType.toggle || type == FlowyOptionTileType.text) { - return GestureDetector( + child = GestureDetector( onTap: onTap, child: child, ); } + if (!enable) { + child = Opacity( + opacity: 0.5, + child: IgnorePointer( + child: child, + ), + ); + } + return child; } @@ -299,7 +319,7 @@ class _Toggle extends StatelessWidget { fit: BoxFit.fill, child: CupertinoSwitch( value: value, - activeColor: Theme.of(context).colorScheme.primary, + activeTrackColor: Theme.of(context).colorScheme.primary, onChanged: onChanged, ), ), diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/widgets/show_flowy_mobile_confirm_dialog.dart b/frontend/appflowy_flutter/lib/mobile/presentation/widgets/show_flowy_mobile_confirm_dialog.dart index 9df9d2e6fd..96c18f5d91 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/widgets/show_flowy_mobile_confirm_dialog.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/widgets/show_flowy_mobile_confirm_dialog.dart @@ -99,7 +99,7 @@ Future showFlowyCupertinoConfirmDialog({ }) { return showDialog( context: context ?? AppGlobals.context, - barrierColor: Colors.black.withOpacity(0.25), + barrierColor: Colors.black.withValues(alpha: 0.25), builder: (context) => CupertinoAlertDialog( title: FlowyText.medium( title, diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/ai_model_switch_listener.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/ai_model_switch_listener.dart new file mode 100644 index 0000000000..2cfc349bf8 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/ai_model_switch_listener.dart @@ -0,0 +1,53 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:appflowy/plugins/ai_chat/application/chat_notification.dart'; +import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-ai/notification.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-notification/subject.pb.dart'; +import 'package:appflowy_backend/rust_stream.dart'; +import 'package:appflowy_result/appflowy_result.dart'; + +typedef OnUpdateSelectedModel = void Function(AIModelPB model); + +class AIModelSwitchListener { + AIModelSwitchListener({required this.objectId}) { + _parser = ChatNotificationParser(id: objectId, callback: _callback); + _subscription = RustStreamReceiver.listen( + (observable) => _parser?.parse(observable), + ); + } + + final String objectId; + StreamSubscription? _subscription; + ChatNotificationParser? _parser; + + void start({ + OnUpdateSelectedModel? onUpdateSelectedModel, + }) { + this.onUpdateSelectedModel = onUpdateSelectedModel; + } + + OnUpdateSelectedModel? onUpdateSelectedModel; + + void _callback( + ChatNotification ty, + FlowyResult result, + ) { + result.map((r) { + switch (ty) { + case ChatNotification.DidUpdateSelectedModel: + onUpdateSelectedModel?.call(AIModelPB.fromBuffer(r)); + break; + default: + break; + } + }); + } + + Future stop() async { + await _subscription?.cancel(); + _subscription = null; + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/ai_prompt_input_bloc.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/ai_prompt_input_bloc.dart deleted file mode 100644 index fdbcbb4cc0..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/ai_prompt_input_bloc.dart +++ /dev/null @@ -1,189 +0,0 @@ -import 'dart:async'; - -import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart'; -import 'package:appflowy/workspace/application/settings/ai/local_llm_listener.dart'; -import 'package:appflowy_backend/dispatch/dispatch.dart'; -import 'package:appflowy_backend/log.dart'; -import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart'; -import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; -import 'package:appflowy_result/appflowy_result.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'ai_prompt_input_bloc.freezed.dart'; - -class AIPromptInputBloc extends Bloc { - AIPromptInputBloc() - : _listener = LocalLLMListener(), - super(AIPromptInputState.initial()) { - _dispatch(); - _startListening(); - _init(); - } - - final LocalLLMListener _listener; - - @override - Future close() async { - await _listener.stop(); - return super.close(); - } - - void _dispatch() { - on( - (event, emit) { - event.when( - updateChatState: (LocalAIChatPB chatState) { - // Only user enable chat with file and the plugin is already running - final supportChatWithFile = chatState.fileEnabled && - chatState.pluginState.state == RunningStatePB.Running; - - final aiType = chatState.pluginState.state == RunningStatePB.Running - ? AIType.localAI - : AIType.appflowyAI; - emit( - state.copyWith( - aiType: aiType, - supportChatWithFile: supportChatWithFile, - chatState: chatState, - ), - ); - }, - updatePluginState: (LocalAIPluginStatePB chatState) { - final fileEnabled = state.chatState?.fileEnabled ?? false; - final supportChatWithFile = - fileEnabled && chatState.state == RunningStatePB.Running; - - final aiType = chatState.state == RunningStatePB.Running - ? AIType.localAI - : AIType.appflowyAI; - - emit( - state.copyWith( - supportChatWithFile: supportChatWithFile, - aiType: aiType, - ), - ); - }, - attachFile: (filePath, fileName) { - final newFile = ChatFile.fromFilePath(filePath); - if (newFile != null) { - emit( - state.copyWith( - attachedFiles: [...state.attachedFiles, newFile], - ), - ); - } - }, - removeFile: (file) { - final files = [...state.attachedFiles]; - files.remove(file); - emit( - state.copyWith( - attachedFiles: files, - ), - ); - }, - updateMentionedViews: (views) { - emit( - state.copyWith( - mentionedPages: views, - ), - ); - }, - clearMetadata: () { - emit( - state.copyWith( - attachedFiles: [], - mentionedPages: [], - ), - ); - }, - ); - }, - ); - } - - void _startListening() { - _listener.start( - stateCallback: (pluginState) { - if (!isClosed) { - add(AIPromptInputEvent.updatePluginState(pluginState)); - } - }, - chatStateCallback: (chatState) { - if (!isClosed) { - add(AIPromptInputEvent.updateChatState(chatState)); - } - }, - ); - } - - void _init() { - AIEventGetLocalAIChatState().send().fold( - (chatState) { - if (!isClosed) { - add(AIPromptInputEvent.updateChatState(chatState)); - } - }, - Log.error, - ); - } - - Map consumeMetadata() { - final metadata = { - for (final file in state.attachedFiles) file.filePath: file, - for (final page in state.mentionedPages) page.id: page, - }; - - if (metadata.isNotEmpty && !isClosed) { - add(const AIPromptInputEvent.clearMetadata()); - } - - return metadata; - } -} - -@freezed -class AIPromptInputEvent with _$AIPromptInputEvent { - const factory AIPromptInputEvent.updateChatState( - LocalAIChatPB chatState, - ) = _UpdateChatState; - const factory AIPromptInputEvent.updatePluginState( - LocalAIPluginStatePB chatState, - ) = _UpdatePluginState; - const factory AIPromptInputEvent.attachFile( - String filePath, - String fileName, - ) = _AttachFile; - const factory AIPromptInputEvent.removeFile(ChatFile file) = _RemoveFile; - const factory AIPromptInputEvent.updateMentionedViews(List views) = - _UpdateMentionedViews; - const factory AIPromptInputEvent.clearMetadata() = _ClearMetadata; -} - -@freezed -class AIPromptInputState with _$AIPromptInputState { - const factory AIPromptInputState({ - required AIType aiType, - required bool supportChatWithFile, - required LocalAIChatPB? chatState, - required List attachedFiles, - required List mentionedPages, - }) = _AIPromptInputState; - - factory AIPromptInputState.initial() => const AIPromptInputState( - aiType: AIType.appflowyAI, - supportChatWithFile: false, - chatState: null, - attachedFiles: [], - mentionedPages: [], - ); -} - -enum AIType { - appflowyAI, - localAI; - - bool get isLocalAI => this == localAI; -} diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_ai_message_bloc.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_ai_message_bloc.dart index aeccb1a3ca..47c1668a2c 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_ai_message_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_ai_message_bloc.dart @@ -23,11 +23,126 @@ class ChatAIMessageBloc extends Bloc { parseMetadata(refSourceJsonString), ), ) { - _dispatch(); + _registerEventHandlers(); + _initializeStreamListener(); + _checkInitialStreamState(); + } + final String chatId; + final Int64? questionId; + + void _registerEventHandlers() { + on<_UpdateText>((event, emit) { + emit( + state.copyWith( + text: event.text, + messageState: const MessageState.ready(), + ), + ); + }); + + on<_ReceiveError>((event, emit) { + emit(state.copyWith(messageState: MessageState.onError(event.error))); + }); + + on<_Retry>((event, emit) async { + if (questionId == null) { + Log.error("Question id is not valid: $questionId"); + return; + } + emit(state.copyWith(messageState: const MessageState.loading())); + final payload = ChatMessageIdPB( + chatId: chatId, + messageId: questionId, + ); + final result = await AIEventGetAnswerForQuestion(payload).send(); + if (!isClosed) { + result.fold( + (answer) => add(ChatAIMessageEvent.retryResult(answer.content)), + (err) { + Log.error("Failed to get answer: $err"); + add(ChatAIMessageEvent.receiveError(err.toString())); + }, + ); + } + }); + + on<_RetryResult>((event, emit) { + emit( + state.copyWith( + text: event.text, + messageState: const MessageState.ready(), + ), + ); + }); + + on<_OnAIResponseLimit>((event, emit) { + emit( + state.copyWith( + messageState: const MessageState.onAIResponseLimit(), + ), + ); + }); + + on<_OnAIImageResponseLimit>((event, emit) { + emit( + state.copyWith( + messageState: const MessageState.onAIImageResponseLimit(), + ), + ); + }); + + on<_OnAIMaxRquired>((event, emit) { + emit( + state.copyWith( + messageState: MessageState.onAIMaxRequired(event.message), + ), + ); + }); + + on<_OnLocalAIInitializing>((event, emit) { + emit( + state.copyWith( + messageState: const MessageState.onInitializingLocalAI(), + ), + ); + }); + + on<_ReceiveMetadata>((event, emit) { + Log.debug("AI Steps: ${event.metadata.progress?.step}"); + emit( + state.copyWith( + sources: event.metadata.sources, + progress: event.metadata.progress, + ), + ); + }); + } + + void _initializeStreamListener() { if (state.stream != null) { - _startListening(); + state.stream!.listen( + onData: (text) => _safeAdd(ChatAIMessageEvent.updateText(text)), + onError: (error) => + _safeAdd(ChatAIMessageEvent.receiveError(error.toString())), + onAIResponseLimit: () => + _safeAdd(const ChatAIMessageEvent.onAIResponseLimit()), + onAIImageResponseLimit: () => + _safeAdd(const ChatAIMessageEvent.onAIImageResponseLimit()), + onMetadata: (metadata) => + _safeAdd(ChatAIMessageEvent.receiveMetadata(metadata)), + onAIMaxRequired: (message) { + Log.info(message); + _safeAdd(ChatAIMessageEvent.onAIMaxRequired(message)); + }, + onLocalAIInitializing: () => + _safeAdd(const ChatAIMessageEvent.onLocalAIInitializing()), + ); + } + } + void _checkInitialStreamState() { + if (state.stream != null) { if (state.stream!.aiLimitReached) { add(const ChatAIMessageEvent.onAIResponseLimit()); } else if (state.stream!.error != null) { @@ -36,105 +151,10 @@ class ChatAIMessageBloc extends Bloc { } } - final String chatId; - final Int64? questionId; - - void _dispatch() { - on( - (event, emit) { - event.when( - updateText: (newText) { - emit( - state.copyWith( - text: newText, - messageState: const MessageState.ready(), - ), - ); - }, - receiveError: (error) { - emit(state.copyWith(messageState: MessageState.onError(error))); - }, - retry: () { - if (questionId is! Int64) { - Log.error("Question id is not Int64: $questionId"); - return; - } - emit( - state.copyWith( - messageState: const MessageState.loading(), - ), - ); - - final payload = ChatMessageIdPB( - chatId: chatId, - messageId: questionId, - ); - AIEventGetAnswerForQuestion(payload).send().then((result) { - if (!isClosed) { - result.fold( - (answer) { - add(ChatAIMessageEvent.retryResult(answer.content)); - }, - (err) { - Log.error("Failed to get answer: $err"); - add(ChatAIMessageEvent.receiveError(err.toString())); - }, - ); - } - }); - }, - retryResult: (String text) { - emit( - state.copyWith( - text: text, - messageState: const MessageState.ready(), - ), - ); - }, - onAIResponseLimit: () { - emit( - state.copyWith( - messageState: const MessageState.onAIResponseLimit(), - ), - ); - }, - receiveMetadata: (metadata) { - Log.debug("AI Steps: ${metadata.progress?.step}"); - emit( - state.copyWith( - sources: metadata.sources, - progress: metadata.progress, - ), - ); - }, - ); - }, - ); - } - - void _startListening() { - state.stream!.listen( - onData: (text) { - if (!isClosed) { - add(ChatAIMessageEvent.updateText(text)); - } - }, - onError: (error) { - if (!isClosed) { - add(ChatAIMessageEvent.receiveError(error.toString())); - } - }, - onAIResponseLimit: () { - if (!isClosed) { - add(const ChatAIMessageEvent.onAIResponseLimit()); - } - }, - onMetadata: (metadata) { - if (!isClosed) { - add(ChatAIMessageEvent.receiveMetadata(metadata)); - } - }, - ); + void _safeAdd(ChatAIMessageEvent event) { + if (!isClosed) { + add(event); + } } } @@ -145,6 +165,12 @@ class ChatAIMessageEvent with _$ChatAIMessageEvent { const factory ChatAIMessageEvent.retry() = _Retry; const factory ChatAIMessageEvent.retryResult(String text) = _RetryResult; const factory ChatAIMessageEvent.onAIResponseLimit() = _OnAIResponseLimit; + const factory ChatAIMessageEvent.onAIImageResponseLimit() = + _OnAIImageResponseLimit; + const factory ChatAIMessageEvent.onAIMaxRequired(String message) = + _OnAIMaxRquired; + const factory ChatAIMessageEvent.onLocalAIInitializing() = + _OnLocalAIInitializing; const factory ChatAIMessageEvent.receiveMetadata( MetadataCollection metadata, ) = _ReceiveMetadata; @@ -178,6 +204,9 @@ class ChatAIMessageState with _$ChatAIMessageState { class MessageState with _$MessageState { const factory MessageState.onError(String error) = _Error; const factory MessageState.onAIResponseLimit() = _AIResponseLimit; + const factory MessageState.onAIImageResponseLimit() = _AIImageResponseLimit; + const factory MessageState.onAIMaxRequired(String message) = _AIMaxRequired; + const factory MessageState.onInitializingLocalAI() = _LocalAIInitializing; const factory MessageState.ready() = _Ready; const factory MessageState.loading() = _Loading; } diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_bloc.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_bloc.dart index c18389e262..602b46f97a 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_bloc.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:collection'; +import 'package:appflowy/ai/ai.dart'; import 'package:appflowy/util/int64_extension.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart'; import 'package:appflowy_backend/log.dart'; @@ -10,6 +11,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_result/appflowy_result.dart'; import 'package:collection/collection.dart'; import 'package:fixnum/fixnum.dart'; +import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_chat_core/flutter_chat_core.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -28,6 +30,7 @@ class ChatBloc extends Bloc { required this.userId, }) : chatController = InMemoryChatController(), listener = ChatMessageListener(chatId: chatId), + selectedSourcesNotifier = ValueNotifier([]), super(ChatState.initial()) { _startListening(); _dispatch(); @@ -38,7 +41,7 @@ class ChatBloc extends Bloc { final String chatId; final String userId; final ChatMessageListener listener; - + final ValueNotifier> selectedSourcesNotifier; final ChatController chatController; /// The last streaming message id @@ -59,7 +62,8 @@ class ChatBloc extends Bloc { bool isLoadingPreviousMessages = false; bool hasMorePreviousMessages = true; AnswerStream? answerStream; - int numSendMessage = 0; + bool isFetchingRelatedQuestions = false; + bool shouldFetchRelatedQuestions = false; @override Future close() async { @@ -67,7 +71,7 @@ class ChatBloc extends Bloc { await listener.stop(); final request = ViewIdPB(value: chatId); unawaited(FolderEventCloseView(request).send()); - + selectedSourcesNotifier.dispose(); return super.close(); } @@ -165,14 +169,18 @@ class ChatBloc extends Bloc { }, sendMessage: ( String message, + PredefinedFormat? format, Map? metadata, ) { - numSendMessage += 1; - + _clearErrorMessages(emit); _clearRelatedQuestions(); - _startStreamingMessage(message, metadata); + _startStreamingMessage(message, format, metadata); lastSentMessage = null; + isFetchingRelatedQuestions = false; + shouldFetchRelatedQuestions = + format == null || format.imageFormat.hasText; + emit( state.copyWith( promptResponseState: PromptResponseState.sendingQuestion, @@ -231,11 +239,14 @@ class ChatBloc extends Bloc { ), ); }, - regenerateAnswer: (id) { + regenerateAnswer: (id, format, model) { _clearRelatedQuestions(); - _regenerateAnswer(id); + _regenerateAnswer(id, format, model); lastSentMessage = null; + isFetchingRelatedQuestions = false; + shouldFetchRelatedQuestions = false; + emit( state.copyWith( promptResponseState: PromptResponseState.sendingQuestion, @@ -243,12 +254,10 @@ class ChatBloc extends Bloc { ); }, didReceiveChatSettings: (settings) { - emit( - state.copyWith(selectedSourceIds: settings.ragIds), - ); + selectedSourcesNotifier.value = settings.ragIds; }, updateSelectedSources: (selectedSourcesIds) async { - emit(state.copyWith(selectedSourceIds: selectedSourcesIds)); + selectedSourcesNotifier.value = [...selectedSourcesIds]; final payload = UpdateChatSettingsPB( chatId: ChatId(value: chatId), @@ -258,6 +267,9 @@ class ChatBloc extends Bloc { .send() .onFailure(Log.error); }, + deleteMessage: (mesesage) async { + await chatController.remove(mesesage); + }, ); }, ); @@ -319,7 +331,9 @@ class ChatBloc extends Bloc { // The answer stream will bet set to null after the streaming has // finished, got cancelled, or errored. In this case, don't retrieve // related questions. - if (answerStream == null || lastSentMessage == null) { + if (answerStream == null || + lastSentMessage == null || + !shouldFetchRelatedQuestions) { return; } @@ -328,17 +342,19 @@ class ChatBloc extends Bloc { messageId: lastSentMessage!.messageId, ); - // when previous numSendMessage is not equal to current numSendMessage, it means that the user - // has sent a new message. So we don't need to get related questions. - final preNumSendMessage = numSendMessage; + isFetchingRelatedQuestions = true; await AIEventGetRelatedQuestion(payload).send().fold( (list) { - if (!isClosed && preNumSendMessage == numSendMessage) { + // while fetching related questions, the user might enter a new + // question or regenerate a previous response. In such cases, don't + // display the relatedQuestions + if (!isClosed && isFetchingRelatedQuestions) { add( ChatEvent.didReceiveRelatedQuestions( list.items.map((e) => e.content).toList(), ), ); + isFetchingRelatedQuestions = false; } }, (err) => Log.error("Failed to get related questions: $err"), @@ -398,6 +414,7 @@ class ChatBloc extends Bloc { Future _startStreamingMessage( String message, + PredefinedFormat? format, Map? metadata, ) async { await answerStream?.dispose(); @@ -418,8 +435,11 @@ class ChatBloc extends Bloc { messageType: ChatMessageTypePB.User, questionStreamPort: Int64(questionStream.nativePort), answerStreamPort: Int64(answerStream!.nativePort), - metadata: await metadataPBFromMetadata(metadata), + //metadata: await metadataPBFromMetadata(metadata), ); + if (format != null) { + payload.format = format.toPB(); + } // stream the question to the server await AIEventStreamMessage(payload).send().fold( @@ -460,7 +480,11 @@ class ChatBloc extends Bloc { ); } - void _regenerateAnswer(String answerMessageIdString) async { + void _regenerateAnswer( + String answerMessageIdString, + PredefinedFormat? format, + AIModelPB? model, + ) async { final id = temporaryMessageIDMap.entries .firstWhereOrNull((e) => e.value == answerMessageIdString) ?.key ?? @@ -479,6 +503,12 @@ class ChatBloc extends Bloc { answerMessageId: answerMessageId, answerStreamPort: Int64(answerStream!.nativePort), ); + if (format != null) { + payload.format = format.toPB(); + } + if (model != null) { + payload.model = model; + } await AIEventRegenerateResponse(payload).send().fold( (success) { @@ -558,6 +588,21 @@ class ChatBloc extends Bloc { ); } + void _clearErrorMessages(Emitter emit) { + final errorMessages = chatController.messages + .where( + (message) => + onetimeMessageTypeFromMeta(message.metadata) == + OnetimeShotType.error, + ) + .toList(); + + for (final message in errorMessages) { + chatController.remove(message); + } + emit(state.copyWith(clearErrorMessages: !state.clearErrorMessages)); + } + void _clearRelatedQuestions() { final relatedQuestionMessages = chatController.messages .where( @@ -586,13 +631,18 @@ class ChatEvent with _$ChatEvent { // send message const factory ChatEvent.sendMessage({ required String message, + PredefinedFormat? format, Map? metadata, }) = _SendMessage; const factory ChatEvent.finishSending() = _FinishSendMessage; const factory ChatEvent.failedSending() = _FailSendMessage; // regenerate - const factory ChatEvent.regenerateAnswer(String id) = _RegenerateAnswer; + const factory ChatEvent.regenerateAnswer( + String id, + PredefinedFormat? format, + AIModelPB? model, + ) = _RegenerateAnswer; // streaming answer const factory ChatEvent.stopStream() = _StopStream; @@ -614,20 +664,22 @@ class ChatEvent with _$ChatEvent { const factory ChatEvent.didReceiveRelatedQuestions( List questions, ) = _DidReceiveRelatedQueston; + + const factory ChatEvent.deleteMessage(Message message) = _DeleteMessage; } @freezed class ChatState with _$ChatState { const factory ChatState({ - required List selectedSourceIds, required LoadChatMessageStatus loadingState, required PromptResponseState promptResponseState, + required bool clearErrorMessages, }) = _ChatState; factory ChatState.initial() => const ChatState( - selectedSourceIds: [], loadingState: LoadChatMessageStatus.loading, promptResponseState: PromptResponseState.ready, + clearErrorMessages: false, ); } diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_edit_document_service.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_edit_document_service.dart index e611b7de3e..630feb379b 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_edit_document_service.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_edit_document_service.dart @@ -14,6 +14,8 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_chat_core/flutter_chat_core.dart'; class ChatEditDocumentService { + const ChatEditDocumentService._(); + static Future saveMessagesToNewPage( String chatPageName, String parentViewId, @@ -29,7 +31,10 @@ class ChatEditDocumentService { return null; } - final document = customMarkdownToDocument(completeMessage); + final document = customMarkdownToDocument( + completeMessage, + tableWidth: 250.0, + ); final initialBytes = DocumentDataPBFromTo.fromDocument(document)?.writeToBuffer(); if (initialBytes == null) { @@ -45,12 +50,13 @@ class ChatEditDocumentService { ).toNullable(); } - static Future addMessageToPage( + static Future addMessagesToPage( String documentId, - TextMessage message, + List messages, ) async { - if (message.text.isEmpty) { - Log.error('Message is empty'); + // Convert messages to markdown and trim the last empty newline. + final completeMessage = messages.map((m) => m.text).join('\n').trimRight(); + if (completeMessage.isEmpty) { return; } @@ -69,7 +75,10 @@ class ChatEditDocumentService { return; } - final messageDocument = customMarkdownToDocument(message.text); + final messageDocument = customMarkdownToDocument( + completeMessage, + tableWidth: 250.0, + ); if (messageDocument.isEmpty) { Log.error('Failed to convert message to document'); return; diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_entity.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_entity.dart index 4494eca899..41e2a6946d 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_entity.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_entity.dart @@ -1,6 +1,6 @@ import 'dart:io'; -import 'package:appflowy_backend/protobuf/flowy-ai/entities.pbenum.dart'; +import 'package:appflowy_backend/protobuf/flowy-ai/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; import 'package:equatable/equatable.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_member_bloc.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_member_bloc.dart index d1d0898ccc..2547ff668e 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_member_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_member_bloc.dart @@ -12,13 +12,13 @@ class ChatMemberBloc extends Bloc { ChatMemberBloc() : super(const ChatMemberState()) { on( (event, emit) async { - event.when( + await event.when( receiveMemberInfo: (String id, WorkspaceMemberPB memberInfo) { final members = Map.from(state.members); members[id] = ChatMember(info: memberInfo); emit(state.copyWith(members: members)); }, - getMemberInfo: (String userId) { + getMemberInfo: (String userId) async { if (state.members.containsKey(userId)) { // Member info already exists. Debouncing refresh member info from backend would be better. return; @@ -27,19 +27,16 @@ class ChatMemberBloc extends Bloc { final payload = WorkspaceMemberIdPB( uid: Int64.parseInt(userId), ); - UserEventGetMemberInfo(payload).send().then((result) { - if (!isClosed) { - result.fold((member) { - add( - ChatMemberEvent.receiveMemberInfo( - userId, - member, - ), - ); - }, (err) { - Log.error("Error getting member info: $err"); - }); - } + + await UserEventGetMemberInfo(payload).send().then((result) { + result.fold( + (member) { + if (!isClosed) { + add(ChatMemberEvent.receiveMemberInfo(userId, member)); + } + }, + (err) => Log.error("Error getting member info: $err"), + ); }); }, ); diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_message_stream.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_message_stream.dart index fc63a7ac82..c22559f21b 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_message_stream.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_message_stream.dart @@ -2,45 +2,28 @@ import 'dart:async'; import 'dart:ffi'; import 'dart:isolate'; +import 'package:appflowy/ai/service/ai_entities.dart'; import 'package:appflowy/plugins/ai_chat/application/chat_message_service.dart'; +/// A stream that receives answer events from an isolate or external process. +/// It caches events that might occur before a listener is attached. class AnswerStream { AnswerStream() { _port.handler = _controller.add; _subscription = _controller.stream.listen( - (event) { - if (event.startsWith("data:")) { - _hasStarted = true; - final newText = event.substring(5); - _text += newText; - _onData?.call(_text); - } else if (event.startsWith("error:")) { - _error = event.substring(5); - _onError?.call(_error!); - } else if (event.startsWith("metadata:")) { - if (_onMetadata != null) { - final s = event.substring(9); - _onMetadata!(parseMetadata(s)); - } - } else if (event == "AI_RESPONSE_LIMIT") { - _aiLimitReached = true; - _onAIResponseLimit?.call(); - } - }, - onDone: () { - _onEnd?.call(); - }, - onError: (error) { - _onError?.call(error.toString()); - }, + _handleEvent, + onDone: _onDoneCallback, + onError: _handleError, ); } final RawReceivePort _port = RawReceivePort(); final StreamController _controller = StreamController.broadcast(); late StreamSubscription _subscription; + bool _hasStarted = false; bool _aiLimitReached = false; + bool _aiImageLimitReached = false; String? _error; String _text = ""; @@ -49,35 +32,112 @@ class AnswerStream { void Function()? _onStart; void Function()? _onEnd; void Function(String error)? _onError; + void Function()? _onLocalAIInitializing; void Function()? _onAIResponseLimit; - void Function(MetadataCollection metadataCollection)? _onMetadata; + void Function()? _onAIImageResponseLimit; + void Function(String message)? _onAIMaxRequired; + void Function(MetadataCollection metadata)? _onMetadata; + + // Caches for events that occur before listen() is called. + final List _pendingAIMaxRequiredEvents = []; + bool _pendingLocalAINotReady = false; int get nativePort => _port.sendPort.nativePort; bool get hasStarted => _hasStarted; bool get aiLimitReached => _aiLimitReached; + bool get aiImageLimitReached => _aiImageLimitReached; String? get error => _error; String get text => _text; + /// Releases the resources used by the AnswerStream. Future dispose() async { await _controller.close(); await _subscription.cancel(); _port.close(); } + /// Handles incoming events from the underlying stream. + void _handleEvent(String event) { + if (event.startsWith(AIStreamEventPrefix.data)) { + _hasStarted = true; + final newText = event.substring(AIStreamEventPrefix.data.length); + _text += newText; + _onData?.call(_text); + } else if (event.startsWith(AIStreamEventPrefix.error)) { + _error = event.substring(AIStreamEventPrefix.error.length); + _onError?.call(_error!); + } else if (event.startsWith(AIStreamEventPrefix.metadata)) { + final s = event.substring(AIStreamEventPrefix.metadata.length); + _onMetadata?.call(parseMetadata(s)); + } else if (event == AIStreamEventPrefix.aiResponseLimit) { + _aiLimitReached = true; + _onAIResponseLimit?.call(); + } else if (event == AIStreamEventPrefix.aiImageResponseLimit) { + _aiImageLimitReached = true; + _onAIImageResponseLimit?.call(); + } else if (event.startsWith(AIStreamEventPrefix.aiMaxRequired)) { + final msg = event.substring(AIStreamEventPrefix.aiMaxRequired.length); + if (_onAIMaxRequired != null) { + _onAIMaxRequired!(msg); + } else { + _pendingAIMaxRequiredEvents.add(msg); + } + } else if (event.startsWith(AIStreamEventPrefix.localAINotReady)) { + if (_onLocalAIInitializing != null) { + _onLocalAIInitializing!(); + } else { + _pendingLocalAINotReady = true; + } + } + } + + void _onDoneCallback() { + _onEnd?.call(); + } + + void _handleError(dynamic error) { + _error = error.toString(); + _onError?.call(_error!); + } + + /// Registers listeners for various events. + /// + /// If certain events have already occurred (e.g. AI_MAX_REQUIRED or LOCAL_AI_NOT_READY), + /// they will be flushed immediately. void listen({ void Function(String text)? onData, void Function()? onStart, void Function()? onEnd, void Function(String error)? onError, void Function()? onAIResponseLimit, + void Function()? onAIImageResponseLimit, + void Function(String message)? onAIMaxRequired, void Function(MetadataCollection metadata)? onMetadata, + void Function()? onLocalAIInitializing, }) { _onData = onData; _onStart = onStart; _onEnd = onEnd; _onError = onError; _onAIResponseLimit = onAIResponseLimit; + _onAIImageResponseLimit = onAIImageResponseLimit; + _onAIMaxRequired = onAIMaxRequired; _onMetadata = onMetadata; + _onLocalAIInitializing = onLocalAIInitializing; + + // Flush pending AI_MAX_REQUIRED events. + if (_onAIMaxRequired != null && _pendingAIMaxRequiredEvents.isNotEmpty) { + for (final msg in _pendingAIMaxRequiredEvents) { + _onAIMaxRequired!(msg); + } + _pendingAIMaxRequiredEvents.clear(); + } + + // Flush pending LOCAL_AI_NOT_READY event. + if (_pendingLocalAINotReady && _onLocalAIInitializing != null) { + _onLocalAIInitializing!(); + _pendingLocalAINotReady = false; + } _onStart?.call(); } diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_select_message_bloc.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_select_message_bloc.dart new file mode 100644 index 0000000000..9977d1df72 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_select_message_bloc.dart @@ -0,0 +1,113 @@ +import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart'; +import 'package:appflowy/plugins/util.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_chat_core/flutter_chat_core.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'chat_select_message_bloc.freezed.dart'; + +class ChatSelectMessageBloc + extends Bloc { + ChatSelectMessageBloc({required this.viewNotifier}) + : super(ChatSelectMessageState.initial()) { + _dispatch(); + } + + final ViewPluginNotifier viewNotifier; + + void _dispatch() { + on( + (event, emit) { + event.when( + enableStartSelectingMessages: () { + emit(state.copyWith(enabled: true)); + }, + toggleSelectingMessages: () { + if (state.isSelectingMessages) { + emit( + state.copyWith( + isSelectingMessages: false, + selectedMessages: [], + ), + ); + } else { + emit(state.copyWith(isSelectingMessages: true)); + } + }, + toggleSelectMessage: (Message message) { + if (state.selectedMessages.contains(message)) { + emit( + state.copyWith( + selectedMessages: state.selectedMessages + .where((m) => m != message) + .toList(), + ), + ); + } else { + emit( + state.copyWith( + selectedMessages: [...state.selectedMessages, message], + ), + ); + } + }, + selectAllMessages: (List messages) { + final filtered = messages.where(isAIMessage).toList(); + emit(state.copyWith(selectedMessages: filtered)); + }, + unselectAllMessages: () { + emit(state.copyWith(selectedMessages: const [])); + }, + reset: () { + emit( + state.copyWith( + isSelectingMessages: false, + selectedMessages: [], + ), + ); + }, + ); + }, + ); + } + + bool isMessageSelected(String messageId) => + state.selectedMessages.any((m) => m.id == messageId); + + bool isAIMessage(Message message) { + return message.author.id == aiResponseUserId || + message.author.id == systemUserId || + message.author.id.startsWith("streamId:"); + } +} + +@freezed +class ChatSelectMessageEvent with _$ChatSelectMessageEvent { + const factory ChatSelectMessageEvent.enableStartSelectingMessages() = + _EnableStartSelectingMessages; + const factory ChatSelectMessageEvent.toggleSelectingMessages() = + _ToggleSelectingMessages; + const factory ChatSelectMessageEvent.toggleSelectMessage(Message message) = + _ToggleSelectMessage; + const factory ChatSelectMessageEvent.selectAllMessages( + List messages, + ) = _SelectAllMessages; + const factory ChatSelectMessageEvent.unselectAllMessages() = + _UnselectAllMessages; + const factory ChatSelectMessageEvent.reset() = _Reset; +} + +@freezed +class ChatSelectMessageState with _$ChatSelectMessageState { + const factory ChatSelectMessageState({ + required bool isSelectingMessages, + required List selectedMessages, + required bool enabled, + }) = _ChatSelectMessageState; + + factory ChatSelectMessageState.initial() => const ChatSelectMessageState( + enabled: false, + isSelectingMessages: false, + selectedMessages: [], + ); +} diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_select_sources_cubit.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_select_sources_cubit.dart index 4c4b473223..76cbd69cdf 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_select_sources_cubit.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/application/chat_select_sources_cubit.dart @@ -101,18 +101,23 @@ class ChatSource { } class ChatSettingsCubit extends Cubit { - ChatSettingsCubit() : super(ChatSettingsState.initial()); + ChatSettingsCubit({ + this.hideDisabled = false, + }) : super(ChatSettingsState.initial()); - List selectedSourceIds = []; - List sources = []; - List selectedSources = []; + final bool hideDisabled; + + final List selectedSourceIds = []; + final List sources = []; + final List selectedSources = []; String filter = ''; void updateSelectedSources(List newSelectedSourceIds) { - selectedSourceIds = [...newSelectedSourceIds]; + selectedSourceIds.clear(); + selectedSourceIds.addAll(newSelectedSourceIds); } - void refreshSources(List spaceViews) async { + void refreshSources(List spaceViews, ViewPB? currentSpace) async { filter = ""; final newSources = await Future.wait( @@ -121,6 +126,11 @@ class ChatSettingsCubit extends Cubit { for (final source in newSources) { _restrictSelectionIfNecessary(source.children); } + if (currentSpace != null) { + newSources + .firstWhereOrNull((e) => e.view.id == currentSpace.id) + ?.toggleIsExpanded(); + } final selected = newSources .map((source) => _buildSelectedSources(source)) @@ -160,6 +170,9 @@ class ChatSettingsCubit extends Cubit { if (childView.layout == ViewLayoutPB.Chat) { continue; } + if (childView.layout != ViewLayoutPB.Document && hideDisabled) { + continue; + } final childChatSource = await _recursiveBuild(childView, view); if (childChatSource.selectedStatus.isSelected) { selectedCount++; diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/chat.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/chat.dart index 72e6b8256a..76aba27dc0 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/chat.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/chat.dart @@ -1,14 +1,22 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/ai_chat/application/chat_select_message_bloc.dart'; import 'package:appflowy/plugins/ai_chat/chat_page.dart'; import 'package:appflowy/plugins/util.dart'; import 'package:appflowy/startup/plugin/plugin.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy/workspace/application/view_info/view_info_bloc.dart'; import 'package:appflowy/workspace/presentation/home/home_stack.dart'; +import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart'; +import 'package:appflowy/workspace/presentation/widgets/favorite_button.dart'; +import 'package:appflowy/workspace/presentation/widgets/more_view_actions/more_view_actions.dart'; +import 'package:appflowy/workspace/presentation/widgets/more_view_actions/widgets/common_view_action.dart'; import 'package:appflowy/workspace/presentation/widgets/tab_bar_item.dart'; import 'package:appflowy/workspace/presentation/widgets/view_title_bar.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -46,13 +54,16 @@ class AIChatPagePlugin extends Plugin { }) : notifier = ViewPluginNotifier(view: view); late final ViewInfoBloc _viewInfoBloc; + late final _chatMessageSelectorBloc = + ChatSelectMessageBloc(viewNotifier: notifier); @override final ViewPluginNotifier notifier; @override PluginWidgetBuilder get widgetBuilder => AIChatPagePluginWidgetBuilder( - bloc: _viewInfoBloc, + viewInfoBloc: _viewInfoBloc, + chatMessageSelectorBloc: _chatMessageSelectorBloc, notifier: notifier, ); @@ -71,6 +82,7 @@ class AIChatPagePlugin extends Plugin { @override void dispose() { _viewInfoBloc.close(); + _chatMessageSelectorBloc.close(); notifier.dispose(); } } @@ -78,11 +90,13 @@ class AIChatPagePlugin extends Plugin { class AIChatPagePluginWidgetBuilder extends PluginWidgetBuilder with NavigationItem { AIChatPagePluginWidgetBuilder({ - required this.bloc, + required this.viewInfoBloc, + required this.chatMessageSelectorBloc, required this.notifier, }); - final ViewInfoBloc bloc; + final ViewInfoBloc viewInfoBloc; + final ChatSelectMessageBloc chatMessageSelectorBloc; final ViewPluginNotifier notifier; int? deletedViewIndex; @@ -110,8 +124,11 @@ class AIChatPagePluginWidgetBuilder extends PluginWidgetBuilder return const SizedBox(); } - return BlocProvider.value( - value: bloc, + return MultiBlocProvider( + providers: [ + BlocProvider.value(value: chatMessageSelectorBloc), + BlocProvider.value(value: viewInfoBloc), + ], child: AIChatPage( userProfile: context.userProfile!, key: ValueKey(notifier.view.id), @@ -134,4 +151,55 @@ class AIChatPagePluginWidgetBuilder extends PluginWidgetBuilder @override EdgeInsets get contentPadding => EdgeInsets.zero; + + @override + Widget? get rightBarItem => MultiBlocProvider( + providers: [ + BlocProvider.value(value: viewInfoBloc), + BlocProvider.value(value: chatMessageSelectorBloc), + ], + child: BlocBuilder( + builder: (context, state) { + if (state.isSelectingMessages) { + return const SizedBox.shrink(); + } + + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + ViewFavoriteButton( + key: ValueKey('favorite_button_${notifier.view.id}'), + view: notifier.view, + ), + const HSpace(4), + MoreViewActions( + key: ValueKey(notifier.view.id), + view: notifier.view, + customActions: [ + CustomViewAction( + view: notifier.view, + disabled: !state.enabled, + leftIcon: FlowySvgs.ai_add_to_page_s, + label: LocaleKeys.moreAction_saveAsNewPage.tr(), + tooltipMessage: state.enabled + ? null + : LocaleKeys.moreAction_saveAsNewPageDisabled.tr(), + onTap: () { + chatMessageSelectorBloc.add( + const ChatSelectMessageEvent + .toggleSelectingMessages(), + ); + }, + ), + ViewAction( + type: ViewMoreActionType.divider, + view: notifier.view, + ), + ], + ), + ], + ); + }, + ), + ); } diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/chat_page.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/chat_page.dart index dc6d9c649c..90085354db 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/chat_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/chat_page.dart @@ -1,15 +1,15 @@ import 'dart:io'; -import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/ai/ai.dart'; +import 'package:appflowy/plugins/ai_chat/presentation/chat_message_selector_banner.dart'; import 'package:appflowy/workspace/application/view/view_service.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; import 'package:appflowy_result/appflowy_result.dart'; import 'package:desktop_drop/desktop_drop.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_chat_core/flutter_chat_core.dart'; import 'package:flutter_chat_ui/flutter_chat_ui.dart' @@ -18,14 +18,13 @@ import 'package:string_validator/string_validator.dart'; import 'package:universal_platform/universal_platform.dart'; import 'package:url_launcher/url_launcher.dart'; -import 'application/ai_prompt_input_bloc.dart'; import 'application/chat_bloc.dart'; import 'application/chat_entity.dart'; import 'application/chat_member_bloc.dart'; +import 'application/chat_select_message_bloc.dart'; import 'application/chat_message_stream.dart'; import 'presentation/animated_chat_list.dart'; -import 'presentation/chat_input/desktop_ai_prompt_input.dart'; -import 'presentation/chat_input/mobile_ai_prompt_input.dart'; +import 'presentation/chat_input/mobile_chat_input.dart'; import 'presentation/chat_related_question.dart'; import 'presentation/chat_welcome_page.dart'; import 'presentation/layout_define.dart'; @@ -49,14 +48,14 @@ class AIChatPage extends StatelessWidget { @override Widget build(BuildContext context) { - if (userProfile.authenticator != AuthenticatorPB.AppFlowyCloud) { - return Center( - child: FlowyText( - LocaleKeys.chat_unsupportedCloudPrompt.tr(), - fontSize: 20, - ), - ); - } + // if (userProfile.authenticator != AuthTypePB.Server) { + // return Center( + // child: FlowyText( + // LocaleKeys.chat_unsupportedCloudPrompt.tr(), + // fontSize: 20, + // ), + // ); + // } return MultiBlocProvider( providers: [ @@ -69,7 +68,15 @@ class AIChatPage extends StatelessWidget { ), /// [AIPromptInputBloc] is used to handle the user prompt - BlocProvider(create: (_) => AIPromptInputBloc()), + BlocProvider( + create: (_) => AIPromptInputBloc( + objectId: view.id, + predefinedFormat: PredefinedFormat( + imageFormat: ImageFormat.text, + textFormat: TextFormat.bulletList, + ), + ), + ), BlocProvider(create: (_) => ChatMemberBloc()), ], child: Builder( @@ -84,9 +91,29 @@ class AIChatPage extends StatelessWidget { } } }, - child: _ChatContentPage( - view: view, - userProfile: userProfile, + child: FocusScope( + onKeyEvent: (focusNode, event) { + if (event is! KeyUpEvent) { + return KeyEventResult.ignored; + } + + if (event.logicalKey == LogicalKeyboardKey.escape || + event.logicalKey == LogicalKeyboardKey.keyC && + HardwareKeyboard.instance.isControlPressed) { + final chatBloc = context.read(); + if (chatBloc.state.promptResponseState != + PromptResponseState.ready) { + chatBloc.add(ChatEvent.stopStream()); + return KeyEventResult.handled; + } + } + + return KeyEventResult.ignored; + }, + child: _ChatContentPage( + view: view, + userProfile: userProfile, + ), ), ); }, @@ -106,44 +133,61 @@ class _ChatContentPage extends StatelessWidget { @override Widget build(BuildContext context) { - return Center( - child: Container( - constraints: const BoxConstraints(maxWidth: 784), - margin: UniversalPlatform.isDesktop - ? const EdgeInsets.symmetric(horizontal: 60.0) - : null, - child: ScrollConfiguration( - behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false), - child: BlocBuilder( - builder: (context, state) { - return switch (state.loadingState) { - LoadChatMessageStatus.ready => Column( - children: [ - Expanded( - child: Chat( - chatController: - context.read().chatController, - user: User(id: userProfile.id.toString()), - darkTheme: ChatTheme.fromThemeData(Theme.of(context)), - theme: ChatTheme.fromThemeData(Theme.of(context)), - builders: Builders( - inputBuilder: (_) => const SizedBox.shrink(), - textMessageBuilder: _buildTextMessage, - chatMessageBuilder: _buildChatMessage, - scrollToBottomBuilder: _buildScrollToBottom, - chatAnimatedListBuilder: _buildChatAnimatedList, + return BlocBuilder( + builder: (context, state) { + return switch (state.loadingState) { + LoadChatMessageStatus.ready => Column( + children: [ + ChatMessageSelectorBanner( + view: view, + allMessages: context.read().chatController.messages, + ), + Expanded( + child: Align( + alignment: Alignment.topCenter, + child: _wrapConstraints( + SelectionArea( + child: ScrollConfiguration( + behavior: ScrollConfiguration.of(context) + .copyWith(scrollbars: false), + child: Chat( + chatController: + context.read().chatController, + user: User(id: userProfile.id.toString()), + darkTheme: + ChatTheme.fromThemeData(Theme.of(context)), + theme: ChatTheme.fromThemeData(Theme.of(context)), + builders: Builders( + inputBuilder: (_) => const SizedBox.shrink(), + textMessageBuilder: _buildTextMessage, + chatMessageBuilder: _buildChatMessage, + scrollToBottomBuilder: _buildScrollToBottom, + chatAnimatedListBuilder: _buildChatAnimatedList, + ), ), ), ), - _buildInput(context), - ], + ), ), - _ => const Center(child: CircularProgressIndicator.adaptive()), - }; - }, - ), - ), - ), + ), + _wrapConstraints( + _Input(view: view), + ), + ], + ), + _ => const Center(child: CircularProgressIndicator.adaptive()), + }; + }, + ); + } + + Widget _wrapConstraints(Widget child) { + return Container( + constraints: const BoxConstraints(maxWidth: 784), + margin: UniversalPlatform.isDesktop + ? const EdgeInsets.symmetric(horizontal: 60.0) + : null, + child: child, ); } @@ -165,26 +209,25 @@ class _ChatContentPage extends StatelessWidget { return RelatedQuestionList( relatedQuestions: message.metadata!['questions'], onQuestionSelected: (question) { - context - .read() - .add(ChatEvent.sendMessage(message: question)); + final bloc = context.read(); + final showPredefinedFormats = bloc.state.showPredefinedFormats; + final predefinedFormat = bloc.state.predefinedFormat; + + context.read().add( + ChatEvent.sendMessage( + message: question, + format: showPredefinedFormats ? predefinedFormat : null, + ), + ); }, ); } - if (message.author.id == userProfile.id.toString()) { + if (message.author.id == userProfile.id.toString() || + isOtherUserMessage(message)) { return ChatUserMessageWidget( user: message.author, message: message, - isCurrentUser: true, - ); - } - - if (isOtherUserMessage(message)) { - return ChatUserMessageWidget( - user: message.author, - message: message, - isCurrentUser: false, ); } @@ -193,28 +236,44 @@ class _ChatContentPage extends StatelessWidget { final refSourceJsonString = message.metadata?[messageRefSourceJsonStringKey] as String?; - return BlocBuilder( - builder: (context, state) { - final chatController = context.read().chatController; - final messages = chatController.messages - .where((e) => onetimeMessageTypeFromMeta(e.metadata) == null); - final isLastMessage = - messages.isEmpty ? false : messages.last.id == message.id; - return ChatAIMessageWidget( - user: message.author, - messageUserId: message.id, - message: message, - stream: stream is AnswerStream ? stream : null, - questionId: questionId, - chatId: view.id, - refSourceJsonString: refSourceJsonString, - isStreaming: state.promptResponseState != PromptResponseState.ready, - isLastMessage: isLastMessage, - onSelectedMetadata: (metadata) => - _onSelectMetadata(context, metadata), - onRegenerate: () => context - .read() - .add(ChatEvent.regenerateAnswer(message.id)), + return BlocSelector( + selector: (state) => state.isSelectingMessages, + builder: (context, isSelectingMessages) { + return BlocBuilder( + builder: (context, state) { + final chatController = context.read().chatController; + final messages = chatController.messages + .where((e) => onetimeMessageTypeFromMeta(e.metadata) == null); + final isLastMessage = + messages.isEmpty ? false : messages.last.id == message.id; + return ChatAIMessageWidget( + user: message.author, + messageUserId: message.id, + message: message, + stream: stream is AnswerStream ? stream : null, + questionId: questionId, + chatId: view.id, + refSourceJsonString: refSourceJsonString, + isStreaming: + state.promptResponseState != PromptResponseState.ready, + isLastMessage: isLastMessage, + isSelectingMessages: isSelectingMessages, + onSelectedMetadata: (metadata) => + _onSelectMetadata(context, metadata), + onRegenerate: () => context + .read() + .add(ChatEvent.regenerateAnswer(message.id, null, null)), + onChangeFormat: (format) => context + .read() + .add(ChatEvent.regenerateAnswer(message.id, format, null)), + onChangeModel: (model) => context + .read() + .add(ChatEvent.regenerateAnswer(message.id, null, model)), + onStopStream: () => context.read().add( + const ChatEvent.stopStream(), + ), + ); + }, ); }, ); @@ -257,103 +316,176 @@ class _ChatContentPage extends StatelessWidget { return ChatWelcomePage( userProfile: userProfile, onSelectedQuestion: (question) { - bloc.add(ChatEvent.sendMessage(message: question)); + final aiPromptInputBloc = context.read(); + final showPredefinedFormats = + aiPromptInputBloc.state.showPredefinedFormats; + final predefinedFormat = aiPromptInputBloc.state.predefinedFormat; + bloc.add( + ChatEvent.sendMessage( + message: question, + format: showPredefinedFormats ? predefinedFormat : null, + ), + ); }, ); } - return ChatAnimatedListReversed( - scrollController: scrollController, - itemBuilder: itemBuilder, - onLoadPreviousMessages: () { - bloc.add(const ChatEvent.loadPreviousMessages()); + context + .read() + .add(ChatSelectMessageEvent.enableStartSelectingMessages()); + + return BlocSelector( + selector: (state) => state.isSelectingMessages, + builder: (context, isSelectingMessages) { + return ChatAnimatedListReversed( + scrollController: scrollController, + itemBuilder: itemBuilder, + bottomPadding: isSelectingMessages + ? 48.0 + DesktopAIChatSizes.messageActionBarIconSize + : 8.0, + onLoadPreviousMessages: () { + bloc.add(const ChatEvent.loadPreviousMessages()); + }, + ); }, ); } - Widget _buildInput(BuildContext context) { - return Padding( - padding: AIChatUILayout.safeAreaInsets(context), - child: BlocSelector( - selector: (state) { - return state.promptResponseState == PromptResponseState.ready; - }, - builder: (context, canSendMessage) { - final chatBloc = context.read(); - - return UniversalPlatform.isDesktop - ? DesktopAIPromptInput( - chatId: view.id, - isStreaming: !canSendMessage, - onStopStreaming: () { - chatBloc.add(const ChatEvent.stopStream()); - }, - onSubmitted: (text, metadata) { - chatBloc.add( - ChatEvent.sendMessage( - message: text, - metadata: metadata, - ), - ); - }, - onUpdateSelectedSources: (ids) { - chatBloc.add( - ChatEvent.updateSelectedSources( - selectedSourcesIds: ids, - ), - ); - }, - ) - : MobileAIPromptInput( - chatId: view.id, - isStreaming: !canSendMessage, - onStopStreaming: () { - chatBloc.add(const ChatEvent.stopStream()); - }, - onSubmitted: (text, metadata) { - chatBloc.add( - ChatEvent.sendMessage( - message: text, - metadata: metadata, - ), - ); - }, - onUpdateSelectedSources: (ids) { - chatBloc.add( - ChatEvent.updateSelectedSources( - selectedSourcesIds: ids, - ), - ); - }, - ); - }, - ), - ); - } - void _onSelectMetadata( BuildContext context, ChatMessageRefSource metadata, ) async { - if (isURL(metadata.name)) { - late Uri uri; - try { - uri = Uri.parse(metadata.name); - // `Uri` identifies `localhost` as a scheme - if (!uri.hasScheme || uri.scheme == 'localhost') { - uri = Uri.parse("http://${metadata.name}"); - await InternetAddress.lookup(uri.host); - } - await launchUrl(uri); - } catch (err) { - Log.error("failed to open url $err"); - } - } else { + // When the source of metatdata is appflowy, which means it is a appflowy page + if (metadata.source == "appflowy") { final sidebarView = await ViewBackendService.getView(metadata.id).toNullable(); if (context.mounted) { openPageFromMessage(context, sidebarView); } + return; + } + + if (metadata.source == "web") { + if (isURL(metadata.name)) { + late Uri uri; + try { + uri = Uri.parse(metadata.name); + // `Uri` identifies `localhost` as a scheme + if (!uri.hasScheme || uri.scheme == 'localhost') { + uri = Uri.parse("http://${metadata.name}"); + await InternetAddress.lookup(uri.host); + } + await launchUrl(uri); + } catch (err) { + Log.error("failed to open url $err"); + } + } + return; } } } + +class _Input extends StatefulWidget { + const _Input({ + required this.view, + }); + + final ViewPB view; + + @override + State<_Input> createState() => _InputState(); +} + +class _InputState extends State<_Input> { + final textController = TextEditingController(); + + @override + void dispose() { + textController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return BlocSelector( + selector: (state) => state.isSelectingMessages, + builder: (context, isSelectingMessages) { + return AnimatedSwitcher( + duration: const Duration(milliseconds: 150), + transitionBuilder: (child, animation) { + return SizeTransition( + sizeFactor: animation, + axisAlignment: -1, + child: child, + ); + }, + child: isSelectingMessages + ? const SizedBox.shrink() + : Padding( + padding: AIChatUILayout.safeAreaInsets(context), + child: BlocSelector( + selector: (state) { + return state.promptResponseState == + PromptResponseState.ready; + }, + builder: (context, canSendMessage) { + final chatBloc = context.read(); + + return UniversalPlatform.isDesktop + ? DesktopPromptInput( + isStreaming: !canSendMessage, + textController: textController, + onStopStreaming: () { + chatBloc.add(const ChatEvent.stopStream()); + }, + onSubmitted: (text, format, metadata) { + chatBloc.add( + ChatEvent.sendMessage( + message: text, + format: format, + metadata: metadata, + ), + ); + }, + selectedSourcesNotifier: + chatBloc.selectedSourcesNotifier, + onUpdateSelectedSources: (ids) { + chatBloc.add( + ChatEvent.updateSelectedSources( + selectedSourcesIds: ids, + ), + ); + }, + ) + : MobileChatInput( + isStreaming: !canSendMessage, + onStopStreaming: () { + chatBloc.add(const ChatEvent.stopStream()); + }, + onSubmitted: (text, format, metadata) { + chatBloc.add( + ChatEvent.sendMessage( + message: text, + format: format, + metadata: metadata, + ), + ); + }, + selectedSourcesNotifier: + chatBloc.selectedSourcesNotifier, + onUpdateSelectedSources: (ids) { + chatBloc.add( + ChatEvent.updateSelectedSources( + selectedSourcesIds: ids, + ), + ); + }, + ); + }, + ), + ), + ); + }, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/animated_chat_list.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/animated_chat_list.dart index e9d7b940b3..9b7aadf4a4 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/animated_chat_list.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/animated_chat_list.dart @@ -7,10 +7,9 @@ import 'package:diffutil_dart/diffutil.dart' as diffutil; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_chat_core/flutter_chat_core.dart'; -import 'package:provider/provider.dart'; - import 'package:flutter_chat_ui/src/scroll_to_bottom.dart'; import 'package:flutter_chat_ui/src/utils/message_list_diff.dart'; +import 'package:provider/provider.dart'; class ChatAnimatedListReversed extends StatefulWidget { const ChatAnimatedListReversed({ @@ -71,7 +70,7 @@ class ChatAnimatedListReversedState extends State event.message != null, 'Message must be provided when inserting a message.', ); - _onInserted(0, event.message!); + _onInserted(event.index!, event.message!); _oldList = List.from(_chatController.messages); break; case ChatOperationType.remove: @@ -208,7 +207,7 @@ class ChatAnimatedListReversedState extends State if (data.id == _lastInsertedMessageId && widget.scrollController.offset > widget.scrollController.position.minScrollExtent && - (user.id == data.author.id || !_userHasScrolled)) { + (user.id == data.author.id && _userHasScrolled)) { if (widget.scrollToEndAnimationDuration == Duration.zero) { widget.scrollController .jumpTo(widget.scrollController.position.minScrollExtent); @@ -234,7 +233,7 @@ class ChatAnimatedListReversedState extends State if (data.id == _lastInsertedMessageId && widget.scrollController.offset > widget.scrollController.position.minScrollExtent && - (user.id == data.author.id || !_userHasScrolled)) { + (user.id == data.author.id && _userHasScrolled)) { widget.scrollController .jumpTo(widget.scrollController.position.minScrollExtent); } @@ -311,14 +310,15 @@ class ChatAnimatedListReversedState extends State // If for some reason `_userHasScrolled` is true and the user is not at the // bottom of the list, set `_userHasScrolled` to false so that the scroll // animation is triggered. - if (_userHasScrolled && - widget.scrollController.offset >= + if (position == 0 && + _userHasScrolled && + widget.scrollController.offset > widget.scrollController.position.minScrollExtent) { _userHasScrolled = false; } _listKey.currentState!.insertItem( - position, + 0, duration: widget.insertAnimationDuration, ); diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_avatar.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_avatar.dart index d9d4594240..59b7fbd39b 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_avatar.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_avatar.dart @@ -10,15 +10,13 @@ import 'package:string_validator/string_validator.dart'; import 'layout_define.dart'; class ChatAIAvatar extends StatelessWidget { - const ChatAIAvatar({ - super.key, - }); + const ChatAIAvatar({super.key}); @override Widget build(BuildContext context) { return Container( - width: DesktopAIConvoSizes.avatarSize, - height: DesktopAIConvoSizes.avatarSize, + width: DesktopAIChatSizes.avatarSize, + height: DesktopAIChatSizes.avatarSize, clipBehavior: Clip.hardEdge, decoration: const BoxDecoration(shape: BoxShape.circle), foregroundDecoration: ShapeDecoration( @@ -29,7 +27,7 @@ class ChatAIAvatar extends StatelessWidget { child: const CircleAvatar( backgroundColor: Colors.transparent, child: FlowySvg( - FlowySvgs.flowy_logo_s, + FlowySvgs.app_logo_s, size: Size.square(16), blendMode: null, ), @@ -61,8 +59,8 @@ class ChatUserAvatar extends StatelessWidget { child = _buildEmojiAvatar(context); } return Container( - width: DesktopAIConvoSizes.avatarSize, - height: DesktopAIConvoSizes.avatarSize, + width: DesktopAIChatSizes.avatarSize, + height: DesktopAIChatSizes.avatarSize, clipBehavior: Clip.hardEdge, decoration: const BoxDecoration(shape: BoxShape.circle), foregroundDecoration: ShapeDecoration( @@ -102,7 +100,7 @@ class ChatUserAvatar extends StatelessWidget { Widget _buildUrlAvatar(BuildContext context) { return CircleAvatar( backgroundColor: Colors.transparent, - radius: DesktopAIConvoSizes.avatarSize / 2, + radius: DesktopAIChatSizes.avatarSize / 2, child: Image.network( iconUrl, fit: BoxFit.cover, @@ -115,7 +113,7 @@ class ChatUserAvatar extends StatelessWidget { Widget _buildEmojiAvatar(BuildContext context) { return CircleAvatar( backgroundColor: Colors.transparent, - radius: DesktopAIConvoSizes.avatarSize / 2, + radius: DesktopAIChatSizes.avatarSize / 2, child: builtInSVGIcons.contains(iconUrl) ? FlowySvg( FlowySvgData('emoji/$iconUrl'), diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_editor_style.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_editor_style.dart index a979e80746..b79e3c52c3 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_editor_style.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_editor_style.dart @@ -75,7 +75,8 @@ class ChatEditorStyleCustomizer extends EditorStyleCustomizer { fontSize: fontSize, fontWeight: FontWeight.normal, color: Colors.red, - backgroundColor: theme.colorScheme.inverseSurface.withOpacity(0.8), + backgroundColor: + theme.colorScheme.inverseSurface.withValues(alpha: 0.8), ), ), ), @@ -144,7 +145,7 @@ class ChatEditorStyleCustomizer extends EditorStyleCustomizer { return TextStyle( fontFamily: defaultFontFamily, height: 1.5, - color: AFThemeExtension.of(context).onBackground.withOpacity(0.6), + color: AFThemeExtension.of(context).onBackground.withValues(alpha: 0.6), ); } } diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/ai_prompt_buttons.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/ai_prompt_buttons.dart deleted file mode 100644 index 7d0fc10e3f..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/ai_prompt_buttons.dart +++ /dev/null @@ -1,123 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:appflowy/generated/flowy_svgs.g.dart'; -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/theme_extension.dart'; -import 'package:flowy_infra_ui/style_widget/icon_button.dart'; -import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; - -import '../layout_define.dart'; - -enum SendButtonState { enabled, streaming, disabled } - -class PromptInputAttachmentButton extends StatelessWidget { - const PromptInputAttachmentButton({required this.onTap, super.key}); - - final VoidCallback onTap; - - @override - Widget build(BuildContext context) { - return FlowyTooltip( - message: LocaleKeys.chat_uploadFile.tr(), - child: SizedBox.square( - dimension: DesktopAIPromptSizes.actionBarButtonSize, - child: FlowyIconButton( - hoverColor: AFThemeExtension.of(context).lightGreyHover, - radius: BorderRadius.circular(8), - icon: FlowySvg( - FlowySvgs.ai_attachment_s, - size: const Size.square(16), - color: Theme.of(context).iconTheme.color, - ), - onPressed: onTap, - ), - ), - ); - } -} - -class PromptInputMentionButton extends StatelessWidget { - const PromptInputMentionButton({ - super.key, - required this.buttonSize, - required this.iconSize, - required this.onTap, - }); - - final double buttonSize; - final double iconSize; - final VoidCallback onTap; - - @override - Widget build(BuildContext context) { - return FlowyTooltip( - message: LocaleKeys.chat_clickToMention.tr(), - preferBelow: false, - child: FlowyIconButton( - width: buttonSize, - hoverColor: AFThemeExtension.of(context).lightGreyHover, - radius: BorderRadius.circular(8), - icon: FlowySvg( - FlowySvgs.chat_at_s, - size: Size.square(iconSize), - color: Theme.of(context).iconTheme.color, - ), - onPressed: onTap, - ), - ); - } -} - -class PromptInputSendButton extends StatelessWidget { - const PromptInputSendButton({ - super.key, - required this.buttonSize, - required this.iconSize, - required this.state, - required this.onSendPressed, - required this.onStopStreaming, - }); - - final double buttonSize; - final double iconSize; - final SendButtonState state; - final VoidCallback onSendPressed; - final VoidCallback onStopStreaming; - - @override - Widget build(BuildContext context) { - return FlowyIconButton( - width: buttonSize, - icon: switch (state) { - SendButtonState.enabled => FlowySvg( - FlowySvgs.ai_send_filled_s, - size: Size.square(iconSize), - color: Theme.of(context).colorScheme.primary, - ), - SendButtonState.disabled => FlowySvg( - FlowySvgs.ai_send_filled_s, - size: Size.square(iconSize), - color: Theme.of(context).disabledColor, - ), - SendButtonState.streaming => FlowySvg( - FlowySvgs.ai_stop_filled_s, - size: Size.square(iconSize), - color: Theme.of(context).colorScheme.primary, - ), - }, - onPressed: () { - switch (state) { - case SendButtonState.enabled: - onSendPressed(); - break; - case SendButtonState.streaming: - onStopStreaming(); - break; - case SendButtonState.disabled: - break; - } - }, - hoverColor: Colors.transparent, - ); - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/chat_input_file.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/chat_input_file.dart deleted file mode 100644 index d2f6f6750e..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/chat_input_file.dart +++ /dev/null @@ -1,167 +0,0 @@ -import 'package:appflowy/generated/flowy_svgs.g.dart'; -import 'package:appflowy/plugins/ai_chat/application/ai_prompt_input_bloc.dart'; -import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart'; -import 'package:appflowy/plugins/ai_chat/application/chat_input_file_bloc.dart'; -import 'package:flowy_infra/theme_extension.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:styled_widget/styled_widget.dart'; - -import '../layout_define.dart'; - -class ChatInputFile extends StatelessWidget { - const ChatInputFile({ - super.key, - required this.chatId, - required this.onDeleted, - }); - - final String chatId; - final void Function(ChatFile) onDeleted; - - @override - Widget build(BuildContext context) { - return BlocSelector>( - selector: (state) => state.attachedFiles, - builder: (context, files) { - if (files.isEmpty) { - return const SizedBox.shrink(); - } - return ListView.separated( - scrollDirection: Axis.horizontal, - padding: DesktopAIPromptSizes.attachedFilesBarPadding - - const EdgeInsets.only(top: 6), - separatorBuilder: (context, index) => const HSpace( - DesktopAIPromptSizes.attachedFilesPreviewSpacing - 6, - ), - itemCount: files.length, - itemBuilder: (context, index) => ChatFilePreview( - chatId: chatId, - file: files[index], - onDeleted: () => onDeleted(files[index]), - ), - ); - }, - ); - } -} - -class ChatFilePreview extends StatefulWidget { - const ChatFilePreview({ - required this.chatId, - required this.file, - required this.onDeleted, - super.key, - }); - - final String chatId; - final ChatFile file; - final VoidCallback onDeleted; - - @override - State createState() => _ChatFilePreviewState(); -} - -class _ChatFilePreviewState extends State { - bool isHover = false; - - @override - Widget build(BuildContext context) { - return BlocProvider( - create: (context) => ChatInputFileBloc(file: widget.file), - child: BlocBuilder( - builder: (context, state) { - return MouseRegion( - onEnter: (_) => setHover(true), - onExit: (_) => setHover(false), - child: Stack( - children: [ - Container( - margin: const EdgeInsetsDirectional.only(top: 6, end: 6), - constraints: const BoxConstraints(maxWidth: 240), - decoration: BoxDecoration( - border: Border.all( - color: Theme.of(context).dividerColor, - ), - borderRadius: BorderRadius.circular(8), - ), - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - decoration: BoxDecoration( - color: AFThemeExtension.of(context).tint1, - borderRadius: BorderRadius.circular(8), - ), - height: 32, - width: 32, - child: Center( - child: FlowySvg( - FlowySvgs.page_m, - size: const Size.square(16), - color: Theme.of(context).hintColor, - ), - ), - ), - const HSpace(8), - Flexible( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - FlowyText( - widget.file.fileName, - fontSize: 12.0, - ), - FlowyText( - widget.file.fileType.name, - color: Theme.of(context).hintColor, - fontSize: 12.0, - ), - ], - ), - ), - ], - ), - ), - if (isHover) - _CloseButton( - onTap: widget.onDeleted, - ).positioned(top: 0, right: 0), - ], - ), - ); - }, - ), - ); - } - - void setHover(bool value) { - if (value != isHover) { - setState(() => isHover = value); - } - } -} - -class _CloseButton extends StatelessWidget { - const _CloseButton({required this.onTap}); - - final VoidCallback onTap; - - @override - Widget build(BuildContext context) { - return MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: onTap, - child: FlowySvg( - FlowySvgs.ai_close_filled_s, - color: AFThemeExtension.of(context).greyHover, - size: const Size.square(16), - ), - ), - ); - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/chat_input_span.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/chat_input_span.dart deleted file mode 100644 index 06d8248048..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/chat_input_span.dart +++ /dev/null @@ -1,78 +0,0 @@ -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/ai_chat/application/chat_input_control_cubit.dart'; -import 'package:collection/collection.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:extended_text_library/extended_text_library.dart'; -import 'package:flutter/material.dart'; - -class ChatInputTextSpanBuilder extends SpecialTextSpanBuilder { - ChatInputTextSpanBuilder({ - required this.inputControlCubit, - this.specialTextStyle, - }); - - final ChatInputControlCubit inputControlCubit; - final TextStyle? specialTextStyle; - - @override - SpecialText? createSpecialText( - String flag, { - TextStyle? textStyle, - SpecialTextGestureTapCallback? onTap, - int? index, - }) { - if (flag == '') { - return null; - } - - if (!isStart(flag, AtText.flag)) { - return null; - } - - // index is at the end of the start flag, so the start index should be index - (flag.length - 1) - return AtText( - inputControlCubit, - specialTextStyle ?? textStyle, - onTap, - // scrubbing over text is kinda funky - start: index! - (AtText.flag.length - 1), - ); - } -} - -class AtText extends SpecialText { - AtText( - this.inputControlCubit, - TextStyle? textStyle, - SpecialTextGestureTapCallback? onTap, { - this.start, - }) : super(flag, '', textStyle, onTap: onTap); - - static const String flag = '@'; - - final int? start; - final ChatInputControlCubit inputControlCubit; - - @override - bool isEnd(String value) => inputControlCubit.selectedViewIds.contains(value); - - @override - InlineSpan finishText() { - final String actualText = toString(); - - final viewName = inputControlCubit.allViews - .firstWhereOrNull((view) => view.id == actualText.substring(1)) - ?.name ?? - ""; - final nonEmptyName = viewName.isEmpty - ? LocaleKeys.document_title_placeholder.tr() - : viewName; - - return SpecialTextSpan( - text: "@$nonEmptyName", - actualText: actualText, - start: start!, - style: textStyle, - ); - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/chat_mention_page_bottom_sheet.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/chat_mention_page_bottom_sheet.dart deleted file mode 100644 index cb2e7248d4..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/chat_mention_page_bottom_sheet.dart +++ /dev/null @@ -1,204 +0,0 @@ -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/mobile/presentation/base/flowy_search_text_field.dart'; -import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart'; -import 'package:appflowy/plugins/base/drag_handler.dart'; -import 'package:appflowy/workspace/application/view/view_service.dart'; -import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/material.dart'; - -import 'chat_mention_page_menu.dart'; - -Future showPageSelectorSheet( - BuildContext context, { - required bool Function(ViewPB view) filter, -}) async { - return showMobileBottomSheet( - context, - backgroundColor: Theme.of(context).colorScheme.surface, - maxChildSize: 0.98, - enableDraggableScrollable: true, - scrollableWidgetBuilder: (context, scrollController) { - return Expanded( - child: _MobilePageSelectorBody( - filter: filter, - scrollController: scrollController, - ), - ); - }, - builder: (context) => const SizedBox.shrink(), - ); -} - -class _MobilePageSelectorBody extends StatefulWidget { - const _MobilePageSelectorBody({ - this.filter, - this.scrollController, - }); - - final bool Function(ViewPB view)? filter; - final ScrollController? scrollController; - - @override - State<_MobilePageSelectorBody> createState() => - _MobilePageSelectorBodyState(); -} - -class _MobilePageSelectorBodyState extends State<_MobilePageSelectorBody> { - final textController = TextEditingController(); - late final Future> _viewsFuture = _fetchViews(); - - @override - void dispose() { - textController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return CustomScrollView( - controller: widget.scrollController, - shrinkWrap: true, - slivers: [ - SliverPersistentHeader( - pinned: true, - delegate: _Header( - child: ColoredBox( - color: Theme.of(context).cardColor, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const DragHandle(), - SizedBox( - height: 44.0, - child: Center( - child: FlowyText.medium( - LocaleKeys.document_mobilePageSelector_title.tr(), - fontSize: 16.0, - ), - ), - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 8.0, - ), - child: SizedBox( - height: 44.0, - child: FlowySearchTextField( - controller: textController, - onChanged: (_) => setState(() {}), - ), - ), - ), - const Divider(height: 0.5, thickness: 0.5), - ], - ), - ), - ), - ), - FutureBuilder( - future: _viewsFuture, - builder: (_, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const SliverToBoxAdapter( - child: CircularProgressIndicator.adaptive(), - ); - } - - if (snapshot.hasError || snapshot.data == null) { - return SliverToBoxAdapter( - child: FlowyText( - LocaleKeys.document_mobilePageSelector_failedToLoad.tr(), - ), - ); - } - - final views = snapshot.data! - .where((v) => widget.filter?.call(v) ?? true) - .toList(); - - final filtered = views.where( - (v) => - textController.text.isEmpty || - v.name - .toLowerCase() - .contains(textController.text.toLowerCase()), - ); - - if (filtered.isEmpty) { - return SliverToBoxAdapter( - child: FlowyText( - LocaleKeys.document_mobilePageSelector_noPagesFound.tr(), - ), - ); - } - - return SliverPadding( - padding: - const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), - sliver: SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) { - final view = filtered.elementAt(index); - return InkWell( - onTap: () => Navigator.of(context).pop(view), - borderRadius: BorderRadius.circular(12), - splashColor: Colors.transparent, - child: Container( - height: 44, - padding: const EdgeInsets.all(4.0), - child: Row( - children: [ - MentionViewIcon(view: view), - const HSpace(8), - Expanded( - child: MentionViewTitleAndAncestors(view: view), - ), - ], - ), - ), - ); - }, - childCount: filtered.length, - ), - ), - ); - }, - ), - ], - ); - } - - Future> _fetchViews() async => - (await ViewBackendService.getAllViews()).toNullable()?.items ?? []; -} - -class _Header extends SliverPersistentHeaderDelegate { - const _Header({ - required this.child, - }); - - final Widget child; - - @override - Widget build( - BuildContext context, - double shrinkOffset, - bool overlapsContent, - ) { - return child; - } - - @override - double get maxExtent => 120.5; - - @override - double get minExtent => 120.5; - - @override - bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) { - return false; - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/chat_mention_page_menu.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/chat_mention_page_menu.dart deleted file mode 100644 index 6f3eb2d6db..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/chat_mention_page_menu.dart +++ /dev/null @@ -1,429 +0,0 @@ -import 'package:appflowy/generated/flowy_svgs.g.dart'; -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/ai_chat/application/chat_input_control_cubit.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart'; -import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; -import 'package:appflowy/workspace/application/view/view_ext.dart'; -import 'package:appflowy/workspace/application/view_title/view_title_bar_bloc.dart'; -import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon.dart'; -import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; -import 'package:easy_localization/easy_localization.dart' hide TextDirection; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/style_widget/hover.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:scroll_to_index/scroll_to_index.dart'; - -const double _itemHeight = 44.0; -const double _noPageHeight = 20.0; -const double _fixedWidth = 360.0; -const double _maxHeight = 328.0; - -class ChatInputAnchor { - ChatInputAnchor(this.anchorKey, this.layerLink); - - final GlobalKey> anchorKey; - final LayerLink layerLink; -} - -class ChatMentionPageMenu extends StatefulWidget { - const ChatMentionPageMenu({ - super.key, - required this.anchor, - required this.textController, - required this.onPageSelected, - }); - - final ChatInputAnchor anchor; - final TextEditingController textController; - final void Function(ViewPB view) onPageSelected; - - @override - State createState() => _ChatMentionPageMenuState(); -} - -class _ChatMentionPageMenuState extends State { - @override - void initState() { - super.initState(); - Future.delayed(Duration.zero, () { - context.read().refreshViews(); - }); - } - - @override - Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - return Stack( - children: [ - CompositedTransformFollower( - link: widget.anchor.layerLink, - showWhenUnlinked: false, - offset: Offset(getPopupOffsetX(), 0.0), - followerAnchor: Alignment.bottomLeft, - child: Container( - constraints: const BoxConstraints( - minWidth: _fixedWidth, - maxWidth: _fixedWidth, - maxHeight: _maxHeight, - ), - decoration: BoxDecoration( - color: Theme.of(context).cardColor, - borderRadius: BorderRadius.circular(6.0), - boxShadow: const [ - BoxShadow( - color: Color(0x0A1F2329), - blurRadius: 24, - offset: Offset(0, 8), - spreadRadius: 8, - ), - BoxShadow( - color: Color(0x0A1F2329), - blurRadius: 12, - offset: Offset(0, 6), - ), - BoxShadow( - color: Color(0x0F1F2329), - blurRadius: 8, - offset: Offset(0, 4), - spreadRadius: -8, - ), - ], - ), - child: TextFieldTapRegion( - child: ChatMentionPageList( - onPageSelected: widget.onPageSelected, - ), - ), - ), - ), - ], - ); - }, - ); - } - - double getPopupOffsetX() { - if (widget.anchor.anchorKey.currentContext == null) { - return 0.0; - } - - final cubit = context.read(); - if (cubit.filterStartPosition == -1) { - return 0.0; - } - - final textPosition = TextPosition(offset: cubit.filterEndPosition); - final renderBox = - widget.anchor.anchorKey.currentContext?.findRenderObject() as RenderBox; - - final textPainter = TextPainter( - text: TextSpan(text: cubit.formatIntputText(widget.textController.text)), - textDirection: TextDirection.ltr, - ); - textPainter.layout( - minWidth: renderBox.size.width, - maxWidth: renderBox.size.width, - ); - - final caretOffset = textPainter.getOffsetForCaret(textPosition, Rect.zero); - final boxes = textPainter.getBoxesForSelection( - TextSelection( - baseOffset: textPosition.offset, - extentOffset: textPosition.offset, - ), - ); - - if (boxes.isNotEmpty) { - return boxes.last.right; - } - - return caretOffset.dx; - } -} - -class ChatMentionPageList extends StatefulWidget { - const ChatMentionPageList({ - super.key, - required this.onPageSelected, - }); - - final void Function(ViewPB view) onPageSelected; - - @override - State createState() => _ChatMentionPageListState(); -} - -class _ChatMentionPageListState extends State { - final autoScrollController = SimpleAutoScrollController( - suggestedRowHeight: _itemHeight, - beginGetter: (rect) => rect.top + 8.0, - endGetter: (rect) => rect.bottom - 8.0, - ); - - @override - void dispose() { - autoScrollController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return BlocConsumer( - listenWhen: (previous, current) { - return previous.maybeWhen( - ready: (_, pFocusedViewIndex) => current.maybeWhen( - ready: (_, cFocusedViewIndex) => - pFocusedViewIndex != cFocusedViewIndex, - orElse: () => false, - ), - orElse: () => false, - ); - }, - listener: (context, state) { - state.maybeWhen( - ready: (views, focusedViewIndex) { - if (focusedViewIndex == -1 || !autoScrollController.hasClients) { - return; - } - if (autoScrollController.isAutoScrolling) { - autoScrollController.position - .jumpTo(autoScrollController.position.pixels); - } - autoScrollController.scrollToIndex( - focusedViewIndex, - duration: const Duration(milliseconds: 200), - preferPosition: AutoScrollPosition.begin, - ); - }, - orElse: () {}, - ); - }, - builder: (context, state) { - return state.maybeWhen( - loading: () { - return const Padding( - padding: EdgeInsets.all(8.0), - child: SizedBox( - height: _noPageHeight, - child: Center( - child: CircularProgressIndicator.adaptive(), - ), - ), - ); - }, - ready: (views, focusedViewIndex) { - if (views.isEmpty) { - return Padding( - padding: const EdgeInsets.all(8.0), - child: SizedBox( - height: _noPageHeight, - child: Center( - child: FlowyText( - LocaleKeys.chat_inputActionNoPages.tr(), - ), - ), - ), - ); - } - - return ListView.builder( - shrinkWrap: true, - controller: autoScrollController, - padding: const EdgeInsets.all(8.0), - itemCount: views.length, - itemBuilder: (context, index) { - final view = views[index]; - return AutoScrollTag( - key: ValueKey("chat_mention_page_item_${view.id}"), - index: index, - controller: autoScrollController, - child: _ChatMentionPageItem( - view: view, - onTap: () => widget.onPageSelected(view), - isSelected: focusedViewIndex == index, - ), - ); - }, - ); - }, - orElse: () => const SizedBox.shrink(), - ); - }, - ); - } -} - -class _ChatMentionPageItem extends StatelessWidget { - const _ChatMentionPageItem({ - required this.view, - required this.isSelected, - required this.onTap, - }); - - final ViewPB view; - final bool isSelected; - final VoidCallback onTap; - - @override - Widget build(BuildContext context) { - return FlowyTooltip( - message: view.name, - child: MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: onTap, - behavior: HitTestBehavior.opaque, - child: FlowyHover( - isSelected: () => isSelected, - child: Container( - height: _itemHeight, - padding: const EdgeInsets.all(4.0), - child: Row( - children: [ - MentionViewIcon(view: view), - const HSpace(8.0), - Expanded(child: MentionViewTitleAndAncestors(view: view)), - ], - ), - ), - ), - ), - ), - ); - } -} - -class MentionViewIcon extends StatelessWidget { - const MentionViewIcon({ - super.key, - required this.view, - }); - - final ViewPB view; - - @override - Widget build(BuildContext context) { - final spaceIcon = view.buildSpaceIconSvg(context); - - if (view.icon.value.isNotEmpty) { - return SizedBox( - width: 16.0, - child: EmojiIconWidget( - emoji: view.icon.toEmojiIconData(), - emojiSize: 14, - ), - ); - } - - if (view.isSpace == true && spaceIcon != null) { - return SpaceIcon( - dimension: 16.0, - svgSize: 9.68, - space: view, - cornerRadius: 4, - ); - } - - return FlowySvg( - view.layout.icon, - size: const Size.square(16), - color: Theme.of(context).hintColor, - ); - } -} - -class MentionViewTitleAndAncestors extends StatelessWidget { - const MentionViewTitleAndAncestors({ - super.key, - required this.view, - }); - - final ViewPB view; - - @override - Widget build(BuildContext context) { - return BlocProvider( - create: (_) => ViewTitleBarBloc(view: view), - child: BlocBuilder( - builder: (context, state) { - final nonEmptyName = view.name.isEmpty - ? LocaleKeys.document_title_placeholder.tr() - : view.name; - - final ancestorList = _getViewAncestorList(state.ancestors); - - if (state.ancestors.isEmpty || ancestorList.trim().isEmpty) { - return FlowyText( - nonEmptyName, - fontSize: 14.0, - overflow: TextOverflow.ellipsis, - ); - } - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - FlowyText( - nonEmptyName, - fontSize: 14.0, - figmaLineHeight: 20.0, - overflow: TextOverflow.ellipsis, - ), - FlowyText( - ancestorList, - fontSize: 12.0, - figmaLineHeight: 16.0, - color: Theme.of(context).hintColor, - overflow: TextOverflow.ellipsis, - ), - ], - ); - }, - ), - ); - } - - /// see workspace/presentation/widgets/view_title_bar.dart, upon which this - /// function was based. This version doesn't include the current view in the - /// result, and returns a string rather than a list of widgets - String _getViewAncestorList( - List views, - ) { - const lowerBound = 2; - final upperBound = views.length - 2; - bool hasAddedEllipsis = false; - String result = ""; - - if (views.length <= 1) { - return ""; - } - - // ignore the workspace name, use section name instead in the future - // skip the workspace view - for (var i = 1; i < views.length - 1; i++) { - final view = views[i]; - - if (i >= lowerBound && i < upperBound) { - if (!hasAddedEllipsis) { - hasAddedEllipsis = true; - result += "… / "; - } - continue; - } - - final nonEmptyName = view.name.isEmpty - ? LocaleKeys.document_title_placeholder.tr() - : view.name; - - result += nonEmptyName; - - if (i != views.length - 2) { - // if not the last one, add a divider - result += " / "; - } - } - return result; - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/desktop_ai_prompt_input.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/desktop_ai_prompt_input.dart deleted file mode 100644 index 0e3ca31980..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/desktop_ai_prompt_input.dart +++ /dev/null @@ -1,604 +0,0 @@ -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/ai_chat/application/ai_prompt_input_bloc.dart'; -import 'package:appflowy/plugins/ai_chat/application/chat_input_control_cubit.dart'; -import 'package:appflowy/startup/startup.dart'; -import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:extended_text_field/extended_text_field.dart'; -import 'package:flowy_infra/file_picker/file_picker_service.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -import '../layout_define.dart'; -import 'ai_prompt_buttons.dart'; -import 'chat_input_file.dart'; -import 'chat_input_span.dart'; -import 'chat_mention_page_menu.dart'; -import 'select_sources_menu.dart'; - -class DesktopAIPromptInput extends StatefulWidget { - const DesktopAIPromptInput({ - super.key, - required this.chatId, - required this.isStreaming, - required this.onStopStreaming, - required this.onSubmitted, - required this.onUpdateSelectedSources, - }); - - final String chatId; - final bool isStreaming; - final void Function() onStopStreaming; - final void Function(String, Map) onSubmitted; - final void Function(List) onUpdateSelectedSources; - - @override - State createState() => _DesktopAIPromptInputState(); -} - -class _DesktopAIPromptInputState extends State { - final textFieldKey = GlobalKey(); - final layerLink = LayerLink(); - final overlayController = OverlayPortalController(); - final inputControlCubit = ChatInputControlCubit(); - final focusNode = FocusNode(); - final textController = TextEditingController(); - - late SendButtonState sendButtonState; - - @override - void initState() { - super.initState(); - - textController.addListener(handleTextControllerChanged); - - // refresh border color on focus change and hide menu when lost focus - focusNode.addListener( - () => setState(() { - if (!focusNode.hasFocus) { - cancelMentionPage(); - } - }), - ); - - updateSendButtonState(); - - WidgetsBinding.instance.addPostFrameCallback((_) { - focusNode.requestFocus(); - }); - } - - @override - void didUpdateWidget(covariant oldWidget) { - updateSendButtonState(); - super.didUpdateWidget(oldWidget); - } - - @override - void dispose() { - focusNode.dispose(); - textController.dispose(); - inputControlCubit.close(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return BlocProvider.value( - value: inputControlCubit, - child: BlocListener( - listener: (context, state) { - state.maybeWhen( - updateSelectedViews: (selectedViews) { - context - .read() - .add(AIPromptInputEvent.updateMentionedViews(selectedViews)); - }, - orElse: () {}, - ); - }, - child: OverlayPortal( - controller: overlayController, - overlayChildBuilder: (context) { - return ChatMentionPageMenu( - anchor: ChatInputAnchor(textFieldKey, layerLink), - textController: textController, - onPageSelected: handlePageSelected, - ); - }, - child: DecoratedBox( - decoration: BoxDecoration( - border: Border.all( - color: focusNode.hasFocus - ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.outline, - width: focusNode.hasFocus ? 1.5 : 1.0, - ), - borderRadius: DesktopAIPromptSizes.promptFrameRadius, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ConstrainedBox( - constraints: BoxConstraints( - maxHeight: - DesktopAIPromptSizes.attachedFilesBarPadding.vertical + - DesktopAIPromptSizes.attachedFilesPreviewHeight, - ), - child: TextFieldTapRegion( - child: ChatInputFile( - chatId: widget.chatId, - onDeleted: (file) => context - .read() - .add(AIPromptInputEvent.removeFile(file)), - ), - ), - ), - Stack( - children: [ - ConstrainedBox( - constraints: BoxConstraints( - minHeight: DesktopAIPromptSizes.textFieldMinHeight + - DesktopAIPromptSizes.actionBarHeight + - DesktopAIPromptSizes.actionBarPadding.vertical, - maxHeight: 300, - ), - child: inputTextField(), - ), - Positioned.fill( - top: null, - child: TextFieldTapRegion( - child: _PromptBottomActions( - textController: textController, - overlayController: overlayController, - focusNode: focusNode, - sendButtonState: sendButtonState, - onSendPressed: handleSendPressed, - onStopStreaming: widget.onStopStreaming, - onUpdateSelectedSources: - widget.onUpdateSelectedSources, - ), - ), - ), - ], - ), - ], - ), - ), - ), - ), - ); - } - - void cancelMentionPage() { - if (overlayController.isShowing) { - inputControlCubit.reset(); - overlayController.hide(); - } - } - - void updateSendButtonState() { - if (widget.isStreaming) { - sendButtonState = SendButtonState.streaming; - } else if (textController.text.trim().isEmpty) { - sendButtonState = SendButtonState.disabled; - } else { - sendButtonState = SendButtonState.enabled; - } - } - - void handleSendPressed() { - if (widget.isStreaming) { - return; - } - final trimmedText = inputControlCubit.formatIntputText( - textController.text.trim(), - ); - textController.clear(); - if (trimmedText.isEmpty) { - return; - } - - // get the attached files and mentioned pages - final metadata = context.read().consumeMetadata(); - - widget.onSubmitted(trimmedText, metadata); - } - - void handleTextControllerChanged() { - if (!textController.value.composing.isCollapsed) { - return; - } - - // update whether send button is clickable - setState(() => updateSendButtonState()); - - // handle text and selection changes ONLY when mentioning a page - - // disable mention - return; - // ignore: dead_code - if (!overlayController.isShowing || - inputControlCubit.filterStartPosition == -1) { - return; - } - - // handle cases where mention a page is cancelled - final textSelection = textController.value.selection; - final isSelectingMultipleCharacters = !textSelection.isCollapsed; - final isCaretBeforeStartOfRange = - textSelection.baseOffset < inputControlCubit.filterStartPosition; - final isCaretAfterEndOfRange = - textSelection.baseOffset > inputControlCubit.filterEndPosition; - final isTextSame = inputControlCubit.inputText == textController.text; - - if (isSelectingMultipleCharacters || - isTextSame && (isCaretBeforeStartOfRange || isCaretAfterEndOfRange)) { - cancelMentionPage(); - return; - } - - final previousLength = inputControlCubit.inputText.characters.length; - final currentLength = textController.text.characters.length; - - // delete "@" - if (previousLength != currentLength && isCaretBeforeStartOfRange) { - cancelMentionPage(); - return; - } - - // handle cases where mention the filter is updated - if (previousLength != currentLength) { - final diff = currentLength - previousLength; - final newEndPosition = inputControlCubit.filterEndPosition + diff; - final newFilter = textController.text.substring( - inputControlCubit.filterStartPosition, - newEndPosition, - ); - inputControlCubit.updateFilter( - textController.text, - newFilter, - newEndPosition: newEndPosition, - ); - } else if (!isTextSame) { - final newFilter = textController.text.substring( - inputControlCubit.filterStartPosition, - inputControlCubit.filterEndPosition, - ); - inputControlCubit.updateFilter(textController.text, newFilter); - } - } - - void handlePageSelected(ViewPB view) { - final newText = textController.text.replaceRange( - inputControlCubit.filterStartPosition, - inputControlCubit.filterEndPosition, - view.id, - ); - textController.value = TextEditingValue( - text: newText, - selection: TextSelection.collapsed( - offset: inputControlCubit.filterStartPosition + view.id.length, - affinity: TextAffinity.upstream, - ), - ); - - inputControlCubit.selectPage(view); - overlayController.hide(); - } - - Widget inputTextField() { - return Actions( - actions: buildActions(), - child: CompositedTransformTarget( - link: layerLink, - child: BlocBuilder( - builder: (context, state) { - return _PromptTextField( - key: textFieldKey, - cubit: inputControlCubit, - textController: textController, - textFieldFocusNode: focusNode, - hintText: switch (state.aiType) { - AIType.appflowyAI => LocaleKeys.chat_inputMessageHint.tr(), - AIType.localAI => LocaleKeys.chat_inputLocalAIMessageHint.tr() - }, - // onStartMentioningPage: () { - // WidgetsBinding.instance.addPostFrameCallback((_) { - // inputControlCubit.startSearching(textController.value); - // overlayController.show(); - // }); - // }, - ); - }, - ), - ), - ); - } - - Map> buildActions() { - return { - _FocusPreviousItemIntent: CallbackAction<_FocusPreviousItemIntent>( - onInvoke: (intent) { - inputControlCubit.updateSelectionUp(); - return; - }, - ), - _FocusNextItemIntent: CallbackAction<_FocusNextItemIntent>( - onInvoke: (intent) { - inputControlCubit.updateSelectionDown(); - return; - }, - ), - _CancelMentionPageIntent: CallbackAction<_CancelMentionPageIntent>( - onInvoke: (intent) { - cancelMentionPage(); - return; - }, - ), - _SubmitOrMentionPageIntent: CallbackAction<_SubmitOrMentionPageIntent>( - onInvoke: (intent) { - if (overlayController.isShowing) { - inputControlCubit.state.maybeWhen( - ready: (visibleViews, focusedViewIndex) { - if (focusedViewIndex != -1 && - focusedViewIndex < visibleViews.length) { - handlePageSelected(visibleViews[focusedViewIndex]); - } - }, - orElse: () {}, - ); - } else { - handleSendPressed(); - } - return; - }, - ), - }; - } -} - -class _PromptTextField extends StatefulWidget { - const _PromptTextField({ - super.key, - required this.cubit, - required this.textController, - required this.textFieldFocusNode, - // required this.onStartMentioningPage, - this.hintText = "", - }); - - final ChatInputControlCubit cubit; - final TextEditingController textController; - final FocusNode textFieldFocusNode; - // final void Function() onStartMentioningPage; - final String hintText; - - @override - State<_PromptTextField> createState() => _PromptTextFieldState(); -} - -class _PromptTextFieldState extends State<_PromptTextField> { - bool isComposing = false; - - @override - void initState() { - super.initState(); - // widget.textFieldFocusNode.onKeyEvent = handleKeyEvent; - widget.textController.addListener(onTextChanged); - } - - @override - void dispose() { - // widget.textFieldFocusNode.onKeyEvent = null; - widget.textController.removeListener(onTextChanged); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Shortcuts( - shortcuts: buildShortcuts(), - child: ExtendedTextField( - controller: widget.textController, - focusNode: widget.textFieldFocusNode, - decoration: InputDecoration( - border: InputBorder.none, - enabledBorder: InputBorder.none, - focusedBorder: InputBorder.none, - contentPadding: DesktopAIPromptSizes.textFieldContentPadding.add( - const EdgeInsets.only( - bottom: DesktopAIPromptSizes.actionBarHeight, - ), - ), - hintText: widget.hintText, - hintStyle: Theme.of(context) - .textTheme - .bodyMedium - ?.copyWith(color: Theme.of(context).hintColor), - isCollapsed: true, - isDense: true, - ), - keyboardType: TextInputType.multiline, - textCapitalization: TextCapitalization.sentences, - minLines: 1, - maxLines: null, - style: Theme.of(context).textTheme.bodyMedium, - specialTextSpanBuilder: ChatInputTextSpanBuilder( - inputControlCubit: widget.cubit, - specialTextStyle: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context).colorScheme.primary, - fontWeight: FontWeight.w600, - ), - ), - ), - ); - } - - // KeyEventResult handleKeyEvent(FocusNode node, KeyEvent event) { - // if (event.character == '@') { - // widget.onStartMentioningPage(); - // } - // return KeyEventResult.ignored; - // } - - void onTextChanged() { - setState( - () => isComposing = !widget.textController.value.composing.isCollapsed, - ); - } - - Map buildShortcuts() { - if (isComposing) { - return const {}; - } - - return const { - SingleActivator(LogicalKeyboardKey.arrowUp): _FocusPreviousItemIntent(), - SingleActivator(LogicalKeyboardKey.arrowDown): _FocusNextItemIntent(), - SingleActivator(LogicalKeyboardKey.escape): _CancelMentionPageIntent(), - SingleActivator(LogicalKeyboardKey.enter): _SubmitOrMentionPageIntent(), - }; - } -} - -class _SubmitOrMentionPageIntent extends Intent { - const _SubmitOrMentionPageIntent(); -} - -class _CancelMentionPageIntent extends Intent { - const _CancelMentionPageIntent(); -} - -class _FocusPreviousItemIntent extends Intent { - const _FocusPreviousItemIntent(); -} - -class _FocusNextItemIntent extends Intent { - const _FocusNextItemIntent(); -} - -class _PromptBottomActions extends StatelessWidget { - const _PromptBottomActions({ - required this.textController, - required this.overlayController, - required this.focusNode, - required this.sendButtonState, - required this.onSendPressed, - required this.onStopStreaming, - required this.onUpdateSelectedSources, - }); - - final TextEditingController textController; - final OverlayPortalController overlayController; - final FocusNode focusNode; - final SendButtonState sendButtonState; - final void Function() onSendPressed; - final void Function() onStopStreaming; - final void Function(List) onUpdateSelectedSources; - - @override - Widget build(BuildContext context) { - return Container( - height: DesktopAIPromptSizes.actionBarHeight, - margin: DesktopAIPromptSizes.actionBarPadding, - child: BlocBuilder( - builder: (context, state) { - if (state.chatState == null) { - return Align( - alignment: AlignmentDirectional.centerEnd, - child: _sendButton(), - ); - } - return Row( - children: [ - // predefinedFormatButton(), - const Spacer(), - if (state.aiType == AIType.appflowyAI) ...[ - _selectSourcesButton(context), - const HSpace( - DesktopAIPromptSizes.actionBarButtonSpacing, - ), - ], - // _mentionButton(context), - // const HSpace( - // DesktopAIPromptSizes.actionBarButtonSpacing, - // ), - if (state.supportChatWithFile) ...[ - _attachmentButton(context), - const HSpace( - DesktopAIPromptSizes.actionBarButtonSpacing, - ), - ], - _sendButton(), - ], - ); - }, - ), - ); - } - - Widget _selectSourcesButton(BuildContext context) { - return PromptInputDesktopSelectSourcesButton( - onUpdateSelectedSources: onUpdateSelectedSources, - ); - } - - // Widget _mentionButton(BuildContext context) { - // return PromptInputMentionButton( - // iconSize: DesktopAIPromptSizes.actionBarIconSize, - // buttonSize: DesktopAIPromptSizes.actionBarButtonSize, - // onTap: () { - // if (overlayController.isShowing) { - // return; - // } - // if (!focusNode.hasFocus) { - // focusNode.requestFocus(); - // } - // textController.text += '@'; - // Future.delayed(Duration.zero, () { - // context - // .read() - // .startSearching(textController.value); - // overlayController.show(); - // }); - // }, - // ); - // } - - Widget _attachmentButton(BuildContext context) { - return PromptInputAttachmentButton( - onTap: () async { - final path = await getIt().pickFiles( - dialogTitle: '', - type: FileType.custom, - allowedExtensions: ["pdf", "txt", "md"], - ); - - if (path == null) { - return; - } - - for (final file in path.files) { - if (file.path != null && context.mounted) { - context - .read() - .add(AIPromptInputEvent.attachFile(file.path!, file.name)); - } - } - }, - ); - } - - Widget _sendButton() { - return PromptInputSendButton( - buttonSize: DesktopAIPromptSizes.actionBarHeight, - iconSize: DesktopAIPromptSizes.sendButtonSize, - state: sendButtonState, - onSendPressed: onSendPressed, - onStopStreaming: onStopStreaming, - ); - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/mobile_ai_prompt_input.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/mobile_ai_prompt_input.dart deleted file mode 100644 index 7dbda11bfb..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/mobile_ai_prompt_input.dart +++ /dev/null @@ -1,321 +0,0 @@ -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/ai_chat/application/ai_prompt_input_bloc.dart'; -import 'package:appflowy/plugins/ai_chat/application/chat_input_control_cubit.dart'; -import 'package:appflowy/workspace/application/view/view_ext.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:extended_text_field/extended_text_field.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -import '../layout_define.dart'; -import 'ai_prompt_buttons.dart'; -import 'chat_input_file.dart'; -import 'chat_input_span.dart'; -import 'chat_mention_page_bottom_sheet.dart'; -import 'select_sources_bottom_sheet.dart'; - -class MobileAIPromptInput extends StatefulWidget { - const MobileAIPromptInput({ - super.key, - required this.chatId, - required this.isStreaming, - required this.onStopStreaming, - required this.onSubmitted, - required this.onUpdateSelectedSources, - }); - - final String chatId; - final bool isStreaming; - final void Function() onStopStreaming; - final void Function(String, Map) onSubmitted; - final void Function(List) onUpdateSelectedSources; - - @override - State createState() => _MobileAIPromptInputState(); -} - -class _MobileAIPromptInputState extends State { - final inputControlCubit = ChatInputControlCubit(); - final focusNode = FocusNode(); - final textController = TextEditingController(); - - late SendButtonState sendButtonState; - - @override - void initState() { - super.initState(); - - textController.addListener(handleTextControllerChange); - // focusNode.onKeyEvent = handleKeyEvent; - - WidgetsBinding.instance.addPostFrameCallback((_) { - focusNode.requestFocus(); - }); - - updateSendButtonState(); - } - - @override - void didUpdateWidget(covariant oldWidget) { - updateSendButtonState(); - super.didUpdateWidget(oldWidget); - } - - @override - void dispose() { - focusNode.dispose(); - textController.dispose(); - inputControlCubit.close(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Hero( - tag: "ai_chat_prompt", - child: BlocProvider.value( - value: inputControlCubit, - child: BlocListener( - listener: (context, state) { - state.maybeWhen( - updateSelectedViews: (selectedViews) { - context.read().add( - AIPromptInputEvent.updateMentionedViews(selectedViews), - ); - }, - orElse: () {}, - ); - }, - child: DecoratedBox( - decoration: BoxDecoration( - border: Border( - top: BorderSide(color: Theme.of(context).colorScheme.outline), - ), - color: Theme.of(context).colorScheme.surface, - boxShadow: const [ - BoxShadow( - blurRadius: 4.0, - offset: Offset(0, -2), - color: Color.fromRGBO(0, 0, 0, 0.05), - ), - ], - borderRadius: MobileAIPromptSizes.promptFrameRadius, - ), - child: Column( - children: [ - ConstrainedBox( - constraints: BoxConstraints( - maxHeight: - MobileAIPromptSizes.attachedFilesBarPadding.vertical + - MobileAIPromptSizes.attachedFilesPreviewHeight, - ), - child: ChatInputFile( - chatId: widget.chatId, - onDeleted: (file) => context - .read() - .add(AIPromptInputEvent.removeFile(file)), - ), - ), - Container( - constraints: const BoxConstraints( - minHeight: MobileAIPromptSizes.textFieldMinHeight, - maxHeight: 220, - ), - child: IntrinsicHeight( - child: Row( - children: [ - const HSpace(8.0), - leadingButtons(context), - Expanded( - child: inputTextField(context), - ), - sendButton(), - const HSpace(12.0), - ], - ), - ), - ), - ], - ), - ), - ), - ), - ); - } - - void updateSendButtonState() { - if (widget.isStreaming) { - sendButtonState = SendButtonState.streaming; - } else if (textController.text.trim().isEmpty) { - sendButtonState = SendButtonState.disabled; - } else { - sendButtonState = SendButtonState.enabled; - } - } - - void handleSendPressed() { - if (widget.isStreaming) { - return; - } - final trimmedText = inputControlCubit.formatIntputText( - textController.text.trim(), - ); - textController.clear(); - if (trimmedText.isEmpty) { - return; - } - - // get the attached files and mentioned pages - final metadata = context.read().consumeMetadata(); - - widget.onSubmitted(trimmedText, metadata); - } - - void handleTextControllerChange() { - if (textController.value.isComposingRangeValid) { - return; - } - // inputControlCubit.updateInputText(textController.text); - setState(() => updateSendButtonState()); - } - - // KeyEventResult handleKeyEvent(FocusNode node, KeyEvent event) { - // if (event.character == '@') { - // WidgetsBinding.instance.addPostFrameCallback((_) { - // mentionPage(context); - // }); - // } - // return KeyEventResult.ignored; - // } - - Future mentionPage(BuildContext context) async { - // if the focus node is on focus, unfocus it for better animation - // otherwise, the page sheet animation will be blocked by the keyboard - inputControlCubit.refreshViews(); - inputControlCubit.startSearching(textController.value); - if (focusNode.hasFocus) { - focusNode.unfocus(); - await Future.delayed(const Duration(milliseconds: 100)); - } - - if (context.mounted) { - final selectedView = await showPageSelectorSheet( - context, - filter: (view) => - !view.isSpace && - view.layout.isDocumentView && - view.parentViewId != view.id && - !inputControlCubit.selectedViewIds.contains(view.id), - ); - if (selectedView != null) { - final newText = textController.text.replaceRange( - inputControlCubit.filterStartPosition, - inputControlCubit.filterStartPosition, - selectedView.id, - ); - textController.value = TextEditingValue( - text: newText, - selection: TextSelection.collapsed( - offset: - textController.selection.baseOffset + selectedView.id.length, - affinity: TextAffinity.upstream, - ), - ); - - inputControlCubit.selectPage(selectedView); - } - focusNode.requestFocus(); - inputControlCubit.reset(); - } - } - - Widget inputTextField(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - return ExtendedTextField( - controller: textController, - focusNode: focusNode, - decoration: InputDecoration( - border: InputBorder.none, - enabledBorder: InputBorder.none, - focusedBorder: InputBorder.none, - contentPadding: MobileAIPromptSizes.textFieldContentPadding, - hintText: switch (state.aiType) { - AIType.appflowyAI => LocaleKeys.chat_inputMessageHint.tr(), - AIType.localAI => LocaleKeys.chat_inputLocalAIMessageHint.tr() - }, - isCollapsed: true, - isDense: true, - ), - keyboardType: TextInputType.multiline, - textCapitalization: TextCapitalization.sentences, - minLines: 1, - maxLines: null, - style: Theme.of(context).textTheme.bodyMedium, - specialTextSpanBuilder: ChatInputTextSpanBuilder( - inputControlCubit: inputControlCubit, - specialTextStyle: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context).colorScheme.primary, - fontWeight: FontWeight.w600, - ), - ), - ); - }, - ); - } - - Widget leadingButtons(BuildContext context) { - return Container( - alignment: Alignment.bottomCenter, - padding: const EdgeInsets.only(bottom: 8.0), - child: _LeadingActions( - textController: textController, - // onMention: () { - // textController.text += '@'; - // if (!focusNode.hasFocus) { - // focusNode.requestFocus(); - // } - // WidgetsBinding.instance.addPostFrameCallback((_) { - // mentionPage(context); - // }); - // }, - onUpdateSelectedSources: widget.onUpdateSelectedSources, - ), - ); - } - - Widget sendButton() { - return Container( - alignment: Alignment.bottomCenter, - padding: const EdgeInsets.only(bottom: 8.0), - child: PromptInputSendButton( - buttonSize: MobileAIPromptSizes.sendButtonSize, - iconSize: MobileAIPromptSizes.sendButtonSize, - onSendPressed: handleSendPressed, - onStopStreaming: widget.onStopStreaming, - state: sendButtonState, - ), - ); - } -} - -class _LeadingActions extends StatelessWidget { - const _LeadingActions({ - required this.textController, - required this.onUpdateSelectedSources, - }); - - final TextEditingController textController; - final void Function(List) onUpdateSelectedSources; - - @override - Widget build(BuildContext context) { - return Material( - color: Theme.of(context).colorScheme.surface, - child: PromptInputMobileSelectSourcesButton( - onUpdateSelectedSources: onUpdateSelectedSources, - ), - ); - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/mobile_chat_input.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/mobile_chat_input.dart new file mode 100644 index 0000000000..76d1af7134 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/mobile_chat_input.dart @@ -0,0 +1,369 @@ +import 'package:appflowy/ai/ai.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/ai_chat/application/chat_input_control_cubit.dart'; +import 'package:appflowy/util/theme_extension.dart'; +import 'package:appflowy/workspace/application/view/view_ext.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:extended_text_field/extended_text_field.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class MobileChatInput extends StatefulWidget { + const MobileChatInput({ + super.key, + required this.isStreaming, + required this.onStopStreaming, + required this.onSubmitted, + required this.selectedSourcesNotifier, + required this.onUpdateSelectedSources, + }); + + final bool isStreaming; + final void Function() onStopStreaming; + final ValueNotifier> selectedSourcesNotifier; + final void Function(String, PredefinedFormat?, Map) + onSubmitted; + final void Function(List) onUpdateSelectedSources; + + @override + State createState() => _MobileChatInputState(); +} + +class _MobileChatInputState extends State { + final inputControlCubit = ChatInputControlCubit(); + final focusNode = FocusNode(); + final textController = TextEditingController(); + + late SendButtonState sendButtonState; + + @override + void initState() { + super.initState(); + + textController.addListener(handleTextControllerChanged); + // focusNode.onKeyEvent = handleKeyEvent; + + WidgetsBinding.instance.addPostFrameCallback((_) { + focusNode.requestFocus(); + }); + + updateSendButtonState(); + } + + @override + void didUpdateWidget(covariant oldWidget) { + updateSendButtonState(); + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + focusNode.dispose(); + textController.dispose(); + inputControlCubit.close(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Hero( + tag: "ai_chat_prompt", + child: BlocProvider.value( + value: inputControlCubit, + child: BlocListener( + listener: (context, state) { + state.maybeWhen( + updateSelectedViews: (selectedViews) { + context.read().add( + AIPromptInputEvent.updateMentionedViews(selectedViews), + ); + }, + orElse: () {}, + ); + }, + child: DecoratedBox( + decoration: BoxDecoration( + border: Border( + top: BorderSide(color: Theme.of(context).colorScheme.outline), + ), + color: Theme.of(context).colorScheme.surface, + boxShadow: const [ + BoxShadow( + blurRadius: 4.0, + offset: Offset(0, -2), + color: Color.fromRGBO(0, 0, 0, 0.05), + ), + ], + borderRadius: + const BorderRadius.vertical(top: Radius.circular(8.0)), + ), + child: BlocBuilder( + builder: (context, state) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ConstrainedBox( + constraints: BoxConstraints( + maxHeight: MobileAIPromptSizes + .attachedFilesBarPadding.vertical + + MobileAIPromptSizes.attachedFilesPreviewHeight, + ), + child: PromptInputFile( + onDeleted: (file) => context + .read() + .add(AIPromptInputEvent.removeFile(file)), + ), + ), + if (state.showPredefinedFormats) + TextFieldTapRegion( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: ChangeFormatBar( + predefinedFormat: state.predefinedFormat, + spacing: 8.0, + onSelectPredefinedFormat: (format) => + context.read().add( + AIPromptInputEvent.updatePredefinedFormat( + format, + ), + ), + ), + ), + ) + else + const VSpace(8.0), + inputTextField(context), + TextFieldTapRegion( + child: Container( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + children: [ + const HSpace(8.0), + leadingButtons( + context, + state.showPredefinedFormats, + ), + const Spacer(), + sendButton(), + const HSpace(12.0), + ], + ), + ), + ), + ], + ); + }, + ), + ), + ), + ), + ); + } + + void updateSendButtonState() { + if (widget.isStreaming) { + sendButtonState = SendButtonState.streaming; + } else if (textController.text.trim().isEmpty) { + sendButtonState = SendButtonState.disabled; + } else { + sendButtonState = SendButtonState.enabled; + } + } + + void handleSendPressed() { + if (widget.isStreaming) { + return; + } + final trimmedText = inputControlCubit.formatIntputText( + textController.text.trim(), + ); + textController.clear(); + if (trimmedText.isEmpty) { + return; + } + + // get the attached files and mentioned pages + final metadata = context.read().consumeMetadata(); + + final bloc = context.read(); + final showPredefinedFormats = bloc.state.showPredefinedFormats; + final predefinedFormat = bloc.state.predefinedFormat; + + widget.onSubmitted( + trimmedText, + showPredefinedFormats ? predefinedFormat : null, + metadata, + ); + } + + void handleTextControllerChanged() { + if (textController.value.isComposingRangeValid) { + return; + } + // inputControlCubit.updateInputText(textController.text); + setState(() => updateSendButtonState()); + } + + // KeyEventResult handleKeyEvent(FocusNode node, KeyEvent event) { + // if (event.character == '@') { + // WidgetsBinding.instance.addPostFrameCallback((_) { + // mentionPage(context); + // }); + // } + // return KeyEventResult.ignored; + // } + + Future mentionPage(BuildContext context) async { + // if the focus node is on focus, unfocus it for better animation + // otherwise, the page sheet animation will be blocked by the keyboard + inputControlCubit.refreshViews(); + inputControlCubit.startSearching(textController.value); + if (focusNode.hasFocus) { + focusNode.unfocus(); + await Future.delayed(const Duration(milliseconds: 100)); + } + + if (context.mounted) { + final selectedView = await showPageSelectorSheet( + context, + filter: (view) => + !view.isSpace && + view.layout.isDocumentView && + view.parentViewId != view.id && + !inputControlCubit.selectedViewIds.contains(view.id), + ); + if (selectedView != null) { + final newText = textController.text.replaceRange( + inputControlCubit.filterStartPosition, + inputControlCubit.filterStartPosition, + selectedView.id, + ); + textController.value = TextEditingValue( + text: newText, + selection: TextSelection.collapsed( + offset: + textController.selection.baseOffset + selectedView.id.length, + affinity: TextAffinity.upstream, + ), + ); + + inputControlCubit.selectPage(selectedView); + } + focusNode.requestFocus(); + inputControlCubit.reset(); + } + } + + Widget inputTextField(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return ExtendedTextField( + controller: textController, + focusNode: focusNode, + textAlignVertical: TextAlignVertical.center, + decoration: InputDecoration( + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + contentPadding: MobileAIPromptSizes.textFieldContentPadding, + hintText: switch (state.aiType) { + AiType.cloud => LocaleKeys.chat_inputMessageHint.tr(), + AiType.local => LocaleKeys.chat_inputLocalAIMessageHint.tr() + }, + hintStyle: inputHintTextStyle(context), + isCollapsed: true, + isDense: true, + ), + keyboardType: TextInputType.multiline, + textCapitalization: TextCapitalization.sentences, + minLines: 1, + maxLines: null, + style: + Theme.of(context).textTheme.bodyMedium?.copyWith(height: 20 / 14), + specialTextSpanBuilder: PromptInputTextSpanBuilder( + inputControlCubit: inputControlCubit, + specialTextStyle: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).colorScheme.primary, + fontWeight: FontWeight.w600, + ), + ), + onTapOutside: (_) => focusNode.unfocus(), + ); + }, + ); + } + + TextStyle? inputHintTextStyle(BuildContext context) { + return Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).isLightMode + ? const Color(0xFFBDC2C8) + : const Color(0xFF3C3E51), + ); + } + + Widget leadingButtons(BuildContext context, bool showPredefinedFormats) { + return _LeadingActions( + // onMention: () { + // textController.text += '@'; + // if (!focusNode.hasFocus) { + // focusNode.requestFocus(); + // } + // WidgetsBinding.instance.addPostFrameCallback((_) { + // mentionPage(context); + // }); + // }, + showPredefinedFormats: showPredefinedFormats, + onTogglePredefinedFormatSection: () { + context + .read() + .add(AIPromptInputEvent.toggleShowPredefinedFormat()); + }, + selectedSourcesNotifier: widget.selectedSourcesNotifier, + onUpdateSelectedSources: widget.onUpdateSelectedSources, + ); + } + + Widget sendButton() { + return PromptInputSendButton( + state: sendButtonState, + onSendPressed: handleSendPressed, + onStopStreaming: widget.onStopStreaming, + ); + } +} + +class _LeadingActions extends StatelessWidget { + const _LeadingActions({ + required this.showPredefinedFormats, + required this.onTogglePredefinedFormatSection, + required this.selectedSourcesNotifier, + required this.onUpdateSelectedSources, + }); + + final bool showPredefinedFormats; + final void Function() onTogglePredefinedFormatSection; + final ValueNotifier> selectedSourcesNotifier; + final void Function(List) onUpdateSelectedSources; + + @override + Widget build(BuildContext context) { + return Material( + color: Theme.of(context).colorScheme.surface, + child: SeparatedRow( + mainAxisSize: MainAxisSize.min, + separatorBuilder: () => const HSpace(4.0), + children: [ + PromptInputMobileSelectSourcesButton( + selectedSourcesNotifier: selectedSourcesNotifier, + onUpdateSelectedSources: onUpdateSelectedSources, + ), + PromptInputMobileToggleFormatButton( + showFormatBar: showPredefinedFormats, + onTap: onTogglePredefinedFormatSection, + ), + ], + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/select_sources_bottom_sheet.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/select_sources_bottom_sheet.dart deleted file mode 100644 index 6e464ef760..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/select_sources_bottom_sheet.dart +++ /dev/null @@ -1,259 +0,0 @@ -import 'package:appflowy/generated/flowy_svgs.g.dart'; -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/mobile/presentation/base/flowy_search_text_field.dart'; -import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; -import 'package:appflowy/plugins/ai_chat/application/chat_bloc.dart'; -import 'package:appflowy/plugins/ai_chat/application/chat_select_sources_cubit.dart'; -import 'package:appflowy/plugins/base/drag_handler.dart'; -import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart'; -import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; -import 'package:appflowy/workspace/application/view/view_ext.dart'; -import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart'; -import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -import 'select_sources_menu.dart'; - -class PromptInputMobileSelectSourcesButton extends StatefulWidget { - const PromptInputMobileSelectSourcesButton({ - super.key, - required this.onUpdateSelectedSources, - }); - - final void Function(List) onUpdateSelectedSources; - - @override - State createState() => - _PromptInputMobileSelectSourcesButtonState(); -} - -class _PromptInputMobileSelectSourcesButtonState - extends State { - late final cubit = ChatSettingsCubit(); - - @override - void initState() { - super.initState(); - WidgetsBinding.instance.addPostFrameCallback((_) { - cubit.updateSelectedSources( - context.read().state.selectedSourceIds, - ); - }); - } - - @override - void dispose() { - cubit.close(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - final userProfile = context.read().userProfile; - final workspaceId = state.currentWorkspace?.workspaceId ?? ''; - return MultiBlocProvider( - providers: [ - BlocProvider( - key: ValueKey(workspaceId), - create: (context) => SpaceBloc( - userProfile: userProfile, - workspaceId: workspaceId, - )..add(const SpaceEvent.initial(openFirstPage: false)), - ), - BlocProvider.value( - value: cubit, - ), - ], - child: BlocSelector>( - selector: (state) => state.spaces, - builder: (context, spaces) { - return BlocListener( - listener: (context, state) { - cubit - ..updateSelectedSources(state.selectedSourceIds) - ..updateSelectedStatus(); - }, - child: FlowyButton( - margin: const EdgeInsetsDirectional.fromSTEB(4, 6, 2, 6), - expandText: false, - text: Row( - mainAxisSize: MainAxisSize.min, - children: [ - FlowySvg( - FlowySvgs.ai_page_s, - color: Theme.of(context).iconTheme.color, - size: const Size.square(20.0), - ), - FlowySvg( - FlowySvgs.ai_source_drop_down_s, - color: Theme.of(context).hintColor, - size: const Size.square(10), - ), - ], - ), - onTap: () async { - context.read().refreshSources(spaces); - await showMobileBottomSheet( - context, - backgroundColor: Theme.of(context).colorScheme.surface, - maxChildSize: 0.98, - enableDraggableScrollable: true, - scrollableWidgetBuilder: (_, scrollController) { - return Expanded( - child: BlocProvider.value( - value: cubit, - child: _MobileSelectSourcesSheetBody( - scrollController: scrollController, - ), - ), - ); - }, - builder: (context) => const SizedBox.shrink(), - ); - if (context.mounted) { - widget.onUpdateSelectedSources(cubit.selectedSourceIds); - } - }, - ), - ); - }, - ), - ); - }, - ); - } -} - -class _MobileSelectSourcesSheetBody extends StatefulWidget { - const _MobileSelectSourcesSheetBody({ - required this.scrollController, - }); - - final ScrollController scrollController; - - @override - State<_MobileSelectSourcesSheetBody> createState() => - _MobileSelectSourcesSheetBodyState(); -} - -class _MobileSelectSourcesSheetBodyState - extends State<_MobileSelectSourcesSheetBody> { - final textController = TextEditingController(); - - @override - void dispose() { - textController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return CustomScrollView( - controller: widget.scrollController, - shrinkWrap: true, - slivers: [ - SliverPersistentHeader( - pinned: true, - delegate: _Header( - child: ColoredBox( - color: Theme.of(context).cardColor, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const DragHandle(), - SizedBox( - height: 44.0, - child: Center( - child: FlowyText.medium( - LocaleKeys.chat_selectSources.tr(), - fontSize: 16.0, - ), - ), - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 8.0, - ), - child: SizedBox( - height: 44.0, - child: FlowySearchTextField( - controller: textController, - onChanged: (value) => context - .read() - .updateFilter(value), - ), - ), - ), - const Divider(height: 0.5, thickness: 0.5), - ], - ), - ), - ), - ), - BlocBuilder( - builder: (context, state) { - final sources = state.visibleSources - .where((e) => e.ignoreStatus != IgnoreViewType.hide); - return SliverList( - delegate: SliverChildBuilderDelegate( - childCount: sources.length, - (context, index) { - final source = sources.elementAt(index); - return ChatSourceTreeItem( - key: ValueKey( - 'visible_select_sources_tree_item_${source.view.id}', - ), - chatSource: source, - level: 0, - isDescendentOfSpace: source.view.isSpace, - isSelectedSection: false, - onSelected: (chatSource) { - context - .read() - .toggleSelectedStatus(chatSource); - }, - height: 40.0, - ); - }, - ), - ); - }, - ), - ], - ); - } -} - -class _Header extends SliverPersistentHeaderDelegate { - const _Header({ - required this.child, - }); - - final Widget child; - - @override - Widget build( - BuildContext context, - double shrinkOffset, - bool overlapsContent, - ) { - return child; - } - - @override - double get maxExtent => 120.5; - - @override - double get minExtent => 120.5; - - @override - bool shouldRebuild(covariant SliverPersistentHeaderDelegate oldDelegate) { - return false; - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/select_sources_menu.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/select_sources_menu.dart deleted file mode 100644 index 72eb18552e..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_input/select_sources_menu.dart +++ /dev/null @@ -1,526 +0,0 @@ -import 'dart:math'; - -import 'package:appflowy/generated/flowy_svgs.g.dart'; -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/ai_chat/application/chat_bloc.dart'; -import 'package:appflowy/plugins/ai_chat/application/chat_select_sources_cubit.dart'; -import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart'; -import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; -import 'package:appflowy/workspace/application/view/view_ext.dart'; -import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart'; -import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart'; -import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; -import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/theme_extension.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/style_widget/hover.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -import '../layout_define.dart'; -import 'chat_mention_page_menu.dart'; - -class PromptInputDesktopSelectSourcesButton extends StatefulWidget { - const PromptInputDesktopSelectSourcesButton({ - super.key, - required this.onUpdateSelectedSources, - }); - - final void Function(List) onUpdateSelectedSources; - - @override - State createState() => - _PromptInputDesktopSelectSourcesButtonState(); -} - -class _PromptInputDesktopSelectSourcesButtonState - extends State { - late final cubit = ChatSettingsCubit(); - final popoverController = PopoverController(); - - @override - void initState() { - super.initState(); - WidgetsBinding.instance.addPostFrameCallback((_) { - cubit.updateSelectedSources( - context.read().state.selectedSourceIds, - ); - }); - } - - @override - void dispose() { - cubit.close(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final userWorkspaceBloc = context.read(); - final userProfile = userWorkspaceBloc.userProfile; - final workspaceId = - userWorkspaceBloc.state.currentWorkspace?.workspaceId ?? ''; - - return MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => SpaceBloc( - userProfile: userProfile, - workspaceId: workspaceId, - )..add(const SpaceEvent.initial(openFirstPage: false)), - ), - BlocProvider.value( - value: cubit, - ), - ], - child: BlocSelector>( - selector: (state) => state.spaces, - builder: (context, spaces) { - return BlocListener( - listener: (context, state) { - cubit - ..updateSelectedSources(state.selectedSourceIds) - ..updateSelectedStatus(); - }, - child: AppFlowyPopover( - constraints: BoxConstraints.loose(const Size(320, 380)), - offset: const Offset(0.0, -10.0), - direction: PopoverDirection.topWithCenterAligned, - margin: EdgeInsets.zero, - controller: popoverController, - onOpen: () { - context.read().refreshSources(spaces); - }, - onClose: () { - widget.onUpdateSelectedSources(cubit.selectedSourceIds); - context.read().refreshSources(spaces); - }, - popupBuilder: (_) { - return BlocProvider.value( - value: context.read(), - child: const _PopoverContent(), - ); - }, - child: _IndicatorButton( - onTap: () => popoverController.show(), - ), - ), - ); - }, - ), - ); - } -} - -class _IndicatorButton extends StatelessWidget { - const _IndicatorButton({ - required this.onTap, - }); - - final VoidCallback onTap; - - @override - Widget build(BuildContext context) { - return GestureDetector( - onTap: onTap, - behavior: HitTestBehavior.opaque, - child: SizedBox( - height: DesktopAIPromptSizes.actionBarButtonSize, - child: FlowyHover( - style: const HoverStyle( - borderRadius: BorderRadius.all(Radius.circular(8)), - ), - child: Padding( - padding: const EdgeInsetsDirectional.fromSTEB(6, 6, 4, 6), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - FlowySvg( - FlowySvgs.ai_page_s, - color: Theme.of(context).iconTheme.color, - ), - const HSpace(2.0), - BlocBuilder( - builder: (context, state) { - return FlowyText( - state.selectedSourceIds.length.toString(), - fontSize: 14, - figmaLineHeight: 16, - color: Theme.of(context).hintColor, - ); - }, - ), - const HSpace(2.0), - FlowySvg( - FlowySvgs.ai_source_drop_down_s, - color: Theme.of(context).hintColor, - size: const Size.square(10), - ), - ], - ), - ), - ), - ), - ); - } -} - -class _PopoverContent extends StatelessWidget { - const _PopoverContent(); - - @override - Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(8, 12, 8, 8), - child: SpaceSearchField( - width: 600, - onSearch: (context, value) => - context.read().updateFilter(value), - ), - ), - _buildDivider(), - Flexible( - child: ListView( - shrinkWrap: true, - padding: const EdgeInsets.fromLTRB(8, 4, 8, 12), - children: [ - ..._buildSelectedSources(context, state), - if (state.selectedSources.isNotEmpty && - state.visibleSources.isNotEmpty) - Padding( - padding: const EdgeInsets.symmetric(vertical: 4.0), - child: _buildDivider(), - ), - ..._buildVisibleSources(context, state), - ], - ), - ), - ], - ); - }, - ); - } - - Widget _buildDivider() { - return const Divider( - height: 1.0, - thickness: 1.0, - indent: 8.0, - endIndent: 8.0, - ); - } - - Iterable _buildSelectedSources( - BuildContext context, - ChatSettingsState state, - ) { - return state.selectedSources - .where((e) => e.ignoreStatus != IgnoreViewType.hide) - .map( - (e) => ChatSourceTreeItem( - key: ValueKey( - 'selected_select_sources_tree_item_${e.view.id}', - ), - chatSource: e, - level: 0, - isDescendentOfSpace: e.view.isSpace, - isSelectedSection: true, - onSelected: (chatSource) { - context - .read() - .toggleSelectedStatus(chatSource); - }, - height: 30.0, - ), - ); - } - - Iterable _buildVisibleSources( - BuildContext context, - ChatSettingsState state, - ) { - return state.visibleSources - .where((e) => e.ignoreStatus != IgnoreViewType.hide) - .map( - (e) => ChatSourceTreeItem( - key: ValueKey( - 'visible_select_sources_tree_item_${e.view.id}', - ), - chatSource: e, - level: 0, - isDescendentOfSpace: e.view.isSpace, - isSelectedSection: false, - onSelected: (chatSource) { - context - .read() - .toggleSelectedStatus(chatSource); - }, - height: 30.0, - ), - ); - } -} - -class ChatSourceTreeItem extends StatefulWidget { - const ChatSourceTreeItem({ - super.key, - required this.chatSource, - required this.level, - required this.isDescendentOfSpace, - required this.isSelectedSection, - required this.onSelected, - required this.height, - this.showCheckbox = true, - }); - - final ChatSource chatSource; - - /// nested level of the view item - final int level; - - final bool isDescendentOfSpace; - - final bool isSelectedSection; - - final void Function(ChatSource chatSource) onSelected; - - final double height; - - final bool showCheckbox; - - @override - State createState() => _ChatSourceTreeItemState(); -} - -class _ChatSourceTreeItemState extends State { - @override - Widget build(BuildContext context) { - final child = SizedBox( - height: widget.height, - child: ChatSourceTreeItemInner( - chatSource: widget.chatSource, - level: widget.level, - isDescendentOfSpace: widget.isDescendentOfSpace, - isSelectedSection: widget.isSelectedSection, - showCheckbox: widget.showCheckbox, - onSelected: widget.onSelected, - ), - ); - - final disabledEnabledChild = - widget.chatSource.ignoreStatus == IgnoreViewType.disable - ? FlowyTooltip( - message: widget.showCheckbox - ? switch (widget.chatSource.view.layout) { - ViewLayoutPB.Document => - LocaleKeys.chat_sourcesLimitReached.tr(), - _ => LocaleKeys.chat_sourceUnsupported.tr(), - } - : "", - child: Opacity( - opacity: 0.5, - child: MouseRegion( - cursor: SystemMouseCursors.forbidden, - child: IgnorePointer(child: child), - ), - ), - ) - : child; - - return ValueListenableBuilder( - valueListenable: widget.chatSource.isExpandedNotifier, - builder: (context, isExpanded, child) { - // filter the child views that should be ignored - final childViews = widget.chatSource.children - .where((e) => e.ignoreStatus != IgnoreViewType.hide) - .toList(); - - if (!isExpanded || childViews.isEmpty) { - return disabledEnabledChild; - } - - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - disabledEnabledChild, - ...childViews.map( - (childSource) => ChatSourceTreeItem( - key: ValueKey( - 'select_sources_tree_item_${childSource.view.id}', - ), - chatSource: childSource, - level: widget.level + 1, - isDescendentOfSpace: widget.isDescendentOfSpace, - isSelectedSection: widget.isSelectedSection, - onSelected: widget.onSelected, - height: widget.height, - showCheckbox: widget.showCheckbox, - ), - ), - ], - ); - }, - ); - } -} - -class ChatSourceTreeItemInner extends StatelessWidget { - const ChatSourceTreeItemInner({ - super.key, - required this.chatSource, - required this.level, - required this.isDescendentOfSpace, - required this.isSelectedSection, - required this.showCheckbox, - this.onSelected, - }); - - final ChatSource chatSource; - final int level; - final bool isDescendentOfSpace; - final bool isSelectedSection; - final bool showCheckbox; - final void Function(ChatSource)? onSelected; - - @override - Widget build(BuildContext context) { - return FlowyHover( - cursor: isSelectedSection ? SystemMouseCursors.basic : null, - style: HoverStyle( - hoverColor: isSelectedSection - ? Colors.transparent - : AFThemeExtension.of(context).lightGreyHover, - ), - child: GestureDetector( - behavior: HitTestBehavior.translucent, - onTap: () { - if (!isSelectedSection) { - onSelected?.call(chatSource); - } - }, - child: Row( - children: [ - const HSpace(4.0), - HSpace(max(20.0 * level - (isDescendentOfSpace ? 2 : 0), 0)), - // builds the >, ^ or · button - ToggleIsExpandedButton( - chatSource: chatSource, - isSelectedSection: isSelectedSection, - ), - const HSpace(2.0), - // checkbox - if (!chatSource.view.isSpace && showCheckbox) ...[ - SourceSelectedStatusCheckbox( - chatSource: chatSource, - ), - const HSpace(4.0), - ], - // icon - MentionViewIcon( - view: chatSource.view, - ), - const HSpace(6.0), - // title - Expanded( - child: FlowyText( - chatSource.view.nameOrDefault, - overflow: TextOverflow.ellipsis, - fontSize: 14.0, - figmaLineHeight: 18.0, - ), - ), - ], - ), - ), - ); - } -} - -class ToggleIsExpandedButton extends StatelessWidget { - const ToggleIsExpandedButton({ - super.key, - required this.chatSource, - required this.isSelectedSection, - }); - - final ChatSource chatSource; - final bool isSelectedSection; - - @override - Widget build(BuildContext context) { - if (isReferencedDatabaseView(chatSource.view, chatSource.parentView)) { - return const _DotIconWidget(); - } - - if (chatSource.children.isEmpty) { - return const SizedBox.square(dimension: 16.0); - } - - return FlowyHover( - child: GestureDetector( - child: ValueListenableBuilder( - valueListenable: chatSource.isExpandedNotifier, - builder: (context, value, _) => FlowySvg( - value - ? FlowySvgs.view_item_expand_s - : FlowySvgs.view_item_unexpand_s, - size: const Size.square(16.0), - ), - ), - onTap: () => context - .read() - .toggleIsExpanded(chatSource, isSelectedSection), - ), - ); - } -} - -class _DotIconWidget extends StatelessWidget { - const _DotIconWidget(); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(6.0), - child: Container( - width: 4, - height: 4, - decoration: BoxDecoration( - color: Theme.of(context).iconTheme.color, - borderRadius: BorderRadius.circular(2), - ), - ), - ); - } -} - -class SourceSelectedStatusCheckbox extends StatelessWidget { - const SourceSelectedStatusCheckbox({ - super.key, - required this.chatSource, - }); - - final ChatSource chatSource; - - @override - Widget build(BuildContext context) { - return ValueListenableBuilder( - valueListenable: chatSource.selectedStatusNotifier, - builder: (context, selectedStatus, _) => FlowySvg( - switch (selectedStatus) { - SourceSelectedStatus.unselected => FlowySvgs.uncheck_s, - SourceSelectedStatus.selected => FlowySvgs.check_filled_s, - SourceSelectedStatus.partiallySelected => FlowySvgs.check_partial_s, - }, - size: const Size.square(18.0), - blendMode: null, - ), - ); - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_message_selector_banner.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_message_selector_banner.dart new file mode 100644 index 0000000000..790a3fac3c --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_message_selector_banner.dart @@ -0,0 +1,314 @@ +import 'dart:async'; + +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/ai_chat/application/chat_edit_document_service.dart'; +import 'package:appflowy/plugins/ai_chat/application/chat_select_message_bloc.dart'; +import 'package:appflowy/plugins/ai_chat/application/chat_select_sources_cubit.dart'; +import 'package:appflowy/plugins/document/application/prelude.dart'; +import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart'; +import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart'; +import 'package:appflowy/workspace/application/user/prelude.dart'; +import 'package:appflowy/workspace/application/view/prelude.dart'; +import 'package:appflowy/workspace/application/view/view_ext.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_result/appflowy_result.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_chat_core/flutter_chat_core.dart'; + +import 'message/ai_message_action_bar.dart'; +import 'message/message_util.dart'; + +class ChatMessageSelectorBanner extends StatelessWidget { + const ChatMessageSelectorBanner({ + super.key, + required this.view, + this.allMessages = const [], + }); + + final ViewPB view; + final List allMessages; + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (!state.isSelectingMessages) { + return const SizedBox.shrink(); + } + + final selectedAmount = state.selectedMessages.length; + final totalAmount = allMessages.length; + final allSelected = selectedAmount == totalAmount; + + return Container( + height: 48, + color: const Color(0xFF00BCF0), + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + children: [ + GestureDetector( + onTap: () { + if (selectedAmount > 0) { + _unselectAllMessages(context); + } else { + _selectAllMessages(context); + } + }, + child: FlowySvg( + allSelected + ? FlowySvgs.checkbox_ai_selected_s + : selectedAmount > 0 + ? FlowySvgs.checkbox_ai_minus_s + : FlowySvgs.checkbox_ai_empty_s, + blendMode: BlendMode.dstIn, + size: const Size.square(18), + ), + ), + const HSpace(8), + Expanded( + child: FlowyText.semibold( + allSelected + ? LocaleKeys.chat_selectBanner_allSelected.tr() + : selectedAmount > 0 + ? LocaleKeys.chat_selectBanner_nSelected + .tr(args: [selectedAmount.toString()]) + : LocaleKeys.chat_selectBanner_selectMessages.tr(), + figmaLineHeight: 16, + color: Colors.white, + ), + ), + SaveToPageButton( + view: view, + ), + const HSpace(8), + MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () => context.read().add( + const ChatSelectMessageEvent.toggleSelectingMessages(), + ), + child: const FlowySvg( + FlowySvgs.close_m, + color: Colors.white, + size: Size.square(24), + ), + ), + ), + ], + ), + ); + }, + ); + } + + void _selectAllMessages(BuildContext context) => context + .read() + .add(ChatSelectMessageEvent.selectAllMessages(allMessages)); + + void _unselectAllMessages(BuildContext context) => context + .read() + .add(const ChatSelectMessageEvent.unselectAllMessages()); +} + +class SaveToPageButton extends StatefulWidget { + const SaveToPageButton({ + super.key, + required this.view, + }); + + final ViewPB view; + + @override + State createState() => _SaveToPageButtonState(); +} + +class _SaveToPageButtonState extends State { + final popoverController = PopoverController(); + + @override + Widget build(BuildContext context) { + final userWorkspaceBloc = context.read(); + final userProfile = userWorkspaceBloc.userProfile; + final workspaceId = + userWorkspaceBloc.state.currentWorkspace?.workspaceId ?? ''; + + return MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => SpaceBloc( + userProfile: userProfile, + workspaceId: workspaceId, + )..add(const SpaceEvent.initial(openFirstPage: false)), + ), + BlocProvider( + create: (context) => ChatSettingsCubit(hideDisabled: true), + ), + ], + child: BlocSelector( + selector: (state) => state.currentSpace, + builder: (context, spaceView) { + return AppFlowyPopover( + controller: popoverController, + triggerActions: PopoverTriggerFlags.none, + margin: EdgeInsets.zero, + offset: const Offset(0, 18), + direction: PopoverDirection.bottomWithRightAligned, + constraints: const BoxConstraints.tightFor(width: 300, height: 400), + child: buildButton(context, spaceView), + popupBuilder: (_) => buildPopover(context), + ); + }, + ), + ); + } + + Widget buildButton(BuildContext context, ViewPB? spaceView) { + return BlocBuilder( + builder: (context, state) { + final selectedAmount = state.selectedMessages.length; + + return Opacity( + opacity: selectedAmount == 0 ? 0.5 : 1, + child: FlowyTextButton( + LocaleKeys.chat_selectBanner_saveButton.tr(), + onPressed: selectedAmount == 0 + ? null + : () async { + final documentId = getOpenedDocumentId(); + if (documentId != null) { + await onAddToExistingPage(context, documentId); + await forceReload(documentId); + await Future.delayed(const Duration(milliseconds: 500)); + await updateSelection(documentId); + } else { + if (spaceView != null) { + context + .read() + .refreshSources([spaceView], spaceView); + } + popoverController.show(); + } + }, + fontColor: Colors.white, + borderColor: Colors.white, + fillColor: Colors.transparent, + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + vertical: 6.0, + ), + ), + ); + }, + ); + } + + Widget buildPopover(BuildContext context) { + return BlocProvider.value( + value: context.read(), + child: SaveToPagePopoverContent( + onAddToNewPage: (parentViewId) async { + await addMessageToNewPage(context, parentViewId); + popoverController.close(); + }, + onAddToExistingPage: (documentId) async { + final view = await onAddToExistingPage(context, documentId); + + if (context.mounted) { + openPageFromMessage(context, view); + } + await Future.delayed(const Duration(milliseconds: 500)); + await updateSelection(documentId); + popoverController.close(); + }, + ), + ); + } + + Future onAddToExistingPage( + BuildContext context, + String documentId, + ) async { + final bloc = context.read(); + + final selectedMessages = [ + ...bloc.state.selectedMessages.whereType(), + ]..sort((a, b) => a.createdAt.compareTo(b.createdAt)); + + await ChatEditDocumentService.addMessagesToPage( + documentId, + selectedMessages, + ); + await Future.delayed(const Duration(milliseconds: 500)); + final view = await ViewBackendService.getView(documentId).toNullable(); + if (context.mounted) { + showSaveMessageSuccessToast(context, view); + } + + bloc.add(const ChatSelectMessageEvent.reset()); + + return view; + } + + Future addMessageToNewPage( + BuildContext context, + String parentViewId, + ) async { + final bloc = context.read(); + + final selectedMessages = [ + ...bloc.state.selectedMessages.whereType(), + ]..sort((a, b) => a.createdAt.compareTo(b.createdAt)); + + final newView = await ChatEditDocumentService.saveMessagesToNewPage( + widget.view.nameOrDefault, + parentViewId, + selectedMessages, + ); + + if (context.mounted) { + showSaveMessageSuccessToast(context, newView); + openPageFromMessage(context, newView); + } + bloc.add(const ChatSelectMessageEvent.reset()); + } + + Future forceReload(String documentId) async { + final bloc = DocumentBloc.findOpen(documentId); + if (bloc == null) { + return; + } + await bloc.forceReloadDocumentState(); + } + + Future updateSelection(String documentId) async { + final bloc = DocumentBloc.findOpen(documentId); + if (bloc == null) { + return; + } + await bloc.forceReloadDocumentState(); + final editorState = bloc.state.editorState; + final lastNodePath = editorState?.getLastSelectable()?.$1.path; + if (editorState == null || lastNodePath == null) { + return; + } + unawaited( + editorState.updateSelectionWithReason( + Selection.collapsed(Position(path: lastNodePath)), + ), + ); + } + + String? getOpenedDocumentId() { + final pageManager = getIt().state.currentPageManager; + if (!pageManager.showSecondaryPluginNotifier.value) { + return null; + } + return pageManager.secondaryNotifier.plugin.id; + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_related_question.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_related_question.dart index fd937cc27f..2c09e77050 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_related_question.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_related_question.dart @@ -16,38 +16,40 @@ class RelatedQuestionList extends StatelessWidget { required this.relatedQuestions, }); - final Function(String) onQuestionSelected; + final void Function(String) onQuestionSelected; final List relatedQuestions; @override Widget build(BuildContext context) { - return ListView.separated( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: relatedQuestions.length + 1, - padding: - const EdgeInsets.only(bottom: 8.0) + AIChatUILayout.messageMargin, - separatorBuilder: (context, index) => const VSpace(4.0), - itemBuilder: (context, index) { - if (index == 0) { - return Padding( - padding: const EdgeInsets.only(bottom: 4.0), - child: FlowyText( - LocaleKeys.chat_relatedQuestion.tr(), - color: Theme.of(context).hintColor, - fontWeight: FontWeight.w600, - ), - ); - } else { - return Align( - alignment: AlignmentDirectional.centerStart, - child: RelatedQuestionItem( - question: relatedQuestions[index - 1], - onQuestionSelected: onQuestionSelected, - ), - ); - } - }, + return SelectionContainer.disabled( + child: ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: relatedQuestions.length + 1, + padding: + const EdgeInsets.only(bottom: 8.0) + AIChatUILayout.messageMargin, + separatorBuilder: (context, index) => const VSpace(4.0), + itemBuilder: (context, index) { + if (index == 0) { + return Padding( + padding: const EdgeInsets.only(bottom: 4.0), + child: FlowyText( + LocaleKeys.chat_relatedQuestion.tr(), + color: Theme.of(context).hintColor, + fontWeight: FontWeight.w600, + ), + ); + } else { + return Align( + alignment: AlignmentDirectional.centerStart, + child: RelatedQuestionItem( + question: relatedQuestions[index - 1], + onQuestionSelected: onQuestionSelected, + ), + ); + } + }, + ), ); } } @@ -70,6 +72,7 @@ class RelatedQuestionItem extends StatelessWidget { child: FlowyText( question, lineHeight: 1.4, + maxLines: 2, overflow: TextOverflow.ellipsis, ), ), diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_welcome_page.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_welcome_page.dart index 6c3dd4bd4d..30dc918f70 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_welcome_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/chat_welcome_page.dart @@ -46,7 +46,7 @@ class ChatWelcomePage extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ const FlowySvg( - FlowySvgs.flowy_logo_xl, + FlowySvgs.app_logo_xl, size: Size.square(32), blendMode: null, ), @@ -125,14 +125,14 @@ class WelcomeSampleQuestion extends StatelessWidget { spreadRadius: -2, color: isLightMode ? const Color(0x051F2329) - : Theme.of(context).shadowColor.withOpacity(0.02), + : Theme.of(context).shadowColor.withValues(alpha: 0.02), ), BoxShadow( offset: const Offset(0, 2), blurRadius: 4, color: isLightMode ? const Color(0x051F2329) - : Theme.of(context).shadowColor.withOpacity(0.02), + : Theme.of(context).shadowColor.withValues(alpha: 0.02), ), BoxShadow( offset: const Offset(0, 2), @@ -140,7 +140,7 @@ class WelcomeSampleQuestion extends StatelessWidget { spreadRadius: 2, color: isLightMode ? const Color(0x051F2329) - : Theme.of(context).shadowColor.withOpacity(0.02), + : Theme.of(context).shadowColor.withValues(alpha: 0.02), ), ], ), diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/layout_define.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/layout_define.dart index 45947ca2d6..611ff5d922 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/layout_define.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/layout_define.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:universal_platform/universal_platform.dart'; class AIChatUILayout { + const AIChatUILayout._(); + static EdgeInsets safeAreaInsets(BuildContext context) { final query = MediaQuery.of(context); return UniversalPlatform.isMobile @@ -19,53 +21,22 @@ class AIChatUILayout { : EdgeInsets.zero; } -class DesktopAIPromptSizes { - static const promptFrameRadius = BorderRadius.all(Radius.circular(12.0)); +class DesktopAIChatSizes { + const DesktopAIChatSizes._(); - static const attachedFilesBarPadding = - EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0); - static const attachedFilesPreviewHeight = 48.0; - static const attachedFilesPreviewSpacing = 12.0; - - static const textFieldMinHeight = 40.0; - static const textFieldContentPadding = - EdgeInsetsDirectional.fromSTEB(14.0, 12.0, 14.0, 8.0); - - static const actionBarHeight = 32.0; - static const actionBarPadding = EdgeInsetsDirectional.fromSTEB(8, 0, 8, 4); - static const actionBarButtonSize = 28.0; - static const actionBarIconSize = 16.0; - static const actionBarButtonSpacing = 4.0; - static const sendButtonSize = 24.0; -} - -class MobileAIPromptSizes { - static const promptFrameRadius = - BorderRadius.vertical(top: Radius.circular(8.0)); - - static const attachedFilesBarHeight = 68.0; - static const attachedFilesBarPadding = - EdgeInsets.only(top: 8.0, left: 8.0, right: 8.0, bottom: 4.0); - static const attachedFilesPreviewHeight = 56.0; - static const attachedFilesPreviewSpacing = 8.0; - - static const textFieldMinHeight = 48.0; - static const textFieldContentPadding = EdgeInsets.all(8.0); - - static const mentionIconSize = 20.0; - static const sendButtonSize = 32.0; -} - -class DesktopAIConvoSizes { static const avatarSize = 32.0; - static const avatarAndChatBubbleSpacing = 12.0; - static const actionBarIconSize = 28.0; - static const actionBarIconSpacing = 8.0; - static const hoverActionBarPadding = EdgeInsets.all(2.0); - static const hoverActionBarRadius = BorderRadius.all(Radius.circular(8.0)); - static const hoverActionBarIconRadius = + static const messageActionBarIconSize = 28.0; + static const messageHoverActionBarPadding = EdgeInsets.all(2.0); + static const messageHoverActionBarRadius = + BorderRadius.all(Radius.circular(8.0)); + static const messageHoverActionBarIconRadius = BorderRadius.all(Radius.circular(6.0)); - static const actionBarIconRadius = BorderRadius.all(Radius.circular(8.0)); + static const messageActionBarIconRadius = + BorderRadius.all(Radius.circular(8.0)); + + static const inputActionBarMargin = + EdgeInsetsDirectional.fromSTEB(8, 0, 8, 4); + static const inputActionBarButtonSpacing = 4.0; } diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_change_format_bottom_sheet.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_change_format_bottom_sheet.dart new file mode 100644 index 0000000000..5fa3b8f8a7 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_change_format_bottom_sheet.dart @@ -0,0 +1,196 @@ +import 'package:appflowy/ai/ai.dart'; +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/mobile/presentation/base/app_bar/app_bar_actions.dart'; +import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; +import 'package:appflowy/mobile/presentation/widgets/widgets.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; + +Future showChangeFormatBottomSheet( + BuildContext context, +) { + return showMobileBottomSheet( + context, + showDragHandle: true, + builder: (context) => const _ChangeFormatBottomSheetContent(), + ); +} + +class _ChangeFormatBottomSheetContent extends StatefulWidget { + const _ChangeFormatBottomSheetContent(); + + @override + State<_ChangeFormatBottomSheetContent> createState() => + _ChangeFormatBottomSheetContentState(); +} + +class _ChangeFormatBottomSheetContentState + extends State<_ChangeFormatBottomSheetContent> { + PredefinedFormat? predefinedFormat; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + _Header( + onCancel: () => Navigator.of(context).pop(), + onDone: () => Navigator.of(context).pop(predefinedFormat), + ), + const VSpace(4.0), + _Body( + predefinedFormat: predefinedFormat, + onSelectPredefinedFormat: (format) { + setState(() => predefinedFormat = format); + }, + ), + const VSpace(16.0), + ], + ); + } +} + +class _Header extends StatelessWidget { + const _Header({ + required this.onCancel, + required this.onDone, + }); + + final VoidCallback onCancel; + final VoidCallback onDone; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 44.0, + child: Stack( + children: [ + Align( + alignment: Alignment.centerLeft, + child: AppBarBackButton( + padding: const EdgeInsets.symmetric( + vertical: 12, + horizontal: 16, + ), + onTap: onCancel, + ), + ), + Align( + child: Container( + constraints: const BoxConstraints(maxWidth: 250), + child: FlowyText( + LocaleKeys.chat_changeFormat_actionButton.tr(), + fontSize: 17.0, + fontWeight: FontWeight.w500, + overflow: TextOverflow.ellipsis, + ), + ), + ), + Align( + alignment: Alignment.centerRight, + child: AppBarDoneButton( + onTap: onDone, + ), + ), + ], + ), + ); + } +} + +class _Body extends StatelessWidget { + const _Body({ + required this.predefinedFormat, + required this.onSelectPredefinedFormat, + }); + + final PredefinedFormat? predefinedFormat; + final void Function(PredefinedFormat) onSelectPredefinedFormat; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildFormatButton(ImageFormat.text, true), + _buildFormatButton(ImageFormat.textAndImage), + _buildFormatButton(ImageFormat.image), + const VSpace(32.0), + Opacity( + opacity: predefinedFormat?.imageFormat.hasText ?? true ? 1 : 0, + child: Column( + children: [ + _buildTextFormatButton(TextFormat.paragraph, true), + _buildTextFormatButton(TextFormat.bulletList), + _buildTextFormatButton(TextFormat.numberedList), + _buildTextFormatButton(TextFormat.table), + ], + ), + ), + ], + ); + } + + Widget _buildFormatButton( + ImageFormat format, [ + bool isFirst = false, + ]) { + return FlowyOptionTile.checkbox( + text: format.i18n, + isSelected: format == predefinedFormat?.imageFormat, + showTopBorder: isFirst, + leftIcon: FlowySvg( + format.icon, + size: format == ImageFormat.textAndImage + ? const Size(21.0 / 16.0 * 20, 20) + : const Size.square(20), + ), + onTap: () { + if (predefinedFormat != null && + format == predefinedFormat!.imageFormat) { + return; + } + if (format.hasText) { + final textFormat = + predefinedFormat?.textFormat ?? TextFormat.paragraph; + onSelectPredefinedFormat( + PredefinedFormat(imageFormat: format, textFormat: textFormat), + ); + } else { + onSelectPredefinedFormat( + PredefinedFormat(imageFormat: format, textFormat: null), + ); + } + }, + ); + } + + Widget _buildTextFormatButton( + TextFormat format, [ + bool isFirst = false, + ]) { + return FlowyOptionTile.checkbox( + text: format.i18n, + isSelected: format == predefinedFormat?.textFormat, + showTopBorder: isFirst, + leftIcon: FlowySvg( + format.icon, + size: const Size.square(20), + ), + onTap: () { + if (predefinedFormat != null && + format == predefinedFormat!.textFormat) { + return; + } + onSelectPredefinedFormat( + PredefinedFormat( + imageFormat: predefinedFormat?.imageFormat ?? ImageFormat.text, + textFormat: format, + ), + ); + }, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_change_model_bottom_sheet.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_change_model_bottom_sheet.dart new file mode 100644 index 0000000000..aa0d840574 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_change_model_bottom_sheet.dart @@ -0,0 +1,145 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/mobile/presentation/base/app_bar/app_bar_actions.dart'; +import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; +import 'package:appflowy/mobile/presentation/widgets/widgets.dart'; +import 'package:appflowy_backend/protobuf/flowy-ai/protobuf.dart'; +import 'package:collection/collection.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; + +Future showChangeModelBottomSheet( + BuildContext context, + List models, +) { + return showMobileBottomSheet( + context, + showDragHandle: true, + builder: (context) => _ChangeModelBottomSheetContent(models: models), + ); +} + +class _ChangeModelBottomSheetContent extends StatefulWidget { + const _ChangeModelBottomSheetContent({ + required this.models, + }); + + final List models; + + @override + State<_ChangeModelBottomSheetContent> createState() => + _ChangeModelBottomSheetContentState(); +} + +class _ChangeModelBottomSheetContentState + extends State<_ChangeModelBottomSheetContent> { + AIModelPB? model; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + _Header( + onCancel: () => Navigator.of(context).pop(), + onDone: () => Navigator.of(context).pop(model), + ), + const VSpace(4.0), + _Body( + models: widget.models, + selectedModel: model, + onSelectModel: (format) { + setState(() => model = format); + }, + ), + const VSpace(16.0), + ], + ); + } +} + +class _Header extends StatelessWidget { + const _Header({ + required this.onCancel, + required this.onDone, + }); + + final VoidCallback onCancel; + final VoidCallback onDone; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 44.0, + child: Stack( + children: [ + Align( + alignment: Alignment.centerLeft, + child: AppBarBackButton( + padding: const EdgeInsets.symmetric( + vertical: 12, + horizontal: 16, + ), + onTap: onCancel, + ), + ), + Align( + child: Container( + constraints: const BoxConstraints(maxWidth: 250), + child: FlowyText( + LocaleKeys.chat_switchModel_label.tr(), + fontSize: 17.0, + fontWeight: FontWeight.w500, + overflow: TextOverflow.ellipsis, + ), + ), + ), + Align( + alignment: Alignment.centerRight, + child: AppBarDoneButton( + onTap: onDone, + ), + ), + ], + ), + ); + } +} + +class _Body extends StatelessWidget { + const _Body({ + required this.models, + required this.selectedModel, + required this.onSelectModel, + }); + + final List models; + final AIModelPB? selectedModel; + final void Function(AIModelPB) onSelectModel; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: models + .mapIndexed( + (index, model) => _buildModelButton(model, index == 0), + ) + .toList(), + ); + } + + Widget _buildModelButton( + AIModelPB model, [ + bool isFirst = false, + ]) { + return FlowyOptionTile.checkbox( + text: model.name, + isSelected: model == selectedModel, + showTopBorder: isFirst, + onTap: () { + onSelectModel(model); + }, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_markdown_text.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_markdown_text.dart index 12c9cc7e68..1e7d428263 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_markdown_text.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_markdown_text.dart @@ -52,7 +52,7 @@ class _AppFlowyEditorMarkdownState extends State<_AppFlowyEditorMarkdown> { void initState() { super.initState(); - editorState = _parseMarkdown(widget.markdown); + editorState = _parseMarkdown(widget.markdown.trim()); scrollController = EditorScrollController( editorState: editorState, shrinkWrap: true, @@ -64,8 +64,12 @@ class _AppFlowyEditorMarkdownState extends State<_AppFlowyEditorMarkdown> { super.didUpdateWidget(oldWidget); if (oldWidget.markdown != widget.markdown) { - editorState.dispose(); - editorState = _parseMarkdown(widget.markdown); + final editorState = _parseMarkdown( + widget.markdown.trim(), + previousDocument: this.editorState.document, + ); + this.editorState.dispose(); + this.editorState = editorState; scrollController.dispose(); scrollController = EditorScrollController( editorState: editorState, @@ -101,6 +105,7 @@ class _AppFlowyEditorMarkdownState extends State<_AppFlowyEditorMarkdown> { styleCustomizer: styleCustomizer, // the editor is not editable in the chat editable: false, + alwaysDistributeSimpleTableColumnWidths: UniversalPlatform.isDesktop, ); return IntrinsicHeight( child: AppFlowyEditor( @@ -108,6 +113,7 @@ class _AppFlowyEditorMarkdownState extends State<_AppFlowyEditorMarkdown> { // the editor is not editable in the chat editable: false, disableKeyboardService: UniversalPlatform.isMobile, + disableSelectionService: UniversalPlatform.isMobile, editorStyle: editorStyle, editorScrollController: scrollController, blockComponentBuilders: blockBuilders, @@ -127,8 +133,30 @@ class _AppFlowyEditorMarkdownState extends State<_AppFlowyEditorMarkdown> { ); } - EditorState _parseMarkdown(String markdown) { + EditorState _parseMarkdown( + String markdown, { + Document? previousDocument, + }) { + // merge the nodes from the previous document with the new document to keep the same node ids final document = customMarkdownToDocument(markdown); + final documentIterator = NodeIterator( + document: document, + startNode: document.root, + ); + if (previousDocument != null) { + final previousDocumentIterator = NodeIterator( + document: previousDocument, + startNode: previousDocument.root, + ); + while ( + documentIterator.moveNext() && previousDocumentIterator.moveNext()) { + final currentNode = documentIterator.current; + final previousNode = previousDocumentIterator.current; + if (currentNode.path.equals(previousNode.path)) { + currentNode.id = previousNode.id; + } + } + } final editorState = EditorState(document: document); return editorState; } diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_message_action_bar.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_message_action_bar.dart index 64183d0346..08fd82188d 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_message_action_bar.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_message_action_bar.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:convert'; +import 'package:appflowy/ai/ai.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/ai_chat/application/chat_ai_message_bloc.dart'; @@ -9,6 +10,7 @@ import 'package:appflowy/plugins/ai_chat/application/chat_select_sources_cubit.d import 'package:appflowy/plugins/document/application/prelude.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart'; import 'package:appflowy/shared/markdown_to_document.dart'; +import 'package:appflowy/shared/patterns/common_patterns.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/util/theme_extension.dart'; import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart'; @@ -19,6 +21,7 @@ import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart'; import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; +import 'package:appflowy_backend/protobuf/flowy-ai/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_result/appflowy_result.dart'; @@ -29,44 +32,54 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_chat_core/flutter_chat_core.dart'; -import '../chat_input/select_sources_menu.dart'; import '../layout_define.dart'; import 'message_util.dart'; -class AIMessageActionBar extends StatelessWidget { +class AIMessageActionBar extends StatefulWidget { const AIMessageActionBar({ super.key, required this.message, required this.showDecoration, this.onRegenerate, + this.onChangeFormat, + this.onChangeModel, this.onOverrideVisibility, }); final Message message; final bool showDecoration; final void Function()? onRegenerate; + final void Function(PredefinedFormat)? onChangeFormat; + final void Function(AIModelPB)? onChangeModel; final void Function(bool)? onOverrideVisibility; + @override + State createState() => _AIMessageActionBarState(); +} + +class _AIMessageActionBarState extends State { + final popoverMutex = PopoverMutex(); + @override Widget build(BuildContext context) { final isLightMode = Theme.of(context).isLightMode; final child = SeparatedRow( mainAxisSize: MainAxisSize.min, - separatorBuilder: () => - const HSpace(DesktopAIConvoSizes.actionBarIconSpacing), + separatorBuilder: () => const HSpace(8.0), children: _buildChildren(), ); - return showDecoration + return widget.showDecoration ? Container( - padding: const EdgeInsets.all(2.0), + padding: DesktopAIChatSizes.messageHoverActionBarPadding, decoration: BoxDecoration( - borderRadius: DesktopAIConvoSizes.hoverActionBarRadius, + borderRadius: DesktopAIChatSizes.messageHoverActionBarRadius, border: Border.all( color: isLightMode ? const Color(0x1F1F2329) : Theme.of(context).dividerColor, + strokeAlign: BorderSide.strokeAlignOutside, ), color: Theme.of(context).cardColor, boxShadow: [ @@ -76,14 +89,14 @@ class AIMessageActionBar extends StatelessWidget { spreadRadius: -2, color: isLightMode ? const Color(0x051F2329) - : Theme.of(context).shadowColor.withOpacity(0.02), + : Theme.of(context).shadowColor.withValues(alpha: 0.02), ), BoxShadow( offset: const Offset(0, 2), blurRadius: 4, color: isLightMode ? const Color(0x051F2329) - : Theme.of(context).shadowColor.withOpacity(0.02), + : Theme.of(context).shadowColor.withValues(alpha: 0.02), ), BoxShadow( offset: const Offset(0, 2), @@ -91,7 +104,7 @@ class AIMessageActionBar extends StatelessWidget { spreadRadius: 2, color: isLightMode ? const Color(0x051F2329) - : Theme.of(context).shadowColor.withOpacity(0.02), + : Theme.of(context).shadowColor.withValues(alpha: 0.02), ), ], ), @@ -103,17 +116,30 @@ class AIMessageActionBar extends StatelessWidget { List _buildChildren() { return [ CopyButton( - isInHoverBar: showDecoration, - textMessage: message as TextMessage, + isInHoverBar: widget.showDecoration, + textMessage: widget.message as TextMessage, ), RegenerateButton( - isInHoverBar: showDecoration, - onTap: () => onRegenerate?.call(), + isInHoverBar: widget.showDecoration, + onTap: () => widget.onRegenerate?.call(), + ), + ChangeFormatButton( + isInHoverBar: widget.showDecoration, + onRegenerate: widget.onChangeFormat, + popoverMutex: popoverMutex, + onOverrideVisibility: widget.onOverrideVisibility, + ), + ChangeModelButton( + isInHoverBar: widget.showDecoration, + onRegenerate: widget.onChangeModel, + popoverMutex: popoverMutex, + onOverrideVisibility: widget.onOverrideVisibility, ), SaveToPageButton( - textMessage: message as TextMessage, - isInHoverBar: showDecoration, - onOverrideVisibility: onOverrideVisibility, + textMessage: widget.message as TextMessage, + isInHoverBar: widget.showDecoration, + popoverMutex: popoverMutex, + onOverrideVisibility: widget.onOverrideVisibility, ), ]; } @@ -134,34 +160,48 @@ class CopyButton extends StatelessWidget { return FlowyTooltip( message: LocaleKeys.settings_menu_clickToCopy.tr(), child: FlowyIconButton( - width: DesktopAIConvoSizes.actionBarIconSize, + width: DesktopAIChatSizes.messageActionBarIconSize, hoverColor: AFThemeExtension.of(context).lightGreyHover, radius: isInHoverBar - ? DesktopAIConvoSizes.hoverActionBarIconRadius - : DesktopAIConvoSizes.actionBarIconRadius, + ? DesktopAIChatSizes.messageHoverActionBarIconRadius + : DesktopAIChatSizes.messageActionBarIconRadius, icon: FlowySvg( FlowySvgs.copy_s, color: Theme.of(context).hintColor, size: const Size.square(16), ), onPressed: () async { - final document = customMarkdownToDocument(textMessage.text); + final messageText = textMessage.text.trim(); + final document = customMarkdownToDocument( + messageText, + tableWidth: 250.0, + ); await getIt().setData( ClipboardServiceData( - plainText: textMessage.text, + plainText: _stripMarkdownIfNecessary(messageText), inAppJson: jsonEncode(document.toJson()), ), ); if (context.mounted) { showToastNotification( - context, - message: LocaleKeys.grid_url_copiedNotification.tr(), + message: LocaleKeys.message_copy_success.tr(), ); } }, ), ); } + + String _stripMarkdownIfNecessary(String plainText) { + // match and capture inner url as group + final matches = singleLineMarkdownImageRegex.allMatches(plainText); + + if (matches.length != 1) { + return plainText; + } + + return matches.first[1] ?? plainText; + } } class RegenerateButton extends StatelessWidget { @@ -179,13 +219,13 @@ class RegenerateButton extends StatelessWidget { return FlowyTooltip( message: LocaleKeys.chat_regenerate.tr(), child: FlowyIconButton( - width: DesktopAIConvoSizes.actionBarIconSize, + width: DesktopAIChatSizes.messageActionBarIconSize, hoverColor: AFThemeExtension.of(context).lightGreyHover, radius: isInHoverBar - ? DesktopAIConvoSizes.hoverActionBarIconRadius - : DesktopAIConvoSizes.actionBarIconRadius, + ? DesktopAIChatSizes.messageHoverActionBarIconRadius + : DesktopAIChatSizes.messageActionBarIconRadius, icon: FlowySvg( - FlowySvgs.ai_undo_s, + FlowySvgs.ai_try_again_s, color: Theme.of(context).hintColor, size: const Size.square(16), ), @@ -195,16 +235,275 @@ class RegenerateButton extends StatelessWidget { } } +class ChangeFormatButton extends StatefulWidget { + const ChangeFormatButton({ + super.key, + required this.isInHoverBar, + this.popoverMutex, + this.onRegenerate, + this.onOverrideVisibility, + }); + + final bool isInHoverBar; + final PopoverMutex? popoverMutex; + final void Function(PredefinedFormat)? onRegenerate; + final void Function(bool)? onOverrideVisibility; + + @override + State createState() => _ChangeFormatButtonState(); +} + +class _ChangeFormatButtonState extends State { + final popoverController = PopoverController(); + + @override + Widget build(BuildContext context) { + return AppFlowyPopover( + controller: popoverController, + mutex: widget.popoverMutex, + triggerActions: PopoverTriggerFlags.none, + margin: EdgeInsets.zero, + offset: Offset(0, widget.isInHoverBar ? 8 : 4), + direction: PopoverDirection.bottomWithLeftAligned, + constraints: const BoxConstraints(), + onClose: () => widget.onOverrideVisibility?.call(false), + child: buildButton(context), + popupBuilder: (_) => BlocProvider.value( + value: context.read(), + child: _ChangeFormatPopoverContent( + onRegenerate: widget.onRegenerate, + ), + ), + ); + } + + Widget buildButton(BuildContext context) { + return FlowyTooltip( + message: LocaleKeys.chat_changeFormat_actionButton.tr(), + child: FlowyIconButton( + width: 32.0, + height: DesktopAIChatSizes.messageActionBarIconSize, + hoverColor: AFThemeExtension.of(context).lightGreyHover, + radius: widget.isInHoverBar + ? DesktopAIChatSizes.messageHoverActionBarIconRadius + : DesktopAIChatSizes.messageActionBarIconRadius, + icon: Row( + mainAxisSize: MainAxisSize.min, + children: [ + FlowySvg( + FlowySvgs.ai_retry_font_s, + color: Theme.of(context).hintColor, + size: const Size.square(16), + ), + FlowySvg( + FlowySvgs.ai_source_drop_down_s, + color: Theme.of(context).hintColor, + size: const Size.square(8), + ), + ], + ), + onPressed: () { + widget.onOverrideVisibility?.call(true); + popoverController.show(); + }, + ), + ); + } +} + +class _ChangeFormatPopoverContent extends StatefulWidget { + const _ChangeFormatPopoverContent({ + this.onRegenerate, + }); + + final void Function(PredefinedFormat)? onRegenerate; + + @override + State<_ChangeFormatPopoverContent> createState() => + _ChangeFormatPopoverContentState(); +} + +class _ChangeFormatPopoverContentState + extends State<_ChangeFormatPopoverContent> { + PredefinedFormat? predefinedFormat; + + @override + Widget build(BuildContext context) { + final isLightMode = Theme.of(context).isLightMode; + return Container( + padding: const EdgeInsets.all(2.0), + decoration: BoxDecoration( + borderRadius: DesktopAIChatSizes.messageHoverActionBarRadius, + border: Border.all( + color: isLightMode + ? const Color(0x1F1F2329) + : Theme.of(context).dividerColor, + strokeAlign: BorderSide.strokeAlignOutside, + ), + color: Theme.of(context).cardColor, + boxShadow: [ + BoxShadow( + offset: const Offset(0, 1), + blurRadius: 2, + spreadRadius: -2, + color: isLightMode + ? const Color(0x051F2329) + : Theme.of(context).shadowColor.withValues(alpha: 0.02), + ), + BoxShadow( + offset: const Offset(0, 2), + blurRadius: 4, + color: isLightMode + ? const Color(0x051F2329) + : Theme.of(context).shadowColor.withValues(alpha: 0.02), + ), + BoxShadow( + offset: const Offset(0, 2), + blurRadius: 8, + spreadRadius: 2, + color: isLightMode + ? const Color(0x051F2329) + : Theme.of(context).shadowColor.withValues(alpha: 0.02), + ), + ], + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + BlocBuilder( + builder: (context, state) { + return ChangeFormatBar( + spacing: 2.0, + showImageFormats: state.aiType.isCloud, + predefinedFormat: predefinedFormat, + onSelectPredefinedFormat: (format) { + setState(() => predefinedFormat = format); + }, + ); + }, + ), + const HSpace(4.0), + FlowyTooltip( + message: LocaleKeys.chat_changeFormat_confirmButton.tr(), + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + if (predefinedFormat != null) { + widget.onRegenerate?.call(predefinedFormat!); + } + }, + child: SizedBox.square( + dimension: DesktopAIPromptSizes.predefinedFormatButtonHeight, + child: Center( + child: FlowySvg( + FlowySvgs.ai_retry_filled_s, + color: Theme.of(context).colorScheme.primary, + size: const Size.square(20), + ), + ), + ), + ), + ), + ), + ], + ), + ); + } +} + +class ChangeModelButton extends StatefulWidget { + const ChangeModelButton({ + super.key, + required this.isInHoverBar, + this.popoverMutex, + this.onRegenerate, + this.onOverrideVisibility, + }); + + final bool isInHoverBar; + final PopoverMutex? popoverMutex; + final void Function(AIModelPB)? onRegenerate; + final void Function(bool)? onOverrideVisibility; + + @override + State createState() => _ChangeModelButtonState(); +} + +class _ChangeModelButtonState extends State { + final popoverController = PopoverController(); + + @override + Widget build(BuildContext context) { + return AppFlowyPopover( + controller: popoverController, + mutex: widget.popoverMutex, + triggerActions: PopoverTriggerFlags.none, + margin: EdgeInsets.zero, + offset: Offset(8, 0), + direction: PopoverDirection.rightWithBottomAligned, + constraints: BoxConstraints(maxWidth: 250, maxHeight: 600), + onClose: () => widget.onOverrideVisibility?.call(false), + child: buildButton(context), + popupBuilder: (_) { + final bloc = context.read(); + final (models, _) = bloc.aiModelStateNotifier.getAvailableModels(); + return SelectModelPopoverContent( + models: models, + selectedModel: null, + onSelectModel: widget.onRegenerate, + ); + }, + ); + } + + Widget buildButton(BuildContext context) { + return FlowyTooltip( + message: LocaleKeys.chat_switchModel_label.tr(), + child: FlowyIconButton( + width: 32.0, + height: DesktopAIChatSizes.messageActionBarIconSize, + hoverColor: AFThemeExtension.of(context).lightGreyHover, + radius: widget.isInHoverBar + ? DesktopAIChatSizes.messageHoverActionBarIconRadius + : DesktopAIChatSizes.messageActionBarIconRadius, + icon: Row( + mainAxisSize: MainAxisSize.min, + children: [ + FlowySvg( + FlowySvgs.ai_sparks_s, + color: Theme.of(context).hintColor, + size: const Size.square(16), + ), + FlowySvg( + FlowySvgs.ai_source_drop_down_s, + color: Theme.of(context).hintColor, + size: const Size.square(8), + ), + ], + ), + onPressed: () { + widget.onOverrideVisibility?.call(true); + popoverController.show(); + }, + ), + ); + } +} + class SaveToPageButton extends StatefulWidget { const SaveToPageButton({ super.key, required this.textMessage, required this.isInHoverBar, + this.popoverMutex, this.onOverrideVisibility, }); final TextMessage textMessage; final bool isInHoverBar; + final PopoverMutex? popoverMutex; final void Function(bool)? onOverrideVisibility; @override @@ -230,7 +529,7 @@ class _SaveToPageButtonState extends State { )..add(const SpaceEvent.initial(openFirstPage: false)), ), BlocProvider( - create: (context) => ChatSettingsCubit(), + create: (context) => ChatSettingsCubit(hideDisabled: true), ), ], child: BlocSelector( @@ -240,12 +539,15 @@ class _SaveToPageButtonState extends State { controller: popoverController, triggerActions: PopoverTriggerFlags.none, margin: EdgeInsets.zero, + mutex: widget.popoverMutex, offset: const Offset(8, 0), direction: PopoverDirection.rightWithBottomAligned, constraints: const BoxConstraints.tightFor(width: 300, height: 400), onClose: () { if (spaceView != null) { - context.read().refreshSources([spaceView]); + context + .read() + .refreshSources([spaceView], spaceView); } widget.onOverrideVisibility?.call(false); }, @@ -261,11 +563,11 @@ class _SaveToPageButtonState extends State { return FlowyTooltip( message: LocaleKeys.chat_addToPageButton.tr(), child: FlowyIconButton( - width: DesktopAIConvoSizes.actionBarIconSize, + width: DesktopAIChatSizes.messageActionBarIconSize, hoverColor: AFThemeExtension.of(context).lightGreyHover, radius: widget.isInHoverBar - ? DesktopAIConvoSizes.hoverActionBarIconRadius - : DesktopAIConvoSizes.actionBarIconRadius, + ? DesktopAIChatSizes.messageHoverActionBarIconRadius + : DesktopAIChatSizes.messageActionBarIconRadius, icon: FlowySvg( FlowySvgs.ai_add_to_page_s, color: Theme.of(context).hintColor, @@ -274,12 +576,16 @@ class _SaveToPageButtonState extends State { onPressed: () async { final documentId = getOpenedDocumentId(); if (documentId != null) { - await onAddToExistingPage(documentId); - await forceReloadAndUpdateSelection(documentId); + await onAddToExistingPage(context, documentId); + await forceReload(documentId); + await Future.delayed(const Duration(milliseconds: 500)); + await updateSelection(documentId); } else { widget.onOverrideVisibility?.call(true); if (spaceView != null) { - context.read().refreshSources([spaceView]); + context + .read() + .refreshSources([spaceView], spaceView); } popoverController.show(); } @@ -291,56 +597,73 @@ class _SaveToPageButtonState extends State { Widget buildPopover(BuildContext context) { return BlocProvider.value( value: context.read(), - child: _SaveToPagePopoverContent( - onAddToNewPage: () { - addMessageToNewPage(context); + child: SaveToPagePopoverContent( + onAddToNewPage: (parentViewId) { + addMessageToNewPage(context, parentViewId); popoverController.close(); }, onAddToExistingPage: (documentId) async { popoverController.close(); - await onAddToExistingPage(documentId); - final view = - await ViewBackendService.getView(documentId).toNullable(); + final view = await onAddToExistingPage(context, documentId); + if (context.mounted) { openPageFromMessage(context, view); } + await Future.delayed(const Duration(milliseconds: 500)); + await updateSelection(documentId); }, ), ); } - Future onAddToExistingPage(String documentId) async { - await ChatEditDocumentService.addMessageToPage( + Future onAddToExistingPage( + BuildContext context, + String documentId, + ) async { + await ChatEditDocumentService.addMessagesToPage( documentId, - widget.textMessage, + [widget.textMessage], ); await Future.delayed(const Duration(milliseconds: 500)); + final view = await ViewBackendService.getView(documentId).toNullable(); + if (context.mounted) { + showSaveMessageSuccessToast(context, view); + } + return view; } - void addMessageToNewPage(BuildContext context) async { + void addMessageToNewPage(BuildContext context, String parentViewId) async { final chatView = await ViewBackendService.getView( context.read().chatId, ).toNullable(); if (chatView != null) { final newView = await ChatEditDocumentService.saveMessagesToNewPage( chatView.nameOrDefault, - chatView.parentViewId, + parentViewId, [widget.textMessage], ); + if (context.mounted) { + showSaveMessageSuccessToast(context, newView); openPageFromMessage(context, newView); } } } - Future forceReloadAndUpdateSelection(String documentId) async { + Future forceReload(String documentId) async { final bloc = DocumentBloc.findOpen(documentId); if (bloc == null) { return; } await bloc.forceReloadDocumentState(); - await Future.delayed(const Duration(milliseconds: 500)); + } + Future updateSelection(String documentId) async { + final bloc = DocumentBloc.findOpen(documentId); + if (bloc == null) { + return; + } + await bloc.forceReloadDocumentState(); final editorState = bloc.state.editorState; final lastNodePath = editorState?.getLastSelectable()?.$1.path; if (editorState == null || lastNodePath == null) { @@ -362,13 +685,14 @@ class _SaveToPageButtonState extends State { } } -class _SaveToPagePopoverContent extends StatelessWidget { - const _SaveToPagePopoverContent({ +class SaveToPagePopoverContent extends StatelessWidget { + const SaveToPagePopoverContent({ + super.key, required this.onAddToNewPage, required this.onAddToExistingPage, }); - final void Function() onAddToNewPage; + final void Function(String) onAddToNewPage; final void Function(String) onAddToExistingPage; @override @@ -406,8 +730,6 @@ class _SaveToPagePopoverContent extends StatelessWidget { children: _buildVisibleSources(context, state).toList(), ), ), - _buildDivider(), - _addToNewPageButton(context), ], ); }, @@ -439,35 +761,19 @@ class _SaveToPagePopoverContent extends StatelessWidget { isDescendentOfSpace: e.view.isSpace, isSelectedSection: false, showCheckbox: false, + showSaveButton: true, onSelected: (source) { - if (!source.view.isSpace) { + if (source.view.isSpace) { + onAddToNewPage(source.view.id); + } else { onAddToExistingPage(source.view.id); } }, + onAdd: (source) { + onAddToNewPage(source.view.id); + }, height: 30.0, ), ); } - - Widget _addToNewPageButton(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - child: SizedBox( - height: 30, - child: FlowyButton( - iconPadding: 8, - onTap: onAddToNewPage, - text: FlowyText( - LocaleKeys.chat_addToNewPage.tr(), - figmaLineHeight: 20, - ), - leftIcon: FlowySvg( - FlowySvgs.add_m, - size: const Size.square(16), - color: Theme.of(context).hintColor, - ), - ), - ), - ); - } } diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_message_bubble.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_message_bubble.dart index 93f350b1c4..2786799520 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_message_bubble.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_message_bubble.dart @@ -1,27 +1,32 @@ import 'dart:convert'; +import 'package:appflowy/ai/ai.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart'; import 'package:appflowy/plugins/ai_chat/application/chat_edit_document_service.dart'; -import 'package:appflowy/plugins/ai_chat/presentation/chat_input/chat_mention_page_bottom_sheet.dart'; +import 'package:appflowy/plugins/ai_chat/application/chat_select_message_bloc.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart'; import 'package:appflowy/shared/markdown_to_document.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; +import 'package:appflowy_backend/protobuf/flowy-ai/protobuf.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_chat_core/flutter_chat_core.dart'; import 'package:go_router/go_router.dart'; import 'package:universal_platform/universal_platform.dart'; import '../chat_avatar.dart'; import '../layout_define.dart'; +import 'ai_change_model_bottom_sheet.dart'; import 'ai_message_action_bar.dart'; +import 'ai_change_format_bottom_sheet.dart'; import 'message_util.dart'; /// Wraps an AI response message with the avatar and actions. On desktop, @@ -35,39 +40,44 @@ class ChatAIMessageBubble extends StatelessWidget { required this.child, required this.showActions, this.isLastMessage = false, + this.isSelectingMessages = false, this.onRegenerate, + this.onChangeFormat, + this.onChangeModel, }); final Message message; final Widget child; final bool showActions; final bool isLastMessage; + final bool isSelectingMessages; final void Function()? onRegenerate; + final void Function(PredefinedFormat)? onChangeFormat; + final void Function(AIModelPB)? onChangeModel; @override Widget build(BuildContext context) { - final avatarAndMessage = Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const ChatAIAvatar(), - const HSpace(DesktopAIConvoSizes.avatarAndChatBubbleSpacing), - Expanded(child: child), - ], + final messageWidget = _WrapIsSelectingMessage( + isSelectingMessages: isSelectingMessages, + message: message, + child: child, ); - return showActions + return !isSelectingMessages && showActions ? UniversalPlatform.isMobile - ? _wrapPopMenu(avatarAndMessage) + ? _wrapPopMenu(messageWidget) : isLastMessage - ? _wrapBottomActions(avatarAndMessage) - : _wrapHover(avatarAndMessage) - : avatarAndMessage; + ? _wrapBottomActions(messageWidget) + : _wrapHover(messageWidget) + : messageWidget; } Widget _wrapBottomActions(Widget child) { return ChatAIBottomInlineActions( message: message, onRegenerate: onRegenerate, + onChangeFormat: onChangeFormat, + onChangeModel: onChangeModel, child: child, ); } @@ -76,6 +86,8 @@ class ChatAIMessageBubble extends StatelessWidget { return ChatAIMessageHover( message: message, onRegenerate: onRegenerate, + onChangeFormat: onChangeFormat, + onChangeModel: onChangeModel, child: child, ); } @@ -84,6 +96,8 @@ class ChatAIMessageBubble extends StatelessWidget { return ChatAIMessagePopup( message: message, onRegenerate: onRegenerate, + onChangeFormat: onChangeFormat, + onChangeModel: onChangeModel, child: child, ); } @@ -95,11 +109,15 @@ class ChatAIBottomInlineActions extends StatelessWidget { required this.child, required this.message, this.onRegenerate, + this.onChangeFormat, + this.onChangeModel, }); final Widget child; final Message message; final void Function()? onRegenerate; + final void Function(PredefinedFormat)? onChangeFormat; + final void Function(AIModelPB)? onChangeModel; @override Widget build(BuildContext context) { @@ -110,13 +128,15 @@ class ChatAIBottomInlineActions extends StatelessWidget { const VSpace(16.0), Padding( padding: const EdgeInsetsDirectional.only( - start: DesktopAIConvoSizes.avatarSize + - DesktopAIConvoSizes.avatarAndChatBubbleSpacing, + start: DesktopAIChatSizes.avatarSize + + DesktopAIChatSizes.avatarAndChatBubbleSpacing, ), child: AIMessageActionBar( message: message, showDecoration: false, onRegenerate: onRegenerate, + onChangeFormat: onChangeFormat, + onChangeModel: onChangeModel, ), ), const VSpace(32.0), @@ -131,11 +151,15 @@ class ChatAIMessageHover extends StatefulWidget { required this.child, required this.message, this.onRegenerate, + this.onChangeFormat, + this.onChangeModel, }); final Widget child; final Message message; final void Function()? onRegenerate; + final void Function(PredefinedFormat)? onChangeFormat; + final void Function(AIModelPB)? onChangeModel; @override State createState() => _ChatAIMessageHoverState(); @@ -187,8 +211,8 @@ class _ChatAIMessageHoverState extends State { link: layerLink, targetAnchor: Alignment.bottomLeft, offset: const Offset( - DesktopAIConvoSizes.avatarSize + - DesktopAIConvoSizes.avatarAndChatBubbleSpacing, + DesktopAIChatSizes.avatarSize + + DesktopAIChatSizes.avatarAndChatBubbleSpacing, 0, ), child: Align( @@ -205,21 +229,23 @@ class _ChatAIMessageHoverState extends State { setState(() => hoverActionBar = false); } }, - child: Container( - constraints: BoxConstraints( - maxWidth: 784, - maxHeight: DesktopAIConvoSizes.actionBarIconSize + - DesktopAIConvoSizes.hoverActionBarPadding.vertical, - ), - alignment: Alignment.topLeft, + child: SizedBox( + width: 784, + height: DesktopAIChatSizes.messageActionBarIconSize + + DesktopAIChatSizes.messageHoverActionBarPadding.vertical, child: hoverBubble || hoverActionBar || overrideVisibility - ? AIMessageActionBar( - message: widget.message, - showDecoration: true, - onRegenerate: widget.onRegenerate, - onOverrideVisibility: (visibility) { - overrideVisibility = visibility; - }, + ? Align( + alignment: AlignmentDirectional.centerStart, + child: AIMessageActionBar( + message: widget.message, + showDecoration: true, + onRegenerate: widget.onRegenerate, + onChangeFormat: widget.onChangeFormat, + onChangeModel: widget.onChangeModel, + onOverrideVisibility: (visibility) { + overrideVisibility = visibility; + }, + ), ) : null, ), @@ -270,8 +296,8 @@ class _ChatAIMessageHoverState extends State { return messageOffset.dy + messageHeight + - DesktopAIConvoSizes.actionBarIconSize + - DesktopAIConvoSizes.hoverActionBarPadding.vertical <= + DesktopAIChatSizes.messageActionBarIconSize + + DesktopAIChatSizes.messageHoverActionBarPadding.vertical <= scrollableOffset.dy + scrollableHeight; } @@ -288,11 +314,15 @@ class ChatAIMessagePopup extends StatelessWidget { required this.child, required this.message, this.onRegenerate, + this.onChangeFormat, + this.onChangeModel, }); final Widget child; final Message message; final void Function()? onRegenerate; + final void Function(PredefinedFormat)? onChangeFormat; + final void Function(AIModelPB)? onChangeModel; @override Widget build(BuildContext context) { @@ -307,13 +337,15 @@ class ChatAIMessagePopup extends StatelessWidget { return Column( mainAxisSize: MainAxisSize.min, children: [ - const VSpace(16.0), _copyButton(context, bottomSheetContext), - const Divider(height: 8.5, thickness: 0.5), + _divider(), _regenerateButton(context), - const Divider(height: 8.5, thickness: 0.5), + _divider(), + _changeFormatButton(context), + _divider(), + _changeModelButton(context), + _divider(), _saveToPageButton(context), - const Divider(height: 8.5, thickness: 0.5), ], ); }, @@ -323,6 +355,8 @@ class ChatAIMessagePopup extends StatelessWidget { ); } + Widget _divider() => const MobileQuickActionDivider(); + Widget _copyButton(BuildContext context, BuildContext bottomSheetContext) { return MobileQuickActionButton( onTap: () async { @@ -342,8 +376,7 @@ class ChatAIMessagePopup extends StatelessWidget { } if (context.mounted) { showToastNotification( - context, - message: LocaleKeys.grid_url_copiedNotification.tr(), + message: LocaleKeys.message_copy_success.tr(), ); } }, @@ -359,12 +392,48 @@ class ChatAIMessagePopup extends StatelessWidget { onRegenerate?.call(); Navigator.of(context).pop(); }, - icon: FlowySvgs.ai_undo_s, + icon: FlowySvgs.ai_try_again_s, iconSize: const Size.square(20), text: LocaleKeys.chat_regenerate.tr(), ); } + Widget _changeFormatButton(BuildContext context) { + return MobileQuickActionButton( + onTap: () async { + final result = await showChangeFormatBottomSheet(context); + if (result != null) { + onChangeFormat?.call(result); + if (context.mounted) { + Navigator.of(context).pop(); + } + } + }, + icon: FlowySvgs.ai_retry_font_s, + iconSize: const Size.square(20), + text: LocaleKeys.chat_changeFormat_actionButton.tr(), + ); + } + + Widget _changeModelButton(BuildContext context) { + return MobileQuickActionButton( + onTap: () async { + final bloc = context.read(); + final (models, _) = bloc.aiModelStateNotifier.getAvailableModels(); + final result = await showChangeModelBottomSheet(context, models); + if (result != null) { + onChangeModel?.call(result); + if (context.mounted) { + Navigator.of(context).pop(); + } + } + }, + icon: FlowySvgs.ai_sparks_s, + iconSize: const Size.square(20), + text: LocaleKeys.chat_switchModel_label.tr(), + ); + } + Widget _saveToPageButton(BuildContext context) { return MobileQuickActionButton( onTap: () async { @@ -379,9 +448,9 @@ class ChatAIMessagePopup extends StatelessWidget { return; } - await ChatEditDocumentService.addMessageToPage( + await ChatEditDocumentService.addMessagesToPage( selectedView.id, - message as TextMessage, + [message as TextMessage], ); if (context.mounted) { @@ -395,3 +464,87 @@ class ChatAIMessagePopup extends StatelessWidget { ); } } + +class _WrapIsSelectingMessage extends StatelessWidget { + const _WrapIsSelectingMessage({ + required this.message, + required this.child, + this.isSelectingMessages = false, + }); + + final Message message; + final Widget child; + final bool isSelectingMessages; + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + final isSelected = + context.read().isMessageSelected(message.id); + return GestureDetector( + onTap: () { + if (isSelectingMessages) { + context + .read() + .add(ChatSelectMessageEvent.toggleSelectMessage(message)); + } + }, + behavior: isSelectingMessages ? HitTestBehavior.opaque : null, + child: DecoratedBox( + decoration: BoxDecoration( + color: isSelected + ? Theme.of(context).colorScheme.tertiaryContainer + : null, + borderRadius: const BorderRadius.all(Radius.circular(8.0)), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (isSelectingMessages) + ChatSelectMessageIndicator(isSelected: isSelected) + else + SelectionContainer.disabled( + child: const ChatAIAvatar(), + ), + const HSpace(DesktopAIChatSizes.avatarAndChatBubbleSpacing), + Expanded( + child: IgnorePointer( + ignoring: isSelectingMessages, + child: child, + ), + ), + ], + ), + ), + ); + }, + ); + } +} + +class ChatSelectMessageIndicator extends StatelessWidget { + const ChatSelectMessageIndicator({ + super.key, + required this.isSelected, + }); + + final bool isSelected; + + @override + Widget build(BuildContext context) { + return MouseRegion( + cursor: SystemMouseCursors.click, + child: SizedBox.square( + dimension: 30.0, + child: Center( + child: FlowySvg( + isSelected ? FlowySvgs.check_filled_s : FlowySvgs.uncheck_s, + blendMode: BlendMode.dst, + size: const Size.square(20), + ), + ), + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_metadata.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_metadata.dart index 1e06ff5f46..cc97610e8d 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_metadata.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_metadata.dart @@ -90,6 +90,7 @@ class _AIMessageMetadataState extends State { data == null) { return _MetadataButton( name: m.name, + onTap: () => widget.onSelectedMetadata?.call(m), ); } return BlocProvider( diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_text_message.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_text_message.dart index 632bf64ebc..380767105f 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_text_message.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/ai_text_message.dart @@ -1,20 +1,21 @@ +import 'package:appflowy/ai/ai.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/ai_chat/application/chat_ai_message_bloc.dart'; +import 'package:appflowy/plugins/ai_chat/application/chat_bloc.dart'; import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart'; import 'package:appflowy/plugins/ai_chat/application/chat_message_stream.dart'; +import 'package:appflowy_backend/protobuf/flowy-ai/protobuf.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:fixnum/fixnum.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_chat_core/flutter_chat_core.dart'; -import 'package:universal_platform/universal_platform.dart'; import '../layout_define.dart'; import 'ai_markdown_text.dart'; import 'ai_message_bubble.dart'; import 'ai_metadata.dart'; -import 'loading_indicator.dart'; import 'error_text_message.dart'; /// [ChatAIMessageWidget] includes both the text of the AI response as well as @@ -32,10 +33,14 @@ class ChatAIMessageWidget extends StatelessWidget { required this.questionId, required this.chatId, required this.refSourceJsonString, + required this.onStopStream, this.onSelectedMetadata, this.onRegenerate, + this.onChangeFormat, + this.onChangeModel, this.isLastMessage = false, this.isStreaming = false, + this.isSelectingMessages = false, }); final User user; @@ -48,8 +53,12 @@ class ChatAIMessageWidget extends StatelessWidget { final String? refSourceJsonString; final void Function(ChatMessageRefSource metadata)? onSelectedMetadata; final void Function()? onRegenerate; + final void Function() onStopStream; + final void Function(PredefinedFormat)? onChangeFormat; + final void Function(AIModelPB)? onChangeModel; final bool isStreaming; final bool isLastMessage; + final bool isSelectingMessages; @override Widget build(BuildContext context) { @@ -65,57 +74,96 @@ class ChatAIMessageWidget extends StatelessWidget { final loadingText = state.progress?.step ?? LocaleKeys.chat_generatingResponse.tr(); - return Padding( - padding: AIChatUILayout.messageMargin, - child: state.messageState.when( - loading: () => ChatAIMessageBubble( - message: message, - showActions: false, - child: ChatAILoading(text: loadingText), - ), - ready: () { - return state.text.isEmpty - ? ChatAIMessageBubble( - message: message, - showActions: false, - child: ChatAILoading(text: loadingText), - ) - : ChatAIMessageBubble( - message: message, - isLastMessage: isLastMessage, - showActions: stream == null && - state.text.isNotEmpty && - !isStreaming, - onRegenerate: onRegenerate, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - IgnorePointer( - ignoring: UniversalPlatform.isMobile, - child: AIMarkdownText(markdown: state.text), - ), - if (state.sources.isNotEmpty) - AIMessageMetadata( - sources: state.sources, - onSelectedMetadata: onSelectedMetadata, + return BlocListener( + listenWhen: (previous, current) => + previous.clearErrorMessages != current.clearErrorMessages, + listener: (context, chatState) { + if (state.stream?.error?.isEmpty != false) { + return; + } + context.read().add(ChatEvent.deleteMessage(message)); + }, + child: Padding( + padding: AIChatUILayout.messageMargin, + child: state.messageState.when( + loading: () => ChatAIMessageBubble( + message: message, + showActions: false, + child: Padding( + padding: const EdgeInsets.only(top: 8.0), + child: AILoadingIndicator(text: loadingText), + ), + ), + ready: () { + return state.text.isEmpty + ? ChatAIMessageBubble( + message: message, + showActions: false, + child: Padding( + padding: const EdgeInsets.only(top: 8.0), + child: AILoadingIndicator(text: loadingText), + ), + ) + : ChatAIMessageBubble( + message: message, + isLastMessage: isLastMessage, + showActions: stream == null && + state.text.isNotEmpty && + !isStreaming, + isSelectingMessages: isSelectingMessages, + onRegenerate: onRegenerate, + onChangeFormat: onChangeFormat, + onChangeModel: onChangeModel, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AIMarkdownText( + markdown: state.text, ), - if (state.sources.isNotEmpty && !isLastMessage) - const VSpace(8.0), - ], - ), - ); - }, - onError: (error) { - return ChatErrorMessageWidget( - errorMessage: LocaleKeys.chat_aiServerUnavailable.tr(), - ); - }, - onAIResponseLimit: () { - return ChatErrorMessageWidget( - errorMessage: - LocaleKeys.sideBar_askOwnerToUpgradeToAIMax.tr(), - ); - }, + if (state.sources.isNotEmpty) + SelectionContainer.disabled( + child: AIMessageMetadata( + sources: state.sources, + onSelectedMetadata: onSelectedMetadata, + ), + ), + if (state.sources.isNotEmpty && !isLastMessage) + const VSpace(8.0), + ], + ), + ); + }, + onError: (error) { + return ChatErrorMessageWidget( + errorMessage: LocaleKeys.chat_aiServerUnavailable.tr(), + ); + }, + onAIResponseLimit: () { + return ChatErrorMessageWidget( + errorMessage: + LocaleKeys.sideBar_askOwnerToUpgradeToAIMax.tr(), + ); + }, + onAIImageResponseLimit: () { + return ChatErrorMessageWidget( + errorMessage: LocaleKeys.sideBar_purchaseAIMax.tr(), + ); + }, + onAIMaxRequired: (message) { + return ChatErrorMessageWidget( + errorMessage: message, + ); + }, + onInitializingLocalAI: () { + onStopStream(); + + return ChatErrorMessageWidget( + errorMessage: LocaleKeys + .settings_aiPage_keys_localAIInitializing + .tr(), + ); + }, + ), ), ); }, diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/error_text_message.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/error_text_message.dart index 66b39fe308..6056ffffa6 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/error_text_message.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/error_text_message.dart @@ -58,7 +58,7 @@ class _ChatErrorMessageWidgetState extends State { mainAxisSize: MainAxisSize.min, children: [ const FlowySvg( - FlowySvgs.warning_filled_s, + FlowySvgs.toast_error_filled_s, blendMode: null, ), const HSpace(8.0), diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/message_util.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/message_util.dart index 4673b9def1..652fe3791b 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/message_util.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/message_util.dart @@ -1,15 +1,22 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/application/mobile_router.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; -import 'package:flutter/widgets.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; import 'package:universal_platform/universal_platform.dart'; /// Opens a message in the right hand sidebar on desktop, and push the page /// on mobile void openPageFromMessage(BuildContext context, ViewPB? view) { if (view == null) { + showToastNotification( + message: LocaleKeys.chat_openPagePreviewFailedToast.tr(), + type: ToastificationType.error, + ); return; } if (UniversalPlatform.isDesktop) { @@ -22,3 +29,31 @@ void openPageFromMessage(BuildContext context, ViewPB? view) { context.pushView(view); } } + +void showSaveMessageSuccessToast(BuildContext context, ViewPB? view) { + if (view == null) { + return; + } + showToastNotification( + richMessage: TextSpan( + children: [ + TextSpan( + text: LocaleKeys.chat_addToNewPageSuccessToast.tr(), + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: const Color(0xFFFFFFFF), + ), + ), + const TextSpan( + text: ' ', + ), + TextSpan( + text: view.nameOrDefault, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: const Color(0xFFFFFFFF), + fontWeight: FontWeight.w700, + ), + ), + ], + ), + ); +} diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/user_message_bubble.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/user_message_bubble.dart index 778ca3d0e9..8bd115ad0f 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/user_message_bubble.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/user_message_bubble.dart @@ -15,13 +15,11 @@ class ChatUserMessageBubble extends StatelessWidget { super.key, required this.message, required this.child, - required this.isCurrentUser, this.files = const [], }); final Message message; final Widget child; - final bool isCurrentUser; final List files; @override @@ -44,40 +42,28 @@ class ChatUserMessageBubble extends StatelessWidget { const VSpace(6), ], Row( - mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, - children: getChildren(context), + mainAxisAlignment: MainAxisAlignment.end, + children: [ + _buildBubble(context), + const HSpace(DesktopAIChatSizes.avatarAndChatBubbleSpacing), + _buildAvatar(), + ], ), ], ), ); } - List getChildren(BuildContext context) { - if (isCurrentUser) { - return [ - const Spacer(), - _buildBubble(context), - const HSpace(DesktopAIConvoSizes.avatarAndChatBubbleSpacing), - _buildAvatar(), - ]; - } else { - return [ - _buildAvatar(), - const HSpace(DesktopAIConvoSizes.avatarAndChatBubbleSpacing), - _buildBubble(context), - const Spacer(), - ]; - } - } - Widget _buildAvatar() { return BlocBuilder( builder: (context, state) { final member = state.members[message.author.id]; - return ChatUserAvatar( - iconUrl: member?.info.avatarUrl ?? "", - name: member?.info.name ?? "", + return SelectionContainer.disabled( + child: ChatUserAvatar( + iconUrl: member?.info.avatarUrl ?? "", + name: member?.info.name ?? "", + ), ); }, ); diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/user_text_message.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/user_text_message.dart index ae0d2863ce..c73100b59d 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/user_text_message.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/message/user_text_message.dart @@ -15,12 +15,10 @@ class ChatUserMessageWidget extends StatelessWidget { super.key, required this.user, required this.message, - required this.isCurrentUser, }); final User user; final TextMessage message; - final bool isCurrentUser; @override Widget build(BuildContext context) { @@ -34,7 +32,6 @@ class ChatUserMessageWidget extends StatelessWidget { ), child: ChatUserMessageBubble( message: message, - isCurrentUser: isCurrentUser, files: _getFiles(), child: BlocBuilder( builder: (context, state) { @@ -83,7 +80,6 @@ class TextMessageText extends StatelessWidget { text, lineHeight: 1.4, maxLines: null, - selectable: true, color: AFThemeExtension.of(context).textColor, ); } diff --git a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/scroll_to_bottom.dart b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/scroll_to_bottom.dart index 0cfa2efe7b..d66a6665b3 100644 --- a/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/scroll_to_bottom.dart +++ b/frontend/appflowy_flutter/lib/plugins/ai_chat/presentation/scroll_to_bottom.dart @@ -41,21 +41,21 @@ class CustomScrollToBottom extends StatelessWidget { spreadRadius: 8, color: isLightMode ? const Color(0x0F1F2329) - : Theme.of(context).shadowColor.withOpacity(0.06), + : Theme.of(context).shadowColor.withValues(alpha: 0.06), ), BoxShadow( offset: const Offset(0, 4), blurRadius: 8, color: isLightMode ? const Color(0x141F2329) - : Theme.of(context).shadowColor.withOpacity(0.08), + : Theme.of(context).shadowColor.withValues(alpha: 0.08), ), BoxShadow( offset: const Offset(0, 2), blurRadius: 4, color: isLightMode ? const Color(0x1F1F2329) - : Theme.of(context).shadowColor.withOpacity(0.12), + : Theme.of(context).shadowColor.withValues(alpha: 0.12), ), ], ), diff --git a/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker.dart b/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker.dart index 517a0d68fa..27b288090a 100644 --- a/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker.dart +++ b/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker.dart @@ -13,6 +13,18 @@ import 'package:flutter_emoji_mart/flutter_emoji_mart.dart'; EmojiData? kCachedEmojiData; const _kRecentEmojiCategoryId = 'Recent'; +class EmojiPickerResult { + EmojiPickerResult({ + required this.emojiId, + required this.emoji, + this.isRandom = false, + }); + + final String emojiId; + final String emoji; + final bool isRandom; +} + class FlowyEmojiPicker extends StatefulWidget { const FlowyEmojiPicker({ super.key, @@ -21,7 +33,7 @@ class FlowyEmojiPicker extends StatefulWidget { this.ensureFocus = false, }); - final EmojiSelectedCallback onEmojiSelected; + final ValueChanged onEmojiSelected; final int emojiPerLine; final bool ensureFocus; @@ -70,7 +82,9 @@ class _FlowyEmojiPickerState extends State { defaultSkinTone: lastSelectedEmojiSkinTone ?? EmojiSkinTone.none, ), onEmojiSelected: (id, emoji) { - widget.onEmojiSelected.call(id, emoji); + widget.onEmojiSelected.call( + EmojiPickerResult(emojiId: id, emoji: emoji), + ); RecentIcons.putEmoji(id); }, padding: const EdgeInsets.symmetric(horizontal: 16.0), @@ -106,7 +120,12 @@ class _FlowyEmojiPickerState extends State { onSkinToneChanged: (value) { skinTone.value = value; }, - onRandomEmojiSelected: widget.onEmojiSelected, + onRandomEmojiSelected: (id, emoji) { + widget.onEmojiSelected.call( + EmojiPickerResult(emojiId: id, emoji: emoji, isRandom: true), + ); + RecentIcons.putEmoji(id); + }, ), ); }, @@ -117,7 +136,7 @@ class _FlowyEmojiPickerState extends State { RecentIcons.getEmojiIds().then((v) { if (v.isEmpty) { emojiData = data; - setState(() => loaded = true); + if (mounted) setState(() => loaded = true); return; } final categories = List.of(data.categories); @@ -129,7 +148,7 @@ class _FlowyEmojiPickerState extends State { ), ); emojiData = EmojiData(categories: categories, emojis: data.emojis); - setState(() => loaded = true); + if (mounted) setState(() => loaded = true); }); } } diff --git a/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker_screen.dart b/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker_screen.dart index 513b0fd224..47f257d174 100644 --- a/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker_screen.dart +++ b/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker_screen.dart @@ -11,15 +11,21 @@ class MobileEmojiPickerScreen extends StatelessWidget { const MobileEmojiPickerScreen({ super.key, this.title, + this.selectedType, + this.documentId, this.tabs = const [PickerTabType.emoji, PickerTabType.icon], }); + final PickerTabType? selectedType; final String? title; + final String? documentId; final List tabs; static const routeName = '/emoji_picker'; static const pageTitle = 'title'; + static const iconSelectedType = 'iconSelected_type'; static const selectTabs = 'tabs'; + static const uploadDocumentId = 'document_id'; @override Widget build(BuildContext context) { @@ -30,8 +36,10 @@ class MobileEmojiPickerScreen extends StatelessWidget { body: SafeArea( child: FlowyIconEmojiPicker( tabs: tabs, + documentId: documentId, + initialType: selectedType, onSelectedEmoji: (r) { - context.pop(r); + context.pop(r.data); }, ), ), diff --git a/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_text.dart b/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_text.dart index 884bd73151..9df541f4a2 100644 --- a/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_text.dart +++ b/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_text.dart @@ -32,6 +32,7 @@ class EmojiText extends StatelessWidget { strutStyle: const StrutStyle(forceStrutHeight: true), fallbackFontFamily: _cachedFallbackFontFamily, lineHeight: lineHeight, + isEmoji: true, ); } diff --git a/frontend/appflowy_flutter/lib/plugins/base/icon/icon_widget.dart b/frontend/appflowy_flutter/lib/plugins/base/icon/icon_widget.dart index 8f647e2a38..630219e060 100644 --- a/frontend/appflowy_flutter/lib/plugins/base/icon/icon_widget.dart +++ b/frontend/appflowy_flutter/lib/plugins/base/icon/icon_widget.dart @@ -1,27 +1,32 @@ +import 'package:appflowy/plugins/base/emoji/emoji_text.dart'; import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart'; import 'package:flutter/material.dart'; import '../../../generated/flowy_svgs.g.dart'; class IconWidget extends StatelessWidget { - const IconWidget({ - super.key, - required this.size, - required this.data, - }); + const IconWidget({super.key, required this.size, required this.iconsData}); - final IconsData data; + final IconsData iconsData; final double size; @override Widget build(BuildContext context) { - final colorValue = int.tryParse(data.color ?? ''); + final colorValue = int.tryParse(iconsData.color ?? ''); Color? color; if (colorValue != null) { color = Color(colorValue); } + final svgString = iconsData.svgString; + if (svgString == null) { + return EmojiText( + emoji: '❓', + fontSize: size, + textAlign: TextAlign.center, + ); + } return FlowySvg.string( - data.iconContent, + svgString, size: Size.square(size), color: color, ); diff --git a/frontend/appflowy_flutter/lib/plugins/blank/blank.dart b/frontend/appflowy_flutter/lib/plugins/blank/blank.dart index b25bb5af06..ebda487515 100644 --- a/frontend/appflowy_flutter/lib/plugins/blank/blank.dart +++ b/frontend/appflowy_flutter/lib/plugins/blank/blank.dart @@ -36,7 +36,7 @@ class BlankPagePlugin extends Plugin { PluginWidgetBuilder get widgetBuilder => BlankPagePluginWidgetBuilder(); @override - PluginId get id => "BlankStack"; + PluginId get id => ""; @override PluginType get pluginType => PluginType.blank; diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/number_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/number_cell_bloc.dart index df159b817b..73b2d2977b 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/number_cell_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/number_cell_bloc.dart @@ -47,15 +47,6 @@ class NumberCellBloc extends Bloc { if (state.content != text) { emit(state.copyWith(content: text)); await cellController.saveCellData(text); - - // If the input content is "abc" that can't parsered as number then the data stored in the backend will be an empty string. - // So for every cell data that will be formatted in the backend. - // It needs to get the formatted data after saving. - add( - NumberCellEvent.didReceiveCellUpdate( - cellController.getCellData(), - ), - ); } }, ); diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/relation_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/relation_cell_bloc.dart index 70c5e074ab..ec789b03a0 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/relation_cell_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/relation_cell_bloc.dart @@ -143,12 +143,11 @@ class RelationCellBloc extends Bloc { (f) => null, ); if (databaseMeta != null) { - final result = - await ViewBackendService.getView(databaseMeta.inlineViewId); + final result = await ViewBackendService.getView(databaseMeta.viewId); return result.fold( (s) => DatabaseMeta( databaseId: databaseId, - inlineViewId: databaseMeta.inlineViewId, + viewId: databaseMeta.viewId, databaseName: s.name, ), (f) => null, diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/select_option_cell_editor_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/select_option_cell_editor_bloc.dart index f8ed915b62..c6e4e6484b 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/select_option_cell_editor_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/select_option_cell_editor_bloc.dart @@ -241,6 +241,11 @@ class SelectOptionCellEditorBloc } else if (!state.selectedOptions .any((option) => option.id == focusedOptionId)) { _selectOptionService.select(optionIds: [focusedOptionId]); + emit( + state.copyWith( + clearFilter: true, + ), + ); } } diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/database_controller.dart b/frontend/appflowy_flutter/lib/plugins/database/application/database_controller.dart index 5d0bb760fe..5317539128 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/database_controller.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/database_controller.dart @@ -1,7 +1,5 @@ import 'dart:async'; -import 'package:flutter/material.dart'; - import 'package:appflowy/plugins/database/application/field/field_controller.dart'; import 'package:appflowy/plugins/database/application/view/view_cache.dart'; import 'package:appflowy/plugins/database/domain/database_view_service.dart'; @@ -14,6 +12,7 @@ import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_result/appflowy_result.dart'; import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; import 'defines.dart'; import 'row/row_cache.dart'; @@ -41,7 +40,9 @@ class GroupCallbacks { } class DatabaseLayoutSettingCallbacks { - DatabaseLayoutSettingCallbacks({required this.onLayoutSettingsChanged}); + DatabaseLayoutSettingCallbacks({ + required this.onLayoutSettingsChanged, + }); final void Function(DatabaseLayoutSettingPB) onLayoutSettingsChanged; } @@ -97,9 +98,11 @@ class DatabaseController { final List _databaseCallbacks = []; final List _groupCallbacks = []; final List _layoutCallbacks = []; + final Set> _compactModeCallbacks = {}; // Getters RowCache get rowCache => _viewCache.rowCache; + String get viewId => view.id; // Listener @@ -107,17 +110,26 @@ class DatabaseController { final DatabaseLayoutSettingListener _layoutListener; final ValueNotifier _isLoading = ValueNotifier(true); + final ValueNotifier _compactMode = ValueNotifier(true); - void setIsLoading(bool isLoading) { - _isLoading.value = isLoading; - } + void setIsLoading(bool isLoading) => _isLoading.value = isLoading; ValueNotifier get isLoading => _isLoading; + void setCompactMode(bool compactMode) { + _compactMode.value = compactMode; + for (final callback in Set.of(_compactModeCallbacks)) { + callback.call(compactMode); + } + } + + ValueNotifier get compactModeNotifier => _compactMode; + void addListener({ DatabaseCallbacks? onDatabaseChanged, DatabaseLayoutSettingCallbacks? onLayoutSettingsChanged, GroupCallbacks? onGroupChanged, + ValueChanged? onCompactModeChanged, }) { if (onLayoutSettingsChanged != null) { _layoutCallbacks.add(onLayoutSettingsChanged); @@ -130,12 +142,17 @@ class DatabaseController { if (onGroupChanged != null) { _groupCallbacks.add(onGroupChanged); } + + if (onCompactModeChanged != null) { + _compactModeCallbacks.add(onCompactModeChanged); + } } void removeListener({ DatabaseCallbacks? onDatabaseChanged, DatabaseLayoutSettingCallbacks? onLayoutSettingsChanged, GroupCallbacks? onGroupChanged, + ValueChanged? onCompactModeChanged, }) { if (onDatabaseChanged != null) { _databaseCallbacks.remove(onDatabaseChanged); @@ -148,6 +165,10 @@ class DatabaseController { if (onGroupChanged != null) { _groupCallbacks.remove(onGroupChanged); } + + if (onCompactModeChanged != null) { + _compactModeCallbacks.remove(onCompactModeChanged); + } } Future> open() async { @@ -242,6 +263,7 @@ class DatabaseController { _databaseCallbacks.clear(); _groupCallbacks.clear(); _layoutCallbacks.clear(); + _compactModeCallbacks.clear(); _isLoading.dispose(); } @@ -376,4 +398,10 @@ class DatabaseController { }, ); } + + void initCompactMode(bool enableCompactMode) { + if (_compactMode.value != enableCompactMode) { + _compactMode.value = enableCompactMode; + } + } } diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/field/field_controller.dart b/frontend/appflowy_flutter/lib/plugins/database/application/field/field_controller.dart index 8370bd9bff..93fd69bcfc 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/field/field_controller.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/field/field_controller.dart @@ -411,23 +411,28 @@ class FieldController { /// Listen for field setting changes in the backend. void _listenOnFieldSettingsChanged() { FieldInfo? updateFieldSettings(FieldSettingsPB updatedFieldSettings) { - final List newFields = fieldInfos; - var updatedField = newFields.firstOrNull; + final newFields = [...fieldInfos]; - if (updatedField == null) { + if (newFields.isEmpty) { return null; } final index = newFields .indexWhere((field) => field.id == updatedFieldSettings.fieldId); + if (index != -1) { newFields[index] = newFields[index].copyWith(fieldSettings: updatedFieldSettings); - updatedField = newFields[index]; + _fieldNotifier.fieldInfos = newFields; + _fieldSettings + ..removeWhere( + (field) => field.fieldId == updatedFieldSettings.fieldId, + ) + ..add(updatedFieldSettings); + return newFields[index]; } - _fieldNotifier.fieldInfos = newFields; - return updatedField; + return null; } _fieldSettingsListener.start( diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/field/type_option/relation_type_option_cubit.dart b/frontend/appflowy_flutter/lib/plugins/database/application/field/type_option/relation_type_option_cubit.dart index 691b6b7227..4ddde80b79 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/field/type_option/relation_type_option_cubit.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/field/type_option/relation_type_option_cubit.dart @@ -17,11 +17,11 @@ class RelationDatabaseListCubit extends Cubit { .send() .fold>((s) => s.items, (f) => []); final futures = metaPBs.map((meta) { - return ViewBackendService.getView(meta.inlineViewId).then( + return ViewBackendService.getView(meta.viewId).then( (result) => result.fold( (s) => DatabaseMeta( databaseId: meta.databaseId, - inlineViewId: meta.inlineViewId, + viewId: meta.viewId, databaseName: s.name, ), (f) => null, @@ -43,10 +43,10 @@ class DatabaseMeta with _$DatabaseMeta { /// id of the database required String databaseId, - /// id of the inline view - required String inlineViewId, + /// id of the view + required String viewId, - /// name of the database, currently identical to the name of the inline view + /// name of the database required String databaseName, }) = _DatabaseMeta; } diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/layout/layout_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/layout/layout_bloc.dart index a5dd0d9ca1..0f884a1e9a 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/layout/layout_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/layout/layout_bloc.dart @@ -31,6 +31,7 @@ class DatabaseLayoutBloc @freezed class DatabaseLayoutEvent with _$DatabaseLayoutEvent { const factory DatabaseLayoutEvent.initial() = _Initial; + const factory DatabaseLayoutEvent.updateLayout(DatabaseLayoutPB layout) = _UpdateLayout; } diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/row/related_row_detail_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/row/related_row_detail_bloc.dart index 4f975cd1a6..f735618dd8 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/row/related_row_detail_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/row/related_row_detail_bloc.dart @@ -73,27 +73,24 @@ class RelatedRowDetailPageBloc }); } - /// initialize bloc through the `database_id` and `row_id`. The process is as - /// follows: - /// 1. use the `database_id` to get the database meta, which contains the - /// `inline_view_id` - /// 2. use the `inline_view_id` to instantiate a `DatabaseController`. - /// 3. use the `row_id` with the DatabaseController` to create `RowController` void _init(String databaseId, String initialRowId) async { - final databaseMeta = - await DatabaseEventGetDatabaseMeta(DatabaseIdPB(value: databaseId)) - .send() - .fold((s) => s, (f) => null); - if (databaseMeta == null) { + final viewId = await DatabaseEventGetDefaultDatabaseViewId( + DatabaseIdPB(value: databaseId), + ).send().fold( + (pb) => pb.value, + (error) => null, + ); + + if (viewId == null) { return; } - final inlineView = - await ViewBackendService.getView(databaseMeta.inlineViewId) - .fold((viewPB) => viewPB, (f) => null); - if (inlineView == null) { + + final databaseView = await ViewBackendService.getView(viewId) + .fold((viewPB) => viewPB, (f) => null); + if (databaseView == null) { return; } - final databaseController = DatabaseController(view: inlineView); + final databaseController = DatabaseController(view: databaseView); await databaseController.open().fold( (s) => databaseController.setIsLoading(false), (f) => null, @@ -104,7 +101,7 @@ class RelatedRowDetailPageBloc } final rowController = RowController( rowMeta: rowInfo.rowMeta, - viewId: inlineView.id, + viewId: databaseView.id, rowCache: databaseController.rowCache, ); diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/sync/database_sync_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/sync/database_sync_bloc.dart index ae0b9173c7..351dea2cd8 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/sync/database_sync_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/sync/database_sync_bloc.dart @@ -30,9 +30,9 @@ class DatabaseSyncBloc extends Bloc { .then((value) => value.fold((s) => s, (f) => null)); emit( state.copyWith( - shouldShowIndicator: userProfile?.authenticator == - AuthenticatorPB.AppFlowyCloud && - databaseId != null, + shouldShowIndicator: + userProfile?.workspaceAuthType == AuthTypePB.Server && + databaseId != null, ), ); if (databaseId != null) { diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/tab_bar_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/tab_bar_bloc.dart index bb892f4b47..e55bbb96a4 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/tab_bar_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/tab_bar_bloc.dart @@ -1,6 +1,7 @@ import 'package:appflowy/plugins/database/domain/database_view_service.dart'; import 'package:appflowy/plugins/database/tab_bar/tab_bar_view.dart'; import 'package:appflowy/plugins/database/widgets/database_layout_ext.dart'; +import 'package:appflowy/plugins/document/presentation/compact_mode_event.dart'; import 'package:appflowy/workspace/application/view/prelude.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy_backend/log.dart'; @@ -17,8 +18,17 @@ part 'tab_bar_bloc.freezed.dart'; class DatabaseTabBarBloc extends Bloc { - DatabaseTabBarBloc({required ViewPB view}) - : super(DatabaseTabBarState.initial(view)) { + DatabaseTabBarBloc({ + required ViewPB view, + required String compactModeId, + required bool enableCompactMode, + }) : super( + DatabaseTabBarState.initial( + view, + compactModeId, + enableCompactMode, + ), + ) { on( (event, emit) async { await event.when( @@ -154,10 +164,13 @@ class DatabaseTabBarBloc ) { final tabBarControllerByViewId = {...state.tabBarControllerByViewId}; for (final view in newViews) { - final controller = DatabaseTabBarController(view: view); - controller.onViewUpdated = (newView) { - add(DatabaseTabBarEvent.viewDidUpdate(newView)); - }; + final controller = DatabaseTabBarController( + view: view, + compactModeId: state.compactModeId, + enableCompactMode: state.enableCompactMode, + )..onViewUpdated = (newView) { + add(DatabaseTabBarEvent.viewDidUpdate(newView)); + }; tabBarControllerByViewId[view.id] = controller; } @@ -205,20 +218,27 @@ class DatabaseTabBarBloc @freezed class DatabaseTabBarEvent with _$DatabaseTabBarEvent { const factory DatabaseTabBarEvent.initial() = _Initial; + const factory DatabaseTabBarEvent.didLoadChildViews( List childViews, ) = _DidLoadChildViews; + const factory DatabaseTabBarEvent.selectView(String viewId) = _DidSelectView; + const factory DatabaseTabBarEvent.createView( DatabaseLayoutPB layout, String? name, ) = _CreateView; + const factory DatabaseTabBarEvent.renameView(String viewId, String newName) = _RenameView; + const factory DatabaseTabBarEvent.deleteView(String viewId) = _DeleteView; + const factory DatabaseTabBarEvent.didUpdateChildViews( ChildViewUpdatePB updatePB, ) = _DidUpdateChildViews; + const factory DatabaseTabBarEvent.viewDidUpdate(ViewPB view) = _ViewDidUpdate; } @@ -227,19 +247,29 @@ class DatabaseTabBarState with _$DatabaseTabBarState { const factory DatabaseTabBarState({ required ViewPB parentView, required int selectedIndex, + required String compactModeId, + required bool enableCompactMode, required List tabBars, required Map tabBarControllerByViewId, }) = _DatabaseTabBarState; - factory DatabaseTabBarState.initial(ViewPB view) { + factory DatabaseTabBarState.initial( + ViewPB view, + String compactModeId, + bool enableCompactMode, + ) { final tabBar = DatabaseTabBar(view: view); return DatabaseTabBarState( parentView: view, selectedIndex: 0, + compactModeId: compactModeId, + enableCompactMode: enableCompactMode, tabBars: [tabBar], tabBarControllerByViewId: { view.id: DatabaseTabBarController( view: view, + compactModeId: compactModeId, + enableCompactMode: enableCompactMode, ), }, ); @@ -257,7 +287,9 @@ class DatabaseTabBar extends Equatable { final DatabaseTabBarItemBuilder _builder; String get viewId => view.id; + DatabaseTabBarItemBuilder get builder => _builder; + ViewLayoutPB get layout => view.layout; @override @@ -274,8 +306,18 @@ typedef OnViewChildViewChanged = void Function( ); class DatabaseTabBarController { - DatabaseTabBarController({required this.view}) - : controller = DatabaseController(view: view), + DatabaseTabBarController({ + required this.view, + required String compactModeId, + required bool enableCompactMode, + }) : controller = DatabaseController(view: view) + ..initCompactMode(enableCompactMode) + ..addListener( + onCompactModeChanged: (v) async { + compactModeEventBus + .fire(CompactModeEvent(id: compactModeId, enable: v)); + }, + ), viewListener = ViewListener(viewId: view.id) { viewListener.start( onViewChildViewsUpdated: (update) => onViewChildViewChanged?.call(update), diff --git a/frontend/appflowy_flutter/lib/plugins/database/board/presentation/board_page.dart b/frontend/appflowy_flutter/lib/plugins/database/board/presentation/board_page.dart index b373e33b2f..70d00bcd25 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/board/presentation/board_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/board/presentation/board_page.dart @@ -1,9 +1,5 @@ import 'dart:io'; -import 'package:appflowy/util/field_type_extension.dart'; -import 'package:flutter/material.dart' hide Card; -import 'package:flutter/services.dart'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/database/board/mobile_board_page.dart'; @@ -19,6 +15,9 @@ import 'package:appflowy/plugins/database/widgets/cell/card_cell_style_maps/desk import 'package:appflowy/plugins/database/widgets/row/row_detail.dart'; import 'package:appflowy/shared/conditional_listenable_builder.dart'; import 'package:appflowy/shared/flowy_error_page.dart'; +import 'package:appflowy/util/field_type_extension.dart'; +import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; +import 'package:appflowy/workspace/application/view/view_lock_status_bloc.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_board/appflowy_board.dart'; @@ -26,13 +25,14 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; +import 'package:flutter/material.dart' hide Card; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:universal_platform/universal_platform.dart'; import '../../widgets/card/card.dart'; import '../../widgets/cell/card_cell_builder.dart'; import '../application/board_bloc.dart'; - import 'toolbar/board_setting_bar.dart'; import 'widgets/board_focus_scope.dart'; import 'widgets/board_hidden_groups.dart'; @@ -206,6 +206,7 @@ class _DesktopBoardPageState extends State { onEditStateChanged: widget.onEditStateChanged, focusScope: _focusScope, boardController: _boardController, + view: widget.view, ), ), ), @@ -240,6 +241,7 @@ class _BoardContent extends StatefulWidget { const _BoardContent({ required this.boardController, required this.focusScope, + required this.view, this.onEditStateChanged, this.shrinkWrap = false, }); @@ -248,6 +250,7 @@ class _BoardContent extends StatefulWidget { final BoardFocusScope focusScope; final VoidCallback? onEditStateChanged; final bool shrinkWrap; + final ViewPB view; @override State<_BoardContent> createState() => _BoardContentState(); @@ -282,6 +285,9 @@ class _BoardContentState extends State<_BoardContent> { @override Widget build(BuildContext context) { + final horizontalPadding = + context.read()?.horizontalPadding ?? + 0.0; return MultiBlocListener( listeners: [ BlocListener( @@ -334,57 +340,92 @@ class _BoardContentState extends State<_BoardContent> { focusScope: widget.focusScope, child: Padding( padding: const EdgeInsets.only(top: 8.0), - child: AppFlowyBoard( - boardScrollController: scrollManager, - scrollController: scrollController, - controller: context.read().boardController, - groupConstraints: const BoxConstraints.tightFor(width: 256), - config: config, - leading: HiddenGroupsColumn(margin: config.groupHeaderPadding), - trailing: context - .read() - .groupingFieldType - ?.canCreateNewGroup ?? - false - ? BoardTrailing(scrollController: scrollController) - : const HSpace(40), - headerBuilder: (_, groupData) => BlocProvider.value( - value: context.read(), - child: BoardColumnHeader( - databaseController: databaseController, - groupData: groupData, - margin: config.groupHeaderPadding, - ), - ), - footerBuilder: (_, groupData) => MultiBlocProvider( - providers: [ - BlocProvider.value(value: context.read()), - BlocProvider.value(value: context.read()), - ], - child: BoardColumnFooter( - columnData: groupData, - boardConfig: config, - scrollManager: scrollManager, - ), - ), - cardBuilder: (_, column, columnItem) => MultiBlocProvider( - key: ValueKey("board_card_${column.id}_${columnItem.id}"), - providers: [ - BlocProvider.value( + child: ValueListenableBuilder( + valueListenable: databaseController.compactModeNotifier, + builder: (context, compactMode, _) { + return AppFlowyBoard( + boardScrollController: scrollManager, + scrollController: scrollController, + shrinkWrap: widget.shrinkWrap, + controller: context.read().boardController, + groupConstraints: + BoxConstraints.tightFor(width: compactMode ? 196 : 256), + config: config, + leading: HiddenGroupsColumn( + shrinkWrap: widget.shrinkWrap, + margin: config.groupHeaderPadding + + EdgeInsets.only( + left: widget.shrinkWrap ? horizontalPadding : 0.0, + ), + ), + trailing: context + .read() + .groupingFieldType + ?.canCreateNewGroup ?? + false + ? BoardTrailing(scrollController: scrollController) + : const HSpace(40), + headerBuilder: (_, groupData) => BlocProvider.value( value: context.read(), + child: BoardColumnHeader( + databaseController: databaseController, + groupData: groupData, + margin: config.groupHeaderPadding, + ), ), - BlocProvider.value( - value: context.read(), + footerBuilder: (_, groupData) => MultiBlocProvider( + providers: [ + BlocProvider.value(value: context.read()), + BlocProvider.value( + value: context.read(), + ), + ], + child: BoardColumnFooter( + columnData: groupData, + boardConfig: config, + scrollManager: scrollManager, + ), ), - ], - child: _BoardCard( - afGroupData: column, - groupItem: columnItem as GroupItem, - boardConfig: config, - notifier: widget.focusScope, - cellBuilder: cellBuilder, - ), - ), + cardBuilder: (cardContext, column, columnItem) => + MultiBlocProvider( + key: ValueKey("board_card_${column.id}_${columnItem.id}"), + providers: [ + BlocProvider.value( + value: cardContext.read(), + ), + BlocProvider.value( + value: cardContext.read(), + ), + BlocProvider( + create: (_) => ViewLockStatusBloc(view: widget.view) + ..add(ViewLockStatusEvent.initial()), + ), + ], + child: BlocBuilder( + builder: (lockStatusContext, state) { + return IgnorePointer( + ignoring: state.isLocked, + child: _BoardCard( + afGroupData: column, + groupItem: columnItem as GroupItem, + boardConfig: config, + notifier: widget.focusScope, + cellBuilder: cellBuilder, + compactMode: compactMode, + onOpenCard: (rowMeta) => _openCard( + context: context, + databaseController: lockStatusContext + .read() + .databaseController, + rowMeta: rowMeta, + ), + ), + ); + }, + ), + ), + ); + }, ), ), ), @@ -546,6 +587,8 @@ class _BoardCard extends StatefulWidget { required this.boardConfig, required this.cellBuilder, required this.notifier, + required this.compactMode, + required this.onOpenCard, }); final AppFlowyGroupData afGroupData; @@ -553,6 +596,8 @@ class _BoardCard extends StatefulWidget { final AppFlowyBoardConfig boardConfig; final CardCellBuilder cellBuilder; final BoardFocusScope notifier; + final bool compactMode; + final void Function(RowMetaPB) onOpenCard; @override State<_BoardCard> createState() => _BoardCardState(); @@ -639,15 +684,21 @@ class _BoardCardState extends State<_BoardCard> { return previousContainsFocus != currentContainsFocus; }, - builder: (context, focusedItems, child) => Container( - margin: widget.boardConfig.cardMargin, - decoration: _makeBoxDecoration( - context, - groupData.group.groupId, - widget.groupItem.id, - ), - child: child, - ), + builder: (context, focusedItems, child) { + final cardMargin = widget.boardConfig.cardMargin; + final margin = widget.compactMode + ? cardMargin - EdgeInsets.symmetric(horizontal: 2) + : cardMargin; + return Container( + margin: margin, + decoration: _makeBoxDecoration( + context, + groupData.group.groupId, + widget.groupItem.id, + ), + child: child, + ); + }, child: RowCard( fieldController: databaseController.fieldController, rowMeta: rowMeta, @@ -656,10 +707,8 @@ class _BoardCardState extends State<_BoardCard> { groupingFieldId: widget.groupItem.fieldInfo.id, isEditing: _isEditing, cellBuilder: widget.cellBuilder, - onTap: (context) => _openCard( - context: context, - databaseController: databaseController, - rowMeta: context.read().rowController.rowMeta, + onTap: (context) => widget.onOpenCard( + context.read().rowController.rowMeta, ), onShiftTap: (_) { Focus.of(context).requestFocus(); @@ -714,19 +763,19 @@ class _BoardCardState extends State<_BoardCard> { .isFocused(GroupedRowId(rowId: rowId, groupId: groupId)) ? Theme.of(context).colorScheme.primary : Theme.of(context).brightness == Brightness.light - ? const Color(0xFF1F2329).withOpacity(0.12) + ? const Color(0xFF1F2329).withValues(alpha: 0.12) : const Color(0xFF59647A), ), ), boxShadow: [ BoxShadow( blurRadius: 4, - color: const Color(0xFF1F2329).withOpacity(0.02), + color: const Color(0xFF1F2329).withValues(alpha: 0.02), ), BoxShadow( blurRadius: 4, spreadRadius: -2, - color: const Color(0xFF1F2329).withOpacity(0.02), + color: const Color(0xFF1F2329).withValues(alpha: 0.02), ), ], ); @@ -805,7 +854,7 @@ class _BoardTrailingState extends State { suffixIcon: Padding( padding: const EdgeInsets.only(left: 4, bottom: 8.0), child: FlowyIconButton( - icon: const FlowySvg(FlowySvgs.close_filled_m), + icon: const FlowySvg(FlowySvgs.close_filled_s), hoverColor: Colors.transparent, onPressed: () => _textController.clear(), ), @@ -856,10 +905,13 @@ void _openCard({ FlowyOverlay.show( context: context, - builder: (_) => RowDetailPage( - databaseController: databaseController, - rowController: rowController, - userProfile: context.read().userProfile, + builder: (_) => BlocProvider.value( + value: context.read(), + child: RowDetailPage( + databaseController: databaseController, + rowController: rowController, + userProfile: context.read().userProfile, + ), ), ); } diff --git a/frontend/appflowy_flutter/lib/plugins/database/board/presentation/toolbar/board_setting_bar.dart b/frontend/appflowy_flutter/lib/plugins/database/board/presentation/toolbar/board_setting_bar.dart index 9e3203e093..e57364b2d8 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/board/presentation/toolbar/board_setting_bar.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/board/presentation/toolbar/board_setting_bar.dart @@ -2,10 +2,13 @@ import 'package:appflowy/plugins/database/application/database_controller.dart'; import 'package:appflowy/plugins/database/grid/application/filter/filter_editor_bloc.dart'; import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart'; import 'package:appflowy/plugins/database/grid/presentation/widgets/toolbar/filter_button.dart'; +import 'package:appflowy/plugins/database/grid/presentation/widgets/toolbar/view_database_button.dart'; import 'package:appflowy/plugins/database/widgets/setting/setting_button.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:provider/provider.dart'; class BoardSettingBar extends StatelessWidget { const BoardSettingBar({ @@ -30,6 +33,8 @@ class BoardSettingBar extends StatelessWidget { if (value) { return const SizedBox.shrink(); } + final isReference = + Provider.of(context)?.isReference ?? false; return SizedBox( height: 20, child: Row( @@ -38,6 +43,10 @@ class BoardSettingBar extends StatelessWidget { FilterButton( toggleExtension: toggleExtension, ), + if (isReference) ...[ + const HSpace(2), + ViewDatabaseButton(view: databaseController.view), + ], const HSpace(2), SettingButton( databaseController: databaseController, diff --git a/frontend/appflowy_flutter/lib/plugins/database/board/presentation/widgets/board_hidden_groups.dart b/frontend/appflowy_flutter/lib/plugins/database/board/presentation/widgets/board_hidden_groups.dart index c2a90fb49e..1a0d1a3163 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/board/presentation/widgets/board_hidden_groups.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/board/presentation/widgets/board_hidden_groups.dart @@ -1,7 +1,5 @@ import 'dart:io'; -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database/application/cell/cell_controller.dart'; @@ -15,20 +13,24 @@ import 'package:appflowy/plugins/database/tab_bar/tab_bar_view.dart'; import 'package:appflowy/plugins/database/widgets/cell/card_cell_builder.dart'; import 'package:appflowy/plugins/database/widgets/cell/card_cell_skeleton/text_card_cell.dart'; import 'package:appflowy/plugins/database/widgets/row/row_detail.dart'; +import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class HiddenGroupsColumn extends StatelessWidget { const HiddenGroupsColumn({ super.key, required this.margin, + required this.shrinkWrap, }); final EdgeInsets margin; + final bool shrinkWrap; @override Widget build(BuildContext context) { @@ -84,11 +86,7 @@ class HiddenGroupsColumn extends StatelessWidget { ], ), ), - Expanded( - child: HiddenGroupList( - databaseController: databaseController, - ), - ), + _hiddenGroupList(databaseController), ], ), ), @@ -97,6 +95,14 @@ class HiddenGroupsColumn extends StatelessWidget { ); } + Widget _hiddenGroupList(DatabaseController databaseController) { + final hiddenGroupList = HiddenGroupList( + shrinkWrap: shrinkWrap, + databaseController: databaseController, + ); + return shrinkWrap ? hiddenGroupList : Expanded(child: hiddenGroupList); + } + Widget _collapseExpandIcon(BuildContext context, bool isCollapsed) { return FlowyTooltip( message: isCollapsed @@ -124,9 +130,11 @@ class HiddenGroupList extends StatelessWidget { const HiddenGroupList({ super.key, required this.databaseController, + required this.shrinkWrap, }); final DatabaseController databaseController; + final bool shrinkWrap; @override Widget build(BuildContext context) { @@ -149,6 +157,7 @@ class HiddenGroupList extends StatelessWidget { ], ), ), + shrinkWrap: shrinkWrap, buildDefaultDragHandles: false, itemCount: state.hiddenGroups.length, itemBuilder: (_, index) => Padding( @@ -426,10 +435,13 @@ class HiddenGroupPopupItemList extends StatelessWidget { onPressed: () { FlowyOverlay.show( context: context, - builder: (_) => RowDetailPage( - databaseController: databaseController, - rowController: rowController, - userProfile: context.read().userProfile, + builder: (_) => BlocProvider.value( + value: context.read(), + child: RowDetailPage( + databaseController: databaseController, + rowController: rowController, + userProfile: context.read().userProfile, + ), ), ); PopoverContainer.of(context).close(); diff --git a/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/calendar_day.dart b/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/calendar_day.dart index de5648291e..1d2838210d 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/calendar_day.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/calendar_day.dart @@ -256,16 +256,16 @@ class NewEventButton extends StatelessWidget { boxShadow: [ BoxShadow( spreadRadius: -2, - color: const Color(0xFF1F2329).withOpacity(0.02), + color: const Color(0xFF1F2329).withValues(alpha: 0.02), blurRadius: 2, ), BoxShadow( - color: const Color(0xFF1F2329).withOpacity(0.02), + color: const Color(0xFF1F2329).withValues(alpha: 0.02), blurRadius: 4, ), BoxShadow( spreadRadius: 2, - color: const Color(0xFF1F2329).withOpacity(0.02), + color: const Color(0xFF1F2329).withValues(alpha: 0.02), blurRadius: 8, ), ], diff --git a/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/calendar_event_card.dart b/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/calendar_event_card.dart index d4abb79a32..5ef2e2c327 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/calendar_event_card.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/calendar_event_card.dart @@ -1,24 +1,23 @@ -import 'package:appflowy/plugins/database/application/row/row_controller.dart'; -import 'package:appflowy/plugins/database/widgets/row/row_detail.dart'; -import 'package:flutter/material.dart'; - import 'package:appflowy/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart'; import 'package:appflowy/plugins/database/application/database_controller.dart'; import 'package:appflowy/plugins/database/application/row/row_cache.dart'; +import 'package:appflowy/plugins/database/application/row/row_controller.dart'; import 'package:appflowy/plugins/database/widgets/card/card.dart'; import 'package:appflowy/plugins/database/widgets/cell/card_cell_builder.dart'; import 'package:appflowy/plugins/database/widgets/cell/card_cell_style_maps/calendar_card_cell_style.dart'; +import 'package:appflowy/plugins/database/widgets/row/row_detail.dart'; import 'package:appflowy/workspace/application/view/view_bloc.dart'; +import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:universal_platform/universal_platform.dart'; import '../application/calendar_bloc.dart'; - import 'calendar_event_editor.dart'; class EventCard extends StatefulWidget { @@ -128,16 +127,16 @@ class _EventCardState extends State { boxShadow: [ BoxShadow( spreadRadius: -2, - color: const Color(0xFF1F2329).withOpacity(0.02), + color: const Color(0xFF1F2329).withValues(alpha: 0.02), blurRadius: 2, ), BoxShadow( - color: const Color(0xFF1F2329).withOpacity(0.02), + color: const Color(0xFF1F2329).withValues(alpha: 0.02), blurRadius: 4, ), BoxShadow( spreadRadius: 2, - color: const Color(0xFF1F2329).withOpacity(0.02), + color: const Color(0xFF1F2329).withValues(alpha: 0.02), blurRadius: 8, ), ], @@ -178,10 +177,13 @@ class _EventCardState extends State { FlowyOverlay.show( context: context, - builder: (_) => RowDetailPage( - databaseController: widget.databaseController, - rowController: rowController, - userProfile: context.read().userProfile, + builder: (_) => BlocProvider.value( + value: context.read(), + child: RowDetailPage( + databaseController: widget.databaseController, + rowController: rowController, + userProfile: context.read().userProfile, + ), ), ); }, diff --git a/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/calendar_event_editor.dart b/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/calendar_event_editor.dart index 40f57b5e9a..dcbe626dd8 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/calendar_event_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/calendar_event_editor.dart @@ -12,6 +12,7 @@ import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dar import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/text.dart'; import 'package:appflowy/plugins/database/widgets/row/accessory/cell_accessory.dart'; import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -92,36 +93,55 @@ class EventEditorControls extends StatelessWidget { message: LocaleKeys.calendar_duplicateEvent.tr(), child: FlowyIconButton( width: 20, - icon: const FlowySvg( + icon: FlowySvg( FlowySvgs.m_duplicate_s, - size: Size.square(17), + size: const Size.square(16), + color: Theme.of(context).iconTheme.color, ), - iconColorOnHover: Theme.of(context).colorScheme.onSecondary, - onPressed: () => context.read().add( - CalendarEvent.duplicateEvent( - rowController.viewId, - rowController.rowId, - ), - ), + onPressed: () { + context.read().add( + CalendarEvent.duplicateEvent( + rowController.viewId, + rowController.rowId, + ), + ); + PopoverContainer.of(context).close(); + }, ), ), const HSpace(8.0), FlowyIconButton( width: 20, - icon: const FlowySvg(FlowySvgs.delete_s), - iconColorOnHover: Theme.of(context).colorScheme.onSecondary, - onPressed: () => context.read().add( - CalendarEvent.deleteEvent( - rowController.viewId, - rowController.rowId, - ), - ), + icon: FlowySvg( + FlowySvgs.delete_s, + size: const Size.square(16), + color: Theme.of(context).iconTheme.color, + ), + onPressed: () { + showConfirmDeletionDialog( + context: context, + name: LocaleKeys.grid_row_label.tr(), + description: LocaleKeys.grid_row_deleteRowPrompt.tr(), + onConfirm: () { + context.read().add( + CalendarEvent.deleteEvent( + rowController.viewId, + rowController.rowId, + ), + ); + PopoverContainer.of(context).close(); + }, + ); + }, ), const HSpace(8.0), FlowyIconButton( width: 20, - icon: const FlowySvg(FlowySvgs.full_view_s), - iconColorOnHover: Theme.of(context).colorScheme.onSecondary, + icon: FlowySvg( + FlowySvgs.full_view_s, + size: const Size.square(16), + color: Theme.of(context).iconTheme.color, + ), onPressed: () { PopoverContainer.of(context).close(); onExpand.call(); @@ -269,6 +289,7 @@ class _TitleTextCellSkin extends IEditableTextCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, TextCellBloc bloc, FocusNode focusNode, TextEditingController textEditingController, diff --git a/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/calendar_page.dart b/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/calendar_page.dart index 231c8830d9..1876332d01 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/calendar_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/calendar_page.dart @@ -1,8 +1,3 @@ -import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart'; -import 'package:appflowy/plugins/database/tab_bar/desktop/setting_menu.dart'; -import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; @@ -11,22 +6,26 @@ import 'package:appflowy/mobile/presentation/presentation.dart'; import 'package:appflowy/plugins/database/application/database_controller.dart'; import 'package:appflowy/plugins/database/calendar/application/calendar_bloc.dart'; import 'package:appflowy/plugins/database/calendar/application/unschedule_event_bloc.dart'; +import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart'; import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; +import 'package:appflowy/plugins/database/tab_bar/desktop/setting_menu.dart'; import 'package:appflowy/plugins/database/tab_bar/tab_bar_view.dart'; -import 'package:appflowy/workspace/application/view/view_bloc.dart'; +import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; +import 'package:appflowy/workspace/application/view/view_lock_status_bloc.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:calendar_view/calendar_view.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:universal_platform/universal_platform.dart'; import '../../application/row/row_controller.dart'; import '../../widgets/row/row_detail.dart'; - import 'calendar_day.dart'; import 'layout/sizes.dart'; import 'toolbar/calendar_setting_bar.dart'; @@ -123,8 +122,18 @@ class _CalendarPageState extends State { Widget build(BuildContext context) { return CalendarControllerProvider( controller: _eventController, - child: BlocProvider.value( - value: _calendarBloc, + child: MultiBlocProvider( + providers: [ + BlocProvider.value( + value: _calendarBloc, + ), + BlocProvider( + create: (context) => ViewLockStatusBloc(view: widget.view) + ..add( + ViewLockStatusEvent.initial(), + ), + ), + ], child: MultiBlocListener( listeners: [ BlocListener( @@ -235,7 +244,21 @@ class _CalendarPageState extends State { showBorder: false, headerBuilder: _headerNavigatorBuilder, weekDayBuilder: _headerWeekDayBuilder, - cellBuilder: _calendarDayBuilder, + cellBuilder: ( + date, + calenderEvents, + isToday, + isInMonth, + position, + ) => + _calendarDayBuilder( + context, + date, + calenderEvents, + isToday, + isInMonth, + position, + ), useAvailableVerticalSpace: widget.shrinkWrap, ), ), @@ -344,6 +367,7 @@ class _CalendarPageState extends State { } Widget _calendarDayBuilder( + BuildContext context, DateTime date, List> calenderEvents, isToday, @@ -355,17 +379,22 @@ class _CalendarPageState extends State { // is implemnted in the develop branch(WIP). Will be replaced with that. final events = calenderEvents.map((value) => value.event!).toList() ..sort((a, b) => a.event.timestamp.compareTo(b.event.timestamp)); + final isLocked = + context.watch()?.state.isLocked ?? false; - return CalendarDayCard( - viewId: widget.view.id, - isToday: isToday, - isInMonth: isInMonth, - events: events, - date: date, - rowCache: _calendarBloc.rowCache, - onCreateEvent: (date) => - _calendarBloc.add(CalendarEvent.createEvent(date)), - position: position, + return IgnorePointer( + ignoring: isLocked, + child: CalendarDayCard( + viewId: widget.view.id, + isToday: isToday, + isInMonth: isInMonth, + events: events, + date: date, + rowCache: _calendarBloc.rowCache, + onCreateEvent: (date) => + _calendarBloc.add(CalendarEvent.createEvent(date)), + position: position, + ), ); } @@ -390,7 +419,7 @@ void showEventDetails({ context: context, builder: (BuildContext overlayContext) { return BlocProvider.value( - value: context.read(), + value: context.read(), child: RowDetailPage( rowController: rowController, databaseController: databaseController, @@ -457,14 +486,18 @@ class _UnscheduledEventsButtonState extends State { ), ), ), - popupBuilder: (_) => BlocProvider.value( - value: context.read(), - child: BlocProvider.value( - value: context.read(), - child: UnscheduleEventsList( - databaseController: widget.databaseController, - unscheduleEvents: state.unscheduleEvents, + popupBuilder: (_) => MultiBlocProvider( + providers: [ + BlocProvider.value( + value: context.read(), ), + BlocProvider.value( + value: context.read(), + ), + ], + child: UnscheduleEventsList( + databaseController: widget.databaseController, + unscheduleEvents: state.unscheduleEvents, ), ), ); diff --git a/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/toolbar/calendar_setting_bar.dart b/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/toolbar/calendar_setting_bar.dart index c2307c63a5..6bfe7b99a8 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/toolbar/calendar_setting_bar.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/calendar/presentation/toolbar/calendar_setting_bar.dart @@ -2,10 +2,13 @@ import 'package:appflowy/plugins/database/application/database_controller.dart'; import 'package:appflowy/plugins/database/grid/application/filter/filter_editor_bloc.dart'; import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart'; import 'package:appflowy/plugins/database/grid/presentation/widgets/toolbar/filter_button.dart'; +import 'package:appflowy/plugins/database/grid/presentation/widgets/toolbar/view_database_button.dart'; import 'package:appflowy/plugins/database/widgets/setting/setting_button.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:provider/provider.dart'; class CalendarSettingBar extends StatelessWidget { const CalendarSettingBar({ @@ -30,6 +33,8 @@ class CalendarSettingBar extends StatelessWidget { if (value) { return const SizedBox.shrink(); } + final isReference = + Provider.of(context)?.isReference ?? false; return SizedBox( height: 20, child: Row( @@ -38,6 +43,10 @@ class CalendarSettingBar extends StatelessWidget { FilterButton( toggleExtension: toggleExtension, ), + if (isReference) ...[ + const HSpace(2), + ViewDatabaseButton(view: databaseController.view), + ], const HSpace(2), SettingButton( databaseController: databaseController, diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/grid_page.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/grid_page.dart index 19330221c6..4c9fd7bd61 100755 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/grid_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/grid_page.dart @@ -1,19 +1,18 @@ import 'dart:async'; -import 'package:appflowy/plugins/database/grid/presentation/widgets/toolbar/grid_setting_bar.dart'; -import 'package:appflowy/plugins/database/tab_bar/desktop/setting_menu.dart'; -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database/application/row/row_service.dart'; import 'package:appflowy/plugins/database/application/tab_bar_bloc.dart'; import 'package:appflowy/plugins/database/domain/sort_service.dart'; import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations/calculations_row.dart'; +import 'package:appflowy/plugins/database/grid/presentation/widgets/toolbar/grid_setting_bar.dart'; +import 'package:appflowy/plugins/database/tab_bar/desktop/setting_menu.dart'; import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart'; import 'package:appflowy/shared/flowy_error_page.dart'; import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart'; import 'package:appflowy/workspace/application/action_navigation/navigation_action.dart'; -import 'package:appflowy/workspace/application/view/view_bloc.dart'; +import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; +import 'package:appflowy/workspace/application/view/view_lock_status_bloc.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; @@ -21,6 +20,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/scrolling/styled_scrollview.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:linked_scroll_controller/linked_scroll_controller.dart'; import 'package:provider/provider.dart'; @@ -30,7 +30,6 @@ import '../../application/row/row_controller.dart'; import '../../tab_bar/tab_bar_view.dart'; import '../../widgets/row/row_detail.dart'; import '../application/grid_bloc.dart'; - import 'grid_scroll.dart'; import 'layout/layout.dart'; import 'layout/sizes.dart'; @@ -140,8 +139,16 @@ class _GridPageState extends State { @override Widget build(BuildContext context) { - return BlocProvider( - create: (_) => gridBloc, + return MultiBlocProvider( + providers: [ + BlocProvider( + create: (_) => gridBloc, + ), + BlocProvider( + create: (context) => ViewLockStatusBloc(view: widget.view) + ..add(ViewLockStatusEvent.initial()), + ), + ], child: BlocListener( listener: (context, state) { final action = state.action; @@ -196,7 +203,7 @@ class _GridPageState extends State { FlowyOverlay.show( context: context, builder: (_) => BlocProvider.value( - value: context.read(), + value: context.read(), child: RowDetailPage( databaseController: context.read().databaseController, rowController: rowController, @@ -233,7 +240,7 @@ class _GridPageState extends State { FlowyOverlay.show( context: context, builder: (_) => BlocProvider.value( - value: context.read(), + value: context.read(), child: RowDetailPage( databaseController: context.read().databaseController, @@ -288,7 +295,11 @@ class _GridPageContentState extends State { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - _GridHeader(headerScrollController: headerScrollController), + _GridHeader( + headerScrollController: headerScrollController, + editable: !context.read().state.isLocked, + shrinkWrap: widget.shrinkWrap, + ), _GridRows( viewId: widget.view.id, scrollController: _scrollController, @@ -300,18 +311,33 @@ class _GridPageContentState extends State { } class _GridHeader extends StatelessWidget { - const _GridHeader({required this.headerScrollController}); + const _GridHeader({ + required this.headerScrollController, + required this.editable, + required this.shrinkWrap, + }); final ScrollController headerScrollController; + final bool editable; + final bool shrinkWrap; @override Widget build(BuildContext context) { - return BlocBuilder( + Widget child = BlocBuilder( builder: (_, state) => GridHeaderSliverAdaptor( viewId: state.viewId, anchorScrollController: headerScrollController, + shrinkWrap: shrinkWrap, ), ); + + if (!editable) { + child = IgnorePointer( + child: child, + ); + } + + return child; } } @@ -394,12 +420,13 @@ class _GridRowsState extends State<_GridRows> { constraints: BoxConstraints( maxWidth: GridLayout.headerWidth( context - .read() - .horizontalPadding, + .read() + .horizontalPadding * + 3, context.read().state.fields, ), ), - child: _renderList(context), + child: _shrinkWrapRenderList(context), ), ), ); @@ -430,9 +457,31 @@ class _GridRowsState extends State<_GridRows> { return Flexible(child: child); } + Widget _shrinkWrapRenderList(BuildContext context) { + final state = context.read().state; + final horizontalPadding = + context.read()?.horizontalPadding ?? + 0.0; + return ListView( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: EdgeInsets.symmetric(horizontal: horizontalPadding), + children: [ + widget.shrinkWrap + ? _reorderableListView(state) + : Expanded(child: _reorderableListView(state)), + if (showFloatingCalculations && !widget.shrinkWrap) ...[ + _PositionedCalculationsRow( + viewId: widget.viewId, + isAtBottom: isAtBottom, + ), + ], + ], + ); + } + Widget _renderList(BuildContext context) { final state = context.read().state; - return Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -474,7 +523,7 @@ class _GridRowsState extends State<_GridRows> { proxyDecorator: (child, _, __) => Provider.value( value: context.read(), child: Material( - color: Colors.white.withOpacity(.1), + color: Colors.white.withValues(alpha: .1), child: Opacity(opacity: .5, child: child), ), ), @@ -504,12 +553,21 @@ class _GridRowsState extends State<_GridRows> { itemCount: itemCount, itemBuilder: (context, index) { if (index == itemCount - 1) { - return Column( + final child = Column( key: const Key('grid_footer'), mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: footer, ); + + if (context.read().state.isLocked) { + return IgnorePointer( + key: const Key('grid_footer'), + child: child, + ); + } + + return child; } return _renderRow( @@ -544,6 +602,7 @@ class _GridRowsState extends State<_GridRows> { rowId: rowId, viewId: viewId, index: index, + editable: !context.watch().state.isLocked, rowController: RowController( viewId: viewId, rowMeta: rowMeta, @@ -559,7 +618,7 @@ class _GridRowsState extends State<_GridRows> { } return BlocProvider.value( - value: context.read(), + value: context.read(), child: RowDetailPage( rowController: RowController( viewId: viewId, diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/layout/layout.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/layout/layout.dart index 903ecbb864..c7402a17f9 100755 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/layout/layout.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/layout/layout.dart @@ -1,5 +1,6 @@ import 'package:appflowy/plugins/database/application/field/field_info.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pbenum.dart'; + import 'sizes.dart'; class GridLayout { diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/layout/sizes.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/layout/sizes.dart index 18883e8a0c..78a8c97dae 100755 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/layout/sizes.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/layout/sizes.dart @@ -5,16 +5,26 @@ class GridSize { static double scale = 1; static double get scrollBarSize => 8 * scale; + static double get headerHeight => 36 * scale; + static double get buttonHeight => 38 * scale; + static double get footerHeight => 36 * scale; + static double get horizontalHeaderPadding => UniversalPlatform.isDesktop ? 40 * scale : 16 * scale; + static double get cellHPadding => 10 * scale; - static double get cellVPadding => 10 * scale; + + static double get cellVPadding => 8 * scale; + static double get popoverItemHeight => 26 * scale; + static double get typeOptionSeparatorHeight => 4 * scale; + static double get newPropertyButtonWidth => 140 * scale; + static double get mobileNewPropertyButtonWidth => 200 * scale; static EdgeInsets get cellContentInsets => EdgeInsets.symmetric( @@ -22,10 +32,8 @@ class GridSize { vertical: GridSize.cellVPadding, ); - static EdgeInsets get fieldContentInsets => EdgeInsets.symmetric( - horizontal: GridSize.cellHPadding, - vertical: GridSize.cellVPadding, - ); + static EdgeInsets get compactCellContentInsets => + cellContentInsets - EdgeInsets.symmetric(vertical: 2); static EdgeInsets get typeOptionContentInsets => const EdgeInsets.all(4); diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/mobile_grid_page.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/mobile_grid_page.dart index f70d98ab77..17e4c0ed1d 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/mobile_grid_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/mobile_grid_page.dart @@ -5,11 +5,11 @@ import 'package:appflowy/plugins/database/application/database_controller.dart'; import 'package:appflowy/plugins/database/application/field/field_info.dart'; import 'package:appflowy/plugins/database/application/row/row_service.dart'; import 'package:appflowy/plugins/database/grid/application/grid_bloc.dart'; -import 'package:appflowy/plugins/database/grid/presentation/widgets/shortcuts.dart'; import 'package:appflowy/plugins/database/tab_bar/tab_bar_view.dart'; import 'package:appflowy/shared/flowy_error_page.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart'; +import 'package:appflowy/workspace/application/view/view_lock_status_bloc.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; @@ -42,6 +42,7 @@ class MobileGridTabBarBuilderImpl extends DatabaseTabBarItemBuilder { view: view, databaseController: controller, initialRowId: initialRowId, + shrinkWrap: shrinkWrap, ); } @@ -68,12 +69,14 @@ class MobileGridPage extends StatefulWidget { required this.databaseController, this.onDeleted, this.initialRowId, + this.shrinkWrap = false, }); final ViewPB view; final DatabaseController databaseController; final VoidCallback? onDeleted; final String? initialRowId; + final bool shrinkWrap; @override State createState() => _MobileGridPageState(); @@ -104,7 +107,10 @@ class _MobileGridPageState extends State { finish: (result) { _openRow(context, widget.initialRowId, true); return result.successOrFail.fold( - (_) => GridShortcuts(child: GridPageContent(view: widget.view)), + (_) => GridPageContent( + view: widget.view, + shrinkWrap: widget.shrinkWrap, + ), (err) => Center( child: AppFlowyErrorPage( error: err, @@ -145,9 +151,11 @@ class GridPageContent extends StatefulWidget { const GridPageContent({ super.key, required this.view, + this.shrinkWrap = false, }); final ViewPB view; + final bool shrinkWrap; @override State createState() => _GridPageContentState(); @@ -175,6 +183,8 @@ class _GridPageContentState extends State { @override Widget build(BuildContext context) { + final isLocked = + context.read()?.state.isLocked ?? false; return BlocListener( listenWhen: (previous, current) => previous.createdRow != current.createdRow, @@ -196,6 +206,7 @@ class _GridPageContentState extends State { children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, children: [ _GridHeader( contentScrollController: contentScrollController, @@ -207,11 +218,12 @@ class _GridPageContentState extends State { ), ], ), - Positioned( - bottom: 16, - right: 16, - child: getGridFabs(context), - ), + if (!widget.shrinkWrap && !isLocked) + Positioned( + bottom: 16, + right: 16, + child: getGridFabs(context), + ), ], ), ); @@ -256,7 +268,7 @@ class _GridRows extends StatelessWidget { buildWhen: (previous, current) => previous.fields != current.fields, builder: (context, state) { final double contentWidth = getMobileGridContentWidth(state.fields); - return Expanded( + return Flexible( child: _WrapScrollView( scrollController: scrollController, contentWidth: contentWidth, @@ -305,6 +317,7 @@ class _GridRows extends StatelessWidget { return ReorderableListView.builder( scrollController: scrollController.verticalController, buildDefaultDragHandles: false, + shrinkWrap: true, proxyDecorator: (child, index, animation) => Material( color: Colors.transparent, child: child, @@ -346,7 +359,7 @@ class _GridRows extends StatelessWidget { final databaseController = context.read().databaseController; - final child = MobileGridRow( + Widget child = MobileGridRow( key: ValueKey(rowMeta.id), rowId: rowId, isDraggable: isDraggable, @@ -363,12 +376,20 @@ class _GridRows extends StatelessWidget { ); if (animation != null) { - return SizeTransition( + child = SizeTransition( sizeFactor: animation, child: child, ); } + final isLocked = + context.read()?.state.isLocked ?? false; + if (isLocked) { + child = IgnorePointer( + child: child, + ); + } + return child; } } diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/footer/grid_footer.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/footer/grid_footer.dart index 1b973554cd..43a0301a10 100755 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/footer/grid_footer.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/footer/grid_footer.dart @@ -1,5 +1,3 @@ -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database/grid/application/grid_bloc.dart'; @@ -9,6 +7,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class GridAddRowButton extends StatelessWidget { @@ -17,8 +16,8 @@ class GridAddRowButton extends StatelessWidget { @override Widget build(BuildContext context) { final color = Theme.of(context).brightness == Brightness.light - ? const Color(0xFF171717).withOpacity(0.4) - : const Color(0xFFFFFFFF).withOpacity(0.4); + ? const Color(0xFF171717).withValues(alpha: 0.4) + : const Color(0xFFFFFFFF).withValues(alpha: 0.4); return FlowyButton( radius: BorderRadius.zero, decoration: BoxDecoration( @@ -66,8 +65,8 @@ class GridRowLoadMoreButton extends StatelessWidget { final padding = context.read().horizontalPadding; final color = Theme.of(context).brightness == Brightness.light - ? const Color(0xFF171717).withOpacity(0.4) - : const Color(0xFFFFFFFF).withOpacity(0.4); + ? const Color(0xFF171717).withValues(alpha: 0.4) + : const Color(0xFFFFFFFF).withValues(alpha: 0.4); return Container( padding: GridSize.footerContentInsets.copyWith(left: 0) + diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/desktop_field_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/desktop_field_cell.dart index f0597c15e4..915bf70a61 100755 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/desktop_field_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/desktop_field_cell.dart @@ -1,17 +1,16 @@ -import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart'; -import 'package:appflowy/util/theme_extension.dart'; -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/plugins/database/application/field/field_cell_bloc.dart'; import 'package:appflowy/plugins/database/application/field/field_controller.dart'; import 'package:appflowy/plugins/database/application/field/field_info.dart'; import 'package:appflowy/plugins/database/widgets/field/field_editor.dart'; +import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart'; import 'package:appflowy/util/field_type_extension.dart'; +import 'package:appflowy/util/theme_extension.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../layout/sizes.dart'; @@ -109,7 +108,7 @@ class _GridFieldCellState extends State { top: 0, bottom: 0, right: 0, - child: _DragToExpandLine(), + child: DragToExpandLine(), ); return _GridHeaderCellContainer( @@ -159,8 +158,11 @@ class _GridHeaderCellContainer extends StatelessWidget { } } -class _DragToExpandLine extends StatelessWidget { - const _DragToExpandLine(); +@visibleForTesting +class DragToExpandLine extends StatelessWidget { + const DragToExpandLine({ + super.key, + }); @override Widget build(BuildContext context) { @@ -261,7 +263,7 @@ class FieldIcon extends StatelessWidget { return svgContent == null ? FlowySvg( fieldInfo.fieldType.svgData, - color: color.withOpacity(0.6), + color: color.withValues(alpha: 0.6), size: Size.square(dimension), ) : SizedBox.square( @@ -269,7 +271,7 @@ class FieldIcon extends StatelessWidget { child: Center( child: FlowySvg.string( svgContent, - color: color.withOpacity(0.45), + color: color.withValues(alpha: 0.45), size: Size.square(dimension - 2), ), ), diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/grid_header.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/grid_header.dart index bcb9139406..e30c238f96 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/grid_header.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/grid_header.dart @@ -21,11 +21,13 @@ class GridHeaderSliverAdaptor extends StatefulWidget { const GridHeaderSliverAdaptor({ super.key, required this.viewId, + required this.shrinkWrap, required this.anchorScrollController, }); final String viewId; final ScrollController anchorScrollController; + final bool shrinkWrap; @override State createState() => @@ -37,6 +39,9 @@ class _GridHeaderSliverAdaptorState extends State { Widget build(BuildContext context) { final fieldController = context.read().databaseController.fieldController; + final horizontalPadding = + context.read()?.horizontalPadding ?? + 0.0; return BlocProvider( create: (context) { return GridHeaderBloc( @@ -47,9 +52,14 @@ class _GridHeaderSliverAdaptorState extends State { child: SingleChildScrollView( scrollDirection: Axis.horizontal, controller: widget.anchorScrollController, - child: _GridHeader( - viewId: widget.viewId, - fieldController: fieldController, + child: Padding( + padding: widget.shrinkWrap + ? EdgeInsets.symmetric(horizontal: horizontalPadding) + : EdgeInsets.zero, + child: _GridHeader( + viewId: widget.viewId, + fieldController: fieldController, + ), ), ), ); diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/mobile_grid_header.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/mobile_grid_header.dart index e20beba726..369bdeb523 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/mobile_grid_header.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/mobile_grid_header.dart @@ -5,6 +5,7 @@ import 'package:appflowy/plugins/database/application/field/field_controller.dar import 'package:appflowy/plugins/database/application/field/field_info.dart'; import 'package:appflowy/plugins/database/grid/application/grid_bloc.dart'; import 'package:appflowy/plugins/database/grid/application/grid_header_bloc.dart'; +import 'package:appflowy/workspace/application/view/view_lock_status_bloc.dart'; import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; @@ -39,6 +40,8 @@ class _MobileGridHeaderState extends State { Widget build(BuildContext context) { final fieldController = context.read().databaseController.fieldController; + final isLocked = + context.read()?.state.isLocked ?? false; return BlocProvider( create: (context) { return GridHeaderBloc( @@ -76,12 +79,15 @@ class _MobileGridHeaderState extends State { ); }, ), - SizedBox( - height: _kGridHeaderHeight, - child: _GridHeader( - viewId: widget.viewId, - fieldController: fieldController, - scrollController: widget.reorderableController, + IgnorePointer( + ignoring: isLocked, + child: SizedBox( + height: _kGridHeaderHeight, + child: _GridHeader( + viewId: widget.viewId, + fieldController: fieldController, + scrollController: widget.reorderableController, + ), ), ), ], diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/row/row.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/row/row.dart index 4de6f20bc2..2306767f46 100755 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/row/row.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/row/row.dart @@ -1,27 +1,25 @@ -import 'package:appflowy/plugins/database/domain/sort_service.dart'; -import 'package:appflowy/plugins/database/grid/application/grid_bloc.dart'; -import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import "package:appflowy/generated/locale_keys.g.dart"; import 'package:appflowy/plugins/database/application/cell/cell_controller.dart'; import 'package:appflowy/plugins/database/application/field/field_controller.dart'; import 'package:appflowy/plugins/database/application/row/row_controller.dart'; import 'package:appflowy/plugins/database/application/row/row_service.dart'; +import 'package:appflowy/plugins/database/domain/sort_service.dart'; +import 'package:appflowy/plugins/database/grid/application/grid_bloc.dart'; import 'package:appflowy/plugins/database/grid/application/row/row_bloc.dart'; import 'package:appflowy/plugins/database/tab_bar/tab_bar_view.dart'; import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:provider/provider.dart'; import '../../../../widgets/row/accessory/cell_accessory.dart'; import '../../../../widgets/row/cells/cell_container.dart'; import '../../layout/sizes.dart'; - import 'action.dart'; class GridRow extends StatelessWidget { @@ -35,6 +33,7 @@ class GridRow extends StatelessWidget { required this.openDetailPage, required this.index, this.shrinkWrap = false, + required this.editable, }); final FieldController fieldController; @@ -45,6 +44,7 @@ class GridRow extends StatelessWidget { final void Function(BuildContext context) openDetailPage; final int index; final bool shrinkWrap; + final bool editable; @override Widget build(BuildContext context) { @@ -58,7 +58,7 @@ class GridRow extends StatelessWidget { rowContent = Expanded(child: rowContent); } - return BlocProvider( + rowContent = BlocProvider( create: (_) => RowBloc( fieldController: fieldController, rowId: rowId, @@ -74,6 +74,14 @@ class GridRow extends StatelessWidget { ), ), ); + + if (!editable) { + rowContent = IgnorePointer( + child: rowContent, + ); + } + + return rowContent; } } @@ -300,14 +308,20 @@ class RowContent extends StatelessWidget { Widget _finalCellDecoration(BuildContext context) { return MouseRegion( cursor: SystemMouseCursors.basic, - child: Container( - width: GridSize.newPropertyButtonWidth, - constraints: const BoxConstraints(minHeight: 36), - decoration: BoxDecoration( - border: Border( - bottom: BorderSide(color: AFThemeExtension.of(context).borderColor), - ), - ), + child: ValueListenableBuilder( + valueListenable: cellBuilder.databaseController.compactModeNotifier, + builder: (context, compactMode, _) { + return Container( + width: GridSize.newPropertyButtonWidth, + constraints: BoxConstraints(minHeight: compactMode ? 32 : 36), + decoration: BoxDecoration( + border: Border( + bottom: + BorderSide(color: AFThemeExtension.of(context).borderColor), + ), + ), + ); + }, ), ); } diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/toolbar/filter_button.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/toolbar/filter_button.dart index ece2658cf2..5c33426281 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/toolbar/filter_button.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/toolbar/filter_button.dart @@ -1,14 +1,13 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database/grid/application/filter/filter_editor_bloc.dart'; import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import '../../layout/sizes.dart'; import '../filter/create_filter_list.dart'; class FilterButton extends StatefulWidget { @@ -30,27 +29,25 @@ class _FilterButtonState extends State { Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - final textColor = state.filters.isEmpty - ? Theme.of(context).hintColor - : Theme.of(context).colorScheme.primary; - return _wrapPopover( - FlowyTextButton( - LocaleKeys.grid_settings_filter.tr(), - fontColor: textColor, - fontSize: FontSizes.s12, - fillColor: Colors.transparent, - hoverColor: AFThemeExtension.of(context).lightGreyHover, - padding: GridSize.toolbarSettingButtonInsets, - radius: Corners.s4Border, - onPressed: () { - final bloc = context.read(); - if (bloc.state.filters.isEmpty) { - _popoverController.show(); - } else { - widget.toggleExtension.toggle(); - } - }, + MouseRegion( + cursor: SystemMouseCursors.click, + child: FlowyIconButton( + tooltipText: LocaleKeys.grid_settings_filter.tr(), + width: 24, + height: 24, + iconPadding: const EdgeInsets.all(3), + hoverColor: AFThemeExtension.of(context).lightGreyHover, + icon: const FlowySvg(FlowySvgs.database_filter_s), + onPressed: () { + final bloc = context.read(); + if (bloc.state.filters.isEmpty) { + _popoverController.show(); + } else { + widget.toggleExtension.toggle(); + } + }, + ), ), ); }, diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/toolbar/grid_setting_bar.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/toolbar/grid_setting_bar.dart index 047781c6cc..f325ab206f 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/toolbar/grid_setting_bar.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/toolbar/grid_setting_bar.dart @@ -3,12 +3,15 @@ import 'package:appflowy/plugins/database/grid/application/filter/filter_editor_ import 'package:appflowy/plugins/database/grid/application/sort/sort_editor_bloc.dart'; import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart'; import 'package:appflowy/plugins/database/widgets/setting/setting_button.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:provider/provider.dart'; import 'filter_button.dart'; import 'sort_button.dart'; +import 'view_database_button.dart'; class GridSettingBar extends StatelessWidget { const GridSettingBar({ @@ -43,18 +46,22 @@ class GridSettingBar extends StatelessWidget { if (isLoading) { return const SizedBox.shrink(); } + final isReference = + Provider.of(context)?.isReference ?? false; return SizedBox( height: 20, child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ FilterButton(toggleExtension: toggleExtension), - const HSpace(6), + const HSpace(2), SortButton(toggleExtension: toggleExtension), - const HSpace(6), - SettingButton( - databaseController: controller, - ), + if (isReference) ...[ + const HSpace(2), + ViewDatabaseButton(view: controller.view), + ], + const HSpace(2), + SettingButton(databaseController: controller), ], ), ); diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/toolbar/sort_button.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/toolbar/sort_button.dart index e16c851f3a..6649d53594 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/toolbar/sort_button.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/toolbar/sort_button.dart @@ -1,13 +1,12 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database/grid/application/sort/sort_editor_bloc.dart'; import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; import '../sort/create_sort_list.dart'; @@ -27,26 +26,24 @@ class _SortButtonState extends State { Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - final textColor = state.sorts.isEmpty - ? Theme.of(context).hintColor - : Theme.of(context).colorScheme.primary; - return wrapPopover( - FlowyTextButton( - LocaleKeys.grid_settings_sort.tr(), - fontColor: textColor, - fontSize: FontSizes.s12, - fillColor: Colors.transparent, - hoverColor: AFThemeExtension.of(context).lightGreyHover, - padding: GridSize.toolbarSettingButtonInsets, - radius: Corners.s4Border, - onPressed: () { - if (state.sorts.isEmpty) { - _popoverController.show(); - } else { - widget.toggleExtension.toggle(); - } - }, + MouseRegion( + cursor: SystemMouseCursors.click, + child: FlowyIconButton( + tooltipText: LocaleKeys.grid_settings_sort.tr(), + width: 24, + height: 24, + iconPadding: const EdgeInsets.all(3), + hoverColor: AFThemeExtension.of(context).lightGreyHover, + icon: const FlowySvg(FlowySvgs.database_sort_s), + onPressed: () { + if (state.sorts.isEmpty) { + _popoverController.show(); + } else { + widget.toggleExtension.toggle(); + } + }, + ), ), ); }, diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/toolbar/view_database_button.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/toolbar/view_database_button.dart new file mode 100644 index 0000000000..93493e599f --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/toolbar/view_database_button.dart @@ -0,0 +1,37 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart'; +import 'package:appflowy/workspace/application/view/view_ext.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/style_widget/icon_button.dart'; +import 'package:flutter/material.dart'; + +class ViewDatabaseButton extends StatelessWidget { + const ViewDatabaseButton({super.key, required this.view}); + + final ViewPB view; + + @override + Widget build(BuildContext context) { + return MouseRegion( + cursor: SystemMouseCursors.click, + child: FlowyIconButton( + tooltipText: LocaleKeys.grid_rowPage_openAsFullPage.tr(), + width: 24, + height: 24, + iconPadding: const EdgeInsets.all(3), + icon: const FlowySvg(FlowySvgs.database_fullscreen_s), + onPressed: () { + getIt().add( + TabsEvent.openPlugin( + plugin: view.plugin(), + view: view, + ), + ); + }, + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/database/tab_bar/desktop/tab_bar_header.dart b/frontend/appflowy_flutter/lib/plugins/database/tab_bar/desktop/tab_bar_header.dart index e9408660ee..fa5e44a5e6 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/tab_bar/desktop/tab_bar_header.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/tab_bar/desktop/tab_bar_header.dart @@ -1,8 +1,12 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database/application/tab_bar_bloc.dart'; -import 'package:appflowy/plugins/database/tab_bar/tab_bar_view.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart'; +import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; +import 'package:appflowy/workspace/application/view/view_service.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; @@ -12,6 +16,7 @@ import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:provider/provider.dart'; import 'tab_bar_add_button.dart'; @@ -22,12 +27,8 @@ class TabBarHeader extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( + return SizedBox( height: 35, - padding: EdgeInsets.symmetric( - horizontal: - context.read().horizontalPadding, - ), child: Stack( children: [ Positioned( @@ -115,9 +116,9 @@ class _DatabaseTabBarState extends State { view: state.tabBars[index].view, isSelected: state.selectedIndex == index, onTap: (selectedView) { - context.read().add( - DatabaseTabBarEvent.selectView(selectedView.id), - ); + context + .read() + .add(DatabaseTabBarEvent.selectView(selectedView.id)); }, ), separatorBuilder: (context, index) => VerticalDivider( @@ -179,7 +180,7 @@ class DatabaseTabBarItem extends StatelessWidget { } } -class TabBarItemButton extends StatelessWidget { +class TabBarItemButton extends StatefulWidget { const TabBarItemButton({ super.key, required this.view, @@ -191,77 +192,167 @@ class TabBarItemButton extends StatelessWidget { final bool isSelected; final VoidCallback onTap; + @override + State createState() => _TabBarItemButtonState(); +} + +class _TabBarItemButtonState extends State { + final menuController = PopoverController(); + final iconController = PopoverController(); + @override Widget build(BuildContext context) { - return PopoverActionList( + Color? color; + if (!widget.isSelected) { + color = Theme.of(context).hintColor; + } + if (Theme.of(context).brightness == Brightness.dark) { + color = null; + } + return AppFlowyPopover( + controller: menuController, + constraints: const BoxConstraints( + minWidth: 120, + maxWidth: 460, + maxHeight: 300, + ), direction: PopoverDirection.bottomWithCenterAligned, - actions: TabBarViewAction.values, - buildChild: (controller) { - Color? color; - if (!isSelected) { - color = Theme.of(context).hintColor; - } - if (Theme.of(context).brightness == Brightness.dark) { - color = null; - } - return IntrinsicWidth( - child: FlowyButton( - radius: Corners.s6Border, - hoverColor: AFThemeExtension.of(context).greyHover, - onTap: onTap, - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), - onSecondaryTap: () { - controller.show(); - }, - leftIcon: FlowySvg( - view.iconData, - size: const Size(14, 14), - color: color, - ), - text: FlowyText( - view.nameOrDefault, - lineHeight: 1.0, - textAlign: TextAlign.center, - overflow: TextOverflow.ellipsis, - color: color, - fontWeight: isSelected ? FontWeight.w500 : FontWeight.w400, + clickHandler: PopoverClickHandler.gestureDetector, + popupBuilder: (_) { + return IntrinsicHeight( + child: IntrinsicWidth( + child: Column( + children: [ + ActionCellWidget( + action: TabBarViewAction.rename, + itemHeight: ActionListSizes.itemHeight, + onSelected: (action) { + NavigatorTextFieldDialog( + title: LocaleKeys.menuAppHeader_renameDialog.tr(), + value: widget.view.nameOrDefault, + onConfirm: (newValue, _) { + context.read().add( + DatabaseTabBarEvent.renameView( + widget.view.id, + newValue, + ), + ); + }, + ).show(context); + menuController.close(); + }, + ), + AppFlowyPopover( + controller: iconController, + direction: PopoverDirection.rightWithCenterAligned, + constraints: BoxConstraints.loose(const Size(364, 356)), + margin: const EdgeInsets.all(0), + child: ActionCellWidget( + action: TabBarViewAction.changeIcon, + itemHeight: ActionListSizes.itemHeight, + onSelected: (action) { + iconController.show(); + }, + ), + popupBuilder: (context) { + return FlowyIconEmojiPicker( + tabs: const [PickerTabType.icon], + enableBackgroundColorSelection: false, + onSelectedEmoji: (r) { + ViewBackendService.updateViewIcon( + view: widget.view, + viewIcon: r.data, + ); + if (!r.keepOpen) { + iconController.close(); + menuController.close(); + } + }, + ); + }, + ), + ActionCellWidget( + action: TabBarViewAction.delete, + itemHeight: ActionListSizes.itemHeight, + onSelected: (action) { + NavigatorAlertDialog( + title: LocaleKeys.grid_deleteView.tr(), + confirm: () { + context.read().add( + DatabaseTabBarEvent.deleteView(widget.view.id), + ); + }, + ).show(context); + menuController.close(); + }, + ), + ], ), ), ); }, - onSelected: (action, controller) { - switch (action) { - case TabBarViewAction.rename: - NavigatorTextFieldDialog( - title: LocaleKeys.menuAppHeader_renameDialog.tr(), - value: view.nameOrDefault, - onConfirm: (newValue, _) { - context.read().add( - DatabaseTabBarEvent.renameView(view.id, newValue), - ); - }, - ).show(context); - break; - case TabBarViewAction.delete: - NavigatorAlertDialog( - title: LocaleKeys.grid_deleteView.tr(), - confirm: () { - context.read().add( - DatabaseTabBarEvent.deleteView(view.id), - ); - }, - ).show(context); - - break; - } - controller.close(); - }, + child: IntrinsicWidth( + child: FlowyButton( + radius: Corners.s6Border, + hoverColor: AFThemeExtension.of(context).greyHover, + onTap: () { + if (widget.isSelected) menuController.show(); + widget.onTap.call(); + }, + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), + onSecondaryTap: () { + menuController.show(); + }, + leftIcon: _buildViewIcon(), + text: FlowyText( + widget.view.nameOrDefault, + lineHeight: 1.0, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + color: color, + fontWeight: widget.isSelected ? FontWeight.w500 : FontWeight.w400, + ), + ), + ), ); } + + Widget _buildViewIcon() { + final iconData = widget.view.icon.toEmojiIconData(); + Widget icon; + if (iconData.isEmpty || iconData.type != FlowyIconType.icon) { + icon = widget.view.defaultIcon(); + } else { + icon = RawEmojiIconWidget( + emoji: iconData, + emojiSize: 14.0, + enableColor: false, + ); + } + final isReference = + Provider.of(context)?.isReference ?? false; + final iconWidget = Opacity(opacity: 0.6, child: icon); + return isReference + ? Stack( + children: [ + iconWidget, + const Positioned( + right: 0, + bottom: 0, + child: FlowySvg( + FlowySvgs.referenced_page_s, + blendMode: BlendMode.dstIn, + ), + ), + ], + ) + : iconWidget; + } } enum TabBarViewAction implements ActionCell { rename, + changeIcon, delete; @override @@ -269,6 +360,8 @@ enum TabBarViewAction implements ActionCell { switch (this) { case TabBarViewAction.rename: return LocaleKeys.disclosureAction_rename.tr(); + case TabBarViewAction.changeIcon: + return LocaleKeys.disclosureAction_changeIcon.tr(); case TabBarViewAction.delete: return LocaleKeys.disclosureAction_delete.tr(); } @@ -278,6 +371,8 @@ enum TabBarViewAction implements ActionCell { switch (this) { case TabBarViewAction.rename: return const FlowySvg(FlowySvgs.edit_s); + case TabBarViewAction.changeIcon: + return const FlowySvg(FlowySvgs.change_icon_s); case TabBarViewAction.delete: return const FlowySvg(FlowySvgs.delete_s); } diff --git a/frontend/appflowy_flutter/lib/plugins/database/tab_bar/mobile/mobile_tab_bar_header.dart b/frontend/appflowy_flutter/lib/plugins/database/tab_bar/mobile/mobile_tab_bar_header.dart index 3606ed82a3..3a1fcac510 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/tab_bar/mobile/mobile_tab_bar_header.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/tab_bar/mobile/mobile_tab_bar_header.dart @@ -1,17 +1,17 @@ -import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/show_transition_bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/database/view/database_view_list.dart'; -import 'package:appflowy/plugins/base/emoji/emoji_text.dart'; import 'package:appflowy/plugins/database/application/tab_bar_bloc.dart'; +import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; import 'package:appflowy/plugins/database/widgets/setting/mobile_database_controls.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart'; +import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; import 'package:appflowy/workspace/application/view/view_bloc.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:collection/collection.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class MobileTabBarHeader extends StatelessWidget { @@ -142,14 +142,16 @@ class _DatabaseViewSelectorButton extends StatelessWidget { } Widget _buildViewIconButton(BuildContext context, ViewPB view) { - return view.icon.value.isNotEmpty - ? EmojiText( - emoji: view.icon.value, - fontSize: 16.0, - ) - : SizedBox.square( - dimension: 16.0, - child: view.defaultIcon(), - ); + final iconData = view.icon.toEmojiIconData(); + if (iconData.isEmpty || iconData.type != FlowyIconType.icon) { + return SizedBox.square( + dimension: 16.0, + child: view.defaultIcon(), + ); + } + return RawEmojiIconWidget( + emoji: iconData, + emojiSize: 16, + ); } } diff --git a/frontend/appflowy_flutter/lib/plugins/database/tab_bar/tab_bar_view.dart b/frontend/appflowy_flutter/lib/plugins/database/tab_bar/tab_bar_view.dart index 542938a574..7c2dc40869 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/tab_bar/tab_bar_view.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/tab_bar/tab_bar_view.dart @@ -1,9 +1,17 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:appflowy/core/config/kv.dart'; +import 'package:appflowy/core/config/kv_keys.dart'; import 'package:appflowy/plugins/database/application/database_controller.dart'; import 'package:appflowy/plugins/database/application/tab_bar_bloc.dart'; import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; +import 'package:appflowy/plugins/document/presentation/compact_mode_event.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/database/database_view_block_component.dart'; import 'package:appflowy/plugins/shared/share/share_button.dart'; import 'package:appflowy/plugins/util.dart'; import 'package:appflowy/startup/plugin/plugin.dart'; +import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/workspace/application/view/view_bloc.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy/workspace/application/view_info/view_info_bloc.dart'; @@ -12,7 +20,9 @@ import 'package:appflowy/workspace/presentation/widgets/favorite_button.dart'; import 'package:appflowy/workspace/presentation/widgets/more_view_actions/more_view_actions.dart'; import 'package:appflowy/workspace/presentation/widgets/tab_bar_item.dart'; import 'package:appflowy/workspace/presentation/widgets/view_title_bar.dart'; +import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flutter/material.dart'; @@ -55,38 +65,101 @@ abstract class DatabaseTabBarItemBuilder { void dispose() {} } -class DatabaseTabBarView extends StatelessWidget { +class DatabaseTabBarView extends StatefulWidget { const DatabaseTabBarView({ super.key, required this.view, required this.shrinkWrap, + required this.showActions, this.initialRowId, + this.actionBuilder, + this.node, }); final ViewPB view; final bool shrinkWrap; + final BlockComponentActionBuilder? actionBuilder; + final bool showActions; + final Node? node; /// Used to open a Row on plugin load /// final String? initialRowId; + @override + State createState() => _DatabaseTabBarViewState(); +} + +class _DatabaseTabBarViewState extends State { + bool enableCompactMode = false; + bool initialed = false; + StreamSubscription? compactModeSubscription; + + String get compactModeId => widget.node?.id ?? widget.view.id; + + @override + void initState() { + super.initState(); + if (widget.node != null) { + enableCompactMode = + widget.node!.attributes[DatabaseBlockKeys.enableCompactMode] ?? false; + setState(() { + initialed = true; + }); + } else { + fetchLocalCompactMode(compactModeId).then((v) { + if (mounted) { + setState(() { + enableCompactMode = v; + initialed = true; + }); + } + }); + compactModeSubscription = + compactModeEventBus.on().listen((event) { + if (event.id != widget.view.id) return; + updateLocalCompactMode(event.enable); + }); + } + } + + @override + void dispose() { + super.dispose(); + compactModeSubscription?.cancel(); + } + @override Widget build(BuildContext context) { + if (!initialed) return Center(child: CircularProgressIndicator()); return MultiBlocProvider( providers: [ BlocProvider( - create: (_) => DatabaseTabBarBloc(view: view) - ..add(const DatabaseTabBarEvent.initial()), + create: (_) => DatabaseTabBarBloc( + view: widget.view, + compactModeId: compactModeId, + enableCompactMode: enableCompactMode, + )..add(const DatabaseTabBarEvent.initial()), ), BlocProvider( - create: (_) => ViewBloc(view: view)..add(const ViewEvent.initial()), + create: (_) => ViewBloc(view: widget.view) + ..add( + const ViewEvent.initial(), + ), ), ], child: BlocBuilder( - builder: (_, state) { + builder: (innerContext, state) { final layout = state.tabBars[state.selectedIndex].layout; - return Column( + final isCalendar = layout == ViewLayoutPB.Calendar; + final horizontalPadding = + context.read().horizontalPadding; + final showActionWrapper = widget.showActions && + widget.actionBuilder != null && + widget.node != null; + final Widget child = Column( crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, children: [ if (UniversalPlatform.isMobile) const VSpace(12), ValueListenableBuilder( @@ -99,25 +172,97 @@ class DatabaseTabBarView extends StatelessWidget { return const SizedBox.shrink(); } - return UniversalPlatform.isDesktop + Widget child = UniversalPlatform.isDesktop ? const TabBarHeader() : const MobileTabBarHeader(); + + if (innerContext.watch().state.view.isLocked) { + child = IgnorePointer( + child: child, + ); + } + + if (showActionWrapper) { + child = BlockComponentActionWrapper( + node: widget.node!, + actionBuilder: widget.actionBuilder!, + child: Padding( + padding: EdgeInsets.only(right: horizontalPadding), + child: child, + ), + ); + } + + if (UniversalPlatform.isDesktop) { + child = Container( + padding: EdgeInsets.symmetric( + horizontal: horizontalPadding, + ), + child: child, + ); + } + + return child; }, ), pageSettingBarExtensionFromState(context, state), wrapContent( layout: layout, - child: pageContentFromState(context, state), + child: Padding( + padding: + (isCalendar && widget.shrinkWrap || showActionWrapper) + ? EdgeInsets.only(left: 42 - horizontalPadding) + : EdgeInsets.zero, + child: pageContentFromState(context, state), + ), ), ], ); + + return child; }, ), ); } + Future fetchLocalCompactMode(String compactModeId) async { + Set compactModeIds = {}; + try { + final localIds = await getIt().get( + KVKeys.compactModeIds, + ); + final List decodedList = jsonDecode(localIds ?? ''); + compactModeIds = Set.from(decodedList.map((item) => item as String)); + } catch (e) { + Log.warn('fetch local compact mode from id :$compactModeId failed', e); + } + return compactModeIds.contains(compactModeId); + } + + Future updateLocalCompactMode(bool enableCompactMode) async { + Set compactModeIds = {}; + try { + final localIds = await getIt().get( + KVKeys.compactModeIds, + ); + final List decodedList = jsonDecode(localIds ?? ''); + compactModeIds = Set.from(decodedList.map((item) => item as String)); + } catch (e) { + Log.warn('get compact mode ids failed', e); + } + if (enableCompactMode) { + compactModeIds.add(compactModeId); + } else { + compactModeIds.remove(compactModeId); + } + await getIt().set( + KVKeys.compactModeIds, + jsonEncode(compactModeIds.toList()), + ); + } + Widget wrapContent({required ViewLayoutPB layout, required Widget child}) { - if (shrinkWrap) { + if (widget.shrinkWrap) { if (layout.shrinkWrappable) { return child; } @@ -139,8 +284,8 @@ class DatabaseTabBarView extends StatelessWidget { context, tab.view, controller, - shrinkWrap, - initialRowId, + widget.shrinkWrap, + widget.initialRowId, ); } @@ -212,11 +357,18 @@ class DatabaseTabBarViewPlugin extends Plugin { } const kDatabasePluginWidgetBuilderHorizontalPadding = 'horizontal_padding'; +const kDatabasePluginWidgetBuilderShowActions = 'show_actions'; +const kDatabasePluginWidgetBuilderActionBuilder = 'action_builder'; +const kDatabasePluginWidgetBuilderNode = 'node'; class DatabasePluginWidgetBuilderSize { - const DatabasePluginWidgetBuilderSize({required this.horizontalPadding}); + const DatabasePluginWidgetBuilderSize({ + required this.horizontalPadding, + this.verticalPadding = 16.0, + }); final double horizontalPadding; + final double verticalPadding; } class DatabasePluginWidgetBuilder extends PluginWidgetBuilder { @@ -260,6 +412,11 @@ class DatabasePluginWidgetBuilder extends PluginWidgetBuilder { final horizontalPadding = data?[kDatabasePluginWidgetBuilderHorizontalPadding] as double? ?? GridSize.horizontalHeaderPadding + 40; + final BlockComponentActionBuilder? actionBuilder = + data?[kDatabasePluginWidgetBuilderActionBuilder]; + final bool showActions = + data?[kDatabasePluginWidgetBuilderShowActions] ?? false; + final Node? node = data?[kDatabasePluginWidgetBuilderNode]; return Provider( create: (context) => DatabasePluginWidgetBuilderSize( @@ -270,6 +427,9 @@ class DatabasePluginWidgetBuilder extends PluginWidgetBuilder { view: notifier.view, shrinkWrap: shrinkWrap, initialRowId: initialRowId, + actionBuilder: actionBuilder, + showActions: showActions, + node: node, ), ); } @@ -288,7 +448,7 @@ class DatabasePluginWidgetBuilder extends PluginWidgetBuilder { const HSpace(10), ViewFavoriteButton(view: view), const HSpace(4), - MoreViewActions(view: view, isDocument: false), + MoreViewActions(view: view), ], ), ); diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/card/card.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/card/card.dart index e7c6150448..68c4b15d5c 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/card/card.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/card/card.dart @@ -1,5 +1,3 @@ -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart'; import 'package:appflowy/mobile/presentation/database/card/card.dart'; @@ -16,12 +14,12 @@ import 'package:collection/collection.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:universal_platform/universal_platform.dart'; import '../cell/card_cell_builder.dart'; import '../cell/card_cell_skeleton/card_cell.dart'; - import 'card_bloc.dart'; import 'container/accessory.dart'; import 'container/card_container.dart'; @@ -462,7 +460,7 @@ class RowCardStyleConfiguration { const RowCardStyleConfiguration({ required this.cellStyleMap, this.showAccessory = true, - this.cardPadding = const EdgeInsets.all(8), + this.cardPadding = const EdgeInsets.all(4), this.hoverStyle, }); diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/card/container/accessory.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/card/container/accessory.dart index 7078685845..e74f947b46 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/card/container/accessory.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/card/container/accessory.dart @@ -1,6 +1,5 @@ -import 'package:flutter/material.dart'; - import 'package:flowy_infra_ui/style_widget/hover.dart'; +import 'package:flutter/material.dart'; enum AccessoryType { edit, @@ -45,7 +44,7 @@ class CardAccessoryContainer extends StatelessWidget { width: 1, thickness: 1, color: Theme.of(context).brightness == Brightness.light - ? const Color(0xFF1F2329).withOpacity(0.12) + ? const Color(0xFF1F2329).withValues(alpha: 0.12) : const Color(0xff59647a), ), ); @@ -77,19 +76,19 @@ class CardAccessoryContainer extends StatelessWidget { border: Border.fromBorderSide( BorderSide( color: Theme.of(context).brightness == Brightness.light - ? const Color(0xFF1F2329).withOpacity(0.12) + ? const Color(0xFF1F2329).withValues(alpha: 0.12) : const Color(0xff59647a), ), ), boxShadow: [ BoxShadow( blurRadius: 4, - color: const Color(0xFF1F2329).withOpacity(0.02), + color: const Color(0xFF1F2329).withValues(alpha: 0.02), ), BoxShadow( blurRadius: 4, spreadRadius: -2, - color: const Color(0xFF1F2329).withOpacity(0.02), + color: const Color(0xFF1F2329).withValues(alpha: 0.02), ), ], ); diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/card/container/card_container.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/card/container/card_container.dart index ba71fcd30b..a91ffae42d 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/card/container/card_container.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/card/container/card_container.dart @@ -40,7 +40,7 @@ class RowCardContainer extends StatelessWidget { } }, child: ConstrainedBox( - constraints: const BoxConstraints(minHeight: 42), + constraints: const BoxConstraints(minHeight: 36), child: _CardEnterRegion( shouldBuildAccessory: shouldBuildAccessory, accessories: accessories, @@ -77,8 +77,8 @@ class _CardEnterRegion extends StatelessWidget { child, if (onEnter && shouldBuildAccessory) Positioned( - top: 10.0, - right: 10.0, + top: 7.0, + right: 7.0, child: CardAccessoryContainer( accessories: accessories, onTapAccessory: onTapAccessory, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_checkbox_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_checkbox_cell.dart index befe49dd6d..74abcecb3a 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_checkbox_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_checkbox_cell.dart @@ -1,7 +1,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/plugins/database/application/cell/bloc/checkbox_cell_bloc.dart'; import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; -import 'package:appflowy/plugins/database/application/cell/bloc/checkbox_cell_bloc.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; @@ -12,22 +12,31 @@ class DesktopGridCheckboxCellSkin extends IEditableCheckboxCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, CheckboxCellBloc bloc, CheckboxCellState state, ) { - return Container( - alignment: AlignmentDirectional.centerStart, - padding: GridSize.cellContentInsets, - child: FlowyIconButton( - hoverColor: Colors.transparent, - onPressed: () => bloc.add(const CheckboxCellEvent.select()), - icon: FlowySvg( - state.isSelected ? FlowySvgs.check_filled_s : FlowySvgs.uncheck_s, - blendMode: BlendMode.dst, - size: const Size.square(20), - ), - width: 20, - ), + return ValueListenableBuilder( + valueListenable: compactModeNotifier, + builder: (context, compactMode, _) { + final padding = compactMode + ? GridSize.compactCellContentInsets + : GridSize.cellContentInsets; + return Container( + alignment: AlignmentDirectional.centerStart, + padding: padding, + child: FlowyIconButton( + hoverColor: Colors.transparent, + onPressed: () => bloc.add(const CheckboxCellEvent.select()), + icon: FlowySvg( + state.isSelected ? FlowySvgs.check_filled_s : FlowySvgs.uncheck_s, + blendMode: BlendMode.dst, + size: const Size.square(20), + ), + width: 20, + ), + ); + }, ); } } diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_checklist_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_checklist_cell.dart index f5ad4f3970..ebc4a6f976 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_checklist_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_checklist_cell.dart @@ -1,11 +1,10 @@ -import 'package:flutter/material.dart'; - import 'package:appflowy/plugins/database/application/cell/bloc/checklist_cell_bloc.dart'; import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; import 'package:appflowy/plugins/database/widgets/cell_editor/checklist_cell_editor.dart'; import 'package:appflowy/plugins/database/widgets/cell_editor/checklist_progress_bar.dart'; import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../editable_cell_skeleton/checklist.dart'; @@ -15,6 +14,7 @@ class DesktopGridChecklistCellSkin extends IEditableChecklistCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, ChecklistCellBloc bloc, PopoverController popoverController, ) { @@ -39,15 +39,24 @@ class DesktopGridChecklistCellSkin extends IEditableChecklistCellSkin { onClose: () => cellContainerNotifier.isFocus = false, child: BlocBuilder( builder: (context, state) { - return Container( - alignment: AlignmentDirectional.centerStart, - padding: GridSize.cellContentInsets, - child: state.tasks.isEmpty - ? const SizedBox.shrink() - : ChecklistProgressBar( - tasks: state.tasks, - percent: state.percent, - ), + return ValueListenableBuilder( + valueListenable: compactModeNotifier, + builder: (context, compactMode, _) { + final padding = compactMode + ? GridSize.compactCellContentInsets + : GridSize.cellContentInsets; + + return Container( + alignment: AlignmentDirectional.centerStart, + padding: padding, + child: state.tasks.isEmpty + ? const SizedBox.shrink() + : ChecklistProgressBar( + tasks: state.tasks, + percent: state.percent, + ), + ); + }, ); }, ), diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_date_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_date_cell.dart index 21bbee23ff..de7f7f5a2e 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_date_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_date_cell.dart @@ -1,9 +1,9 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; -import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:appflowy/plugins/database/application/cell/bloc/date_cell_bloc.dart'; +import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; import 'package:appflowy/plugins/database/widgets/cell_editor/date_cell_editor.dart'; +import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/widgets.dart'; @@ -15,6 +15,7 @@ class DesktopGridDateCellSkin extends IEditableDateCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, DateCellBloc bloc, DateCellState state, PopoverController popoverController, @@ -28,11 +29,11 @@ class DesktopGridDateCellSkin extends IEditableDateCellSkin { child: Align( alignment: AlignmentDirectional.centerStart, child: state.fieldInfo.wrapCellContent ?? false - ? _buildCellContent(state) + ? _buildCellContent(state, compactModeNotifier) : SingleChildScrollView( physics: const NeverScrollableScrollPhysics(), scrollDirection: Axis.horizontal, - child: _buildCellContent(state), + child: _buildCellContent(state, compactModeNotifier), ), ), popupBuilder: (BuildContext popoverContent) { @@ -47,33 +48,44 @@ class DesktopGridDateCellSkin extends IEditableDateCellSkin { ); } - Widget _buildCellContent(DateCellState state) { + Widget _buildCellContent( + DateCellState state, + ValueNotifier compactModeNotifier, + ) { final wrap = state.fieldInfo.wrapCellContent ?? false; final dateStr = getDateCellStrFromCellData( state.fieldInfo, state.cellData, ); - return Padding( - padding: GridSize.cellContentInsets, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Flexible( - child: FlowyText( - dateStr, - overflow: wrap ? null : TextOverflow.ellipsis, - maxLines: wrap ? null : 1, - ), + return ValueListenableBuilder( + valueListenable: compactModeNotifier, + builder: (context, compactMode, _) { + final padding = compactMode + ? GridSize.compactCellContentInsets + : GridSize.cellContentInsets; + return Padding( + padding: padding, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + child: FlowyText( + dateStr, + overflow: wrap ? null : TextOverflow.ellipsis, + maxLines: wrap ? null : 1, + ), + ), + if (state.cellData.reminderId.isNotEmpty) ...[ + const HSpace(4), + FlowyTooltip( + message: LocaleKeys.grid_field_reminderOnDateTooltip.tr(), + child: const FlowySvg(FlowySvgs.clock_alarm_s), + ), + ], + ], ), - if (state.cellData.reminderId.isNotEmpty) ...[ - const HSpace(4), - FlowyTooltip( - message: LocaleKeys.grid_field_reminderOnDateTooltip.tr(), - child: const FlowySvg(FlowySvgs.clock_alarm_s), - ), - ], - ], - ), + ); + }, ); } } diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_media_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_media_cell.dart index f66d106ed0..b070af7cc7 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_media_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_media_cell.dart @@ -72,7 +72,7 @@ class GridMediaCellSkin extends IEditableMediaCellSkin { if (!isMobile && wrapContent) { return Padding( - padding: const EdgeInsets.all(4), + padding: const EdgeInsets.symmetric(horizontal: 4), child: SizedBox( width: double.infinity, child: Wrap( @@ -233,7 +233,7 @@ class _FilePreviewRender extends StatelessWidget { height: 28, width: 28, clipBehavior: Clip.antiAlias, - padding: const EdgeInsets.all(8), + padding: const EdgeInsets.symmetric(horizontal: 8), decoration: BoxDecoration( color: AFThemeExtension.of(context).greyHover, borderRadius: BorderRadius.circular(4), diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_number_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_number_cell.dart index 04368bc725..7a6f3e63bc 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_number_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_number_cell.dart @@ -1,6 +1,6 @@ +import 'package:appflowy/plugins/database/application/cell/bloc/number_cell_bloc.dart'; import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; -import 'package:appflowy/plugins/database/application/cell/bloc/number_cell_bloc.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -11,27 +11,37 @@ class DesktopGridNumberCellSkin extends IEditableNumberCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, NumberCellBloc bloc, FocusNode focusNode, TextEditingController textEditingController, ) { - return TextField( - controller: textEditingController, - focusNode: focusNode, - onEditingComplete: () => focusNode.unfocus(), - onSubmitted: (_) => focusNode.unfocus(), - maxLines: context.watch().state.wrap ? null : 1, - style: Theme.of(context).textTheme.bodyMedium, - textInputAction: TextInputAction.done, - decoration: InputDecoration( - contentPadding: GridSize.cellContentInsets, - border: InputBorder.none, - focusedBorder: InputBorder.none, - enabledBorder: InputBorder.none, - errorBorder: InputBorder.none, - disabledBorder: InputBorder.none, - isDense: true, - ), + return ValueListenableBuilder( + valueListenable: compactModeNotifier, + builder: (context, compactMode, _) { + final padding = compactMode + ? GridSize.compactCellContentInsets + : GridSize.cellContentInsets; + + return TextField( + controller: textEditingController, + focusNode: focusNode, + onEditingComplete: () => focusNode.unfocus(), + onSubmitted: (_) => focusNode.unfocus(), + maxLines: context.watch().state.wrap ? null : 1, + style: Theme.of(context).textTheme.bodyMedium, + textInputAction: TextInputAction.done, + decoration: InputDecoration( + contentPadding: padding, + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + disabledBorder: InputBorder.none, + isDense: true, + ), + ); + }, ); } } diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_relation_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_relation_cell.dart index f4b8fb97f0..dda3183b59 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_relation_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_relation_cell.dart @@ -1,8 +1,9 @@ import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; -import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; -import 'package:appflowy/plugins/database/widgets/cell_editor/relation_cell_editor.dart'; import 'package:appflowy/plugins/database/application/cell/bloc/relation_cell_bloc.dart'; +import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; +import 'package:appflowy/plugins/database/widgets/cell_editor/relation_cell_editor.dart'; +import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; +import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; @@ -16,10 +17,12 @@ class DesktopGridRelationCellSkin extends IEditableRelationCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, RelationCellBloc bloc, RelationCellState state, PopoverController popoverController, ) { + final userWorkspaceBloc = context.read(); return AppFlowyPopover( controller: popoverController, direction: PopoverDirection.bottomWithLeftAligned, @@ -27,16 +30,24 @@ class DesktopGridRelationCellSkin extends IEditableRelationCellSkin { margin: EdgeInsets.zero, onClose: () => cellContainerNotifier.isFocus = false, popupBuilder: (context) { - return BlocProvider.value( - value: bloc, + return MultiBlocProvider( + providers: [ + BlocProvider.value(value: userWorkspaceBloc), + BlocProvider.value(value: bloc), + ], child: const RelationCellEditor(), ); }, child: Align( alignment: AlignmentDirectional.centerStart, - child: state.wrap - ? _buildWrapRows(context, state.rows) - : _buildNoWrapRows(context, state.rows), + child: ValueListenableBuilder( + valueListenable: compactModeNotifier, + builder: (context, compactMode, _) { + return state.wrap + ? _buildWrapRows(context, state.rows, compactMode) + : _buildNoWrapRows(context, state.rows, compactMode); + }, + ), ), ); } @@ -44,9 +55,12 @@ class DesktopGridRelationCellSkin extends IEditableRelationCellSkin { Widget _buildWrapRows( BuildContext context, List rows, + bool compactMode, ) { return Padding( - padding: GridSize.cellContentInsets, + padding: compactMode + ? GridSize.compactCellContentInsets + : GridSize.cellContentInsets, child: Wrap( runSpacing: 4, spacing: 4.0, @@ -68,6 +82,7 @@ class DesktopGridRelationCellSkin extends IEditableRelationCellSkin { Widget _buildNoWrapRows( BuildContext context, List rows, + bool compactMode, ) { return SingleChildScrollView( physics: const NeverScrollableScrollPhysics(), diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_select_option_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_select_option_cell.dart index 8cebb2d77a..b599acc4f1 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_select_option_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_select_option_cell.dart @@ -15,6 +15,7 @@ class DesktopGridSelectOptionCellSkin extends IEditableSelectOptionCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, SelectOptionCellBloc bloc, PopoverController popoverController, ) { @@ -35,63 +36,92 @@ class DesktopGridSelectOptionCellSkin extends IEditableSelectOptionCellSkin { return Align( alignment: AlignmentDirectional.centerStart, child: state.wrap - ? _buildWrapOptions(context, state.selectedOptions) - : _buildNoWrapOptions(context, state.selectedOptions), + ? _buildWrapOptions( + context, + state.selectedOptions, + compactModeNotifier, + ) + : _buildNoWrapOptions( + context, + state.selectedOptions, + compactModeNotifier, + ), ); }, ), ); } - Widget _buildWrapOptions(BuildContext context, List options) { - return Padding( - padding: GridSize.cellContentInsets, - child: Wrap( - runSpacing: 4, - children: options.map( - (option) { - return Padding( - padding: const EdgeInsets.only(right: 4), - child: SelectOptionTag( - option: option, - padding: const EdgeInsets.symmetric( - vertical: 4, - horizontal: 8, - ), - ), - ); - }, - ).toList(), - ), + Widget _buildWrapOptions( + BuildContext context, + List options, + ValueNotifier compactModeNotifier, + ) { + return ValueListenableBuilder( + valueListenable: compactModeNotifier, + builder: (context, compactMode, _) { + final padding = compactMode + ? GridSize.compactCellContentInsets + : GridSize.cellContentInsets; + return Padding( + padding: padding, + child: Wrap( + runSpacing: 4, + children: options.map( + (option) { + return Padding( + padding: const EdgeInsets.only(right: 4), + child: SelectOptionTag( + option: option, + padding: EdgeInsets.symmetric( + vertical: compactMode ? 2 : 4, + horizontal: 8, + ), + ), + ); + }, + ).toList(), + ), + ); + }, ); } Widget _buildNoWrapOptions( BuildContext context, List options, + ValueNotifier compactModeNotifier, ) { return SingleChildScrollView( physics: const NeverScrollableScrollPhysics(), scrollDirection: Axis.horizontal, - child: Padding( - padding: GridSize.cellContentInsets, - child: Row( - mainAxisSize: MainAxisSize.min, - children: options.map( - (option) { - return Padding( - padding: const EdgeInsets.only(right: 4), - child: SelectOptionTag( - option: option, - padding: const EdgeInsets.symmetric( - vertical: 1, - horizontal: 8, - ), - ), - ); - }, - ).toList(), - ), + child: ValueListenableBuilder( + valueListenable: compactModeNotifier, + builder: (context, compactMode, _) { + final padding = compactMode + ? GridSize.compactCellContentInsets + : GridSize.cellContentInsets; + return Padding( + padding: padding, + child: Row( + mainAxisSize: MainAxisSize.min, + children: options.map( + (option) { + return Padding( + padding: const EdgeInsets.only(right: 4), + child: SelectOptionTag( + option: option, + padding: const EdgeInsets.symmetric( + vertical: 1, + horizontal: 8, + ), + ), + ); + }, + ).toList(), + ), + ); + }, ), ); } diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_summary_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_summary_cell.dart index b5d915b022..1f3ded0109 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_summary_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_summary_cell.dart @@ -11,6 +11,7 @@ class DesktopGridSummaryCellSkin extends IEditableSummaryCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, SummaryCellBloc bloc, FocusNode focusNode, TextEditingController textEditingController, @@ -27,58 +28,69 @@ class DesktopGridSummaryCellSkin extends IEditableSummaryCellSkin { onExit: (p) => Provider.of(context, listen: false) .onEnter = false, - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: GridSize.headerHeight, - ), - child: Stack( - fit: StackFit.expand, - children: [ - Center( - child: TextField( - controller: textEditingController, - enabled: false, - focusNode: focusNode, - onEditingComplete: () => focusNode.unfocus(), - onSubmitted: (_) => focusNode.unfocus(), - maxLines: null, - style: Theme.of(context).textTheme.bodyMedium, - textInputAction: TextInputAction.done, - decoration: InputDecoration( - contentPadding: GridSize.cellContentInsets, - border: InputBorder.none, - focusedBorder: InputBorder.none, - enabledBorder: InputBorder.none, - errorBorder: InputBorder.none, - disabledBorder: InputBorder.none, - isDense: true, - ), - ), + child: ValueListenableBuilder( + valueListenable: compactModeNotifier, + builder: (context, compactMode, _) { + final padding = compactMode + ? GridSize.compactCellContentInsets + : GridSize.cellContentInsets; + + return ConstrainedBox( + constraints: BoxConstraints( + minHeight: compactMode + ? GridSize.headerHeight - 4 + : GridSize.headerHeight, ), - Padding( - padding: EdgeInsets.symmetric( - horizontal: GridSize.cellVPadding, - ), - child: Consumer( - builder: ( - BuildContext context, - SummaryMouseNotifier notifier, - Widget? child, - ) { - if (notifier.onEnter) { - return SummaryCellAccessory( - viewId: bloc.cellController.viewId, - fieldId: bloc.cellController.fieldId, - rowId: bloc.cellController.rowId, - ); - } else { - return const SizedBox.shrink(); - } - }, - ), - ).positioned(right: 0, bottom: 8), - ], - ), + child: Stack( + fit: StackFit.expand, + children: [ + Center( + child: TextField( + controller: textEditingController, + enabled: false, + focusNode: focusNode, + onEditingComplete: () => focusNode.unfocus(), + onSubmitted: (_) => focusNode.unfocus(), + maxLines: null, + style: Theme.of(context).textTheme.bodyMedium, + textInputAction: TextInputAction.done, + decoration: InputDecoration( + contentPadding: padding, + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + disabledBorder: InputBorder.none, + isDense: true, + ), + ), + ), + Padding( + padding: EdgeInsets.symmetric( + horizontal: GridSize.cellVPadding, + ), + child: Consumer( + builder: ( + BuildContext context, + SummaryMouseNotifier notifier, + Widget? child, + ) { + if (notifier.onEnter) { + return SummaryCellAccessory( + viewId: bloc.cellController.viewId, + fieldId: bloc.cellController.fieldId, + rowId: bloc.cellController.rowId, + ); + } else { + return const SizedBox.shrink(); + } + }, + ), + ).positioned(right: 0, bottom: compactMode ? 4 : 8), + ], + ), + ); + }, ), ); }, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_text_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_text_cell.dart index f28cb756c8..75c973d886 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_text_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_text_cell.dart @@ -13,43 +13,52 @@ class DesktopGridTextCellSkin extends IEditableTextCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, TextCellBloc bloc, FocusNode focusNode, TextEditingController textEditingController, ) { - return Padding( - padding: GridSize.cellContentInsets, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const _IconOrEmoji(), - Expanded( - child: TextField( - controller: textEditingController, - focusNode: focusNode, - maxLines: context.watch().state.wrap ? null : 1, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - fontWeight: context - .read() - .cellController - .fieldInfo - .isPrimary - ? FontWeight.w500 - : null, + return ValueListenableBuilder( + valueListenable: compactModeNotifier, + builder: (context, compactMode, data) { + final padding = compactMode + ? GridSize.compactCellContentInsets + : GridSize.cellContentInsets; + return Padding( + padding: padding, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const _IconOrEmoji(), + Expanded( + child: TextField( + controller: textEditingController, + focusNode: focusNode, + maxLines: context.watch().state.wrap ? null : 1, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontWeight: context + .read() + .cellController + .fieldInfo + .isPrimary + ? FontWeight.w500 + : null, + ), + decoration: const InputDecoration( + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + disabledBorder: InputBorder.none, + isDense: true, + isCollapsed: true, ), - decoration: const InputDecoration( - border: InputBorder.none, - focusedBorder: InputBorder.none, - enabledBorder: InputBorder.none, - errorBorder: InputBorder.none, - disabledBorder: InputBorder.none, - isDense: true, - isCollapsed: true, + ), ), - ), + ], ), - ], - ), + ); + }, ); } } diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_timestamp_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_timestamp_cell.dart index a1690310d4..8a1fd92499 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_timestamp_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_timestamp_cell.dart @@ -1,6 +1,6 @@ +import 'package:appflowy/plugins/database/application/cell/bloc/timestamp_cell_bloc.dart'; import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; -import 'package:appflowy/plugins/database/application/cell/bloc/timestamp_cell_bloc.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/widgets.dart'; @@ -11,29 +11,41 @@ class DesktopGridTimestampCellSkin extends IEditableTimestampCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, TimestampCellBloc bloc, TimestampCellState state, ) { return Container( alignment: AlignmentDirectional.centerStart, child: state.wrap - ? _buildCellContent(state) + ? _buildCellContent(state, compactModeNotifier) : SingleChildScrollView( physics: const NeverScrollableScrollPhysics(), scrollDirection: Axis.horizontal, - child: _buildCellContent(state), + child: _buildCellContent(state, compactModeNotifier), ), ); } - Widget _buildCellContent(TimestampCellState state) { - return Padding( - padding: GridSize.cellContentInsets, - child: FlowyText( - state.dateStr, - overflow: state.wrap ? null : TextOverflow.ellipsis, - maxLines: state.wrap ? null : 1, - ), + Widget _buildCellContent( + TimestampCellState state, + ValueNotifier compactModeNotifier, + ) { + return ValueListenableBuilder( + valueListenable: compactModeNotifier, + builder: (context, compactMode, _) { + final padding = compactMode + ? GridSize.compactCellContentInsets + : GridSize.cellContentInsets; + return Padding( + padding: padding, + child: FlowyText( + state.dateStr, + overflow: state.wrap ? null : TextOverflow.ellipsis, + maxLines: state.wrap ? null : 1, + ), + ); + }, ); } } diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_translate_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_translate_cell.dart index aece28373c..102b491f52 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_translate_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_translate_cell.dart @@ -11,6 +11,7 @@ class DesktopGridTranslateCellSkin extends IEditableTranslateCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, TranslateCellBloc bloc, FocusNode focusNode, TextEditingController textEditingController, @@ -18,68 +19,79 @@ class DesktopGridTranslateCellSkin extends IEditableTranslateCellSkin { return ChangeNotifierProvider( create: (_) => TranslateMouseNotifier(), builder: (context, child) { - return MouseRegion( - cursor: SystemMouseCursors.click, - opaque: false, - onEnter: (p) => - Provider.of(context, listen: false) - .onEnter = true, - onExit: (p) => - Provider.of(context, listen: false) - .onEnter = false, - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: GridSize.headerHeight, - ), - child: Stack( - fit: StackFit.expand, - children: [ - Center( - child: TextField( - controller: textEditingController, - readOnly: true, - focusNode: focusNode, - onEditingComplete: () => focusNode.unfocus(), - onSubmitted: (_) => focusNode.unfocus(), - maxLines: null, - style: Theme.of(context).textTheme.bodyMedium, - textInputAction: TextInputAction.done, - decoration: InputDecoration( - contentPadding: GridSize.cellContentInsets, - border: InputBorder.none, - focusedBorder: InputBorder.none, - enabledBorder: InputBorder.none, - errorBorder: InputBorder.none, - disabledBorder: InputBorder.none, - isDense: true, - ), - ), + return ValueListenableBuilder( + valueListenable: compactModeNotifier, + builder: (context, compactMode, _) { + final padding = compactMode + ? GridSize.compactCellContentInsets + : GridSize.cellContentInsets; + + return MouseRegion( + cursor: SystemMouseCursors.click, + opaque: false, + onEnter: (p) => + Provider.of(context, listen: false) + .onEnter = true, + onExit: (p) => + Provider.of(context, listen: false) + .onEnter = false, + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: compactMode + ? GridSize.headerHeight - 4 + : GridSize.headerHeight, ), - Padding( - padding: EdgeInsets.symmetric( - horizontal: GridSize.cellVPadding, - ), - child: Consumer( - builder: ( - BuildContext context, - TranslateMouseNotifier notifier, - Widget? child, - ) { - if (notifier.onEnter) { - return TranslateCellAccessory( - viewId: bloc.cellController.viewId, - fieldId: bloc.cellController.fieldId, - rowId: bloc.cellController.rowId, - ); - } else { - return const SizedBox.shrink(); - } - }, - ), - ).positioned(right: 0, bottom: 8), - ], - ), - ), + child: Stack( + fit: StackFit.expand, + children: [ + Center( + child: TextField( + controller: textEditingController, + readOnly: true, + focusNode: focusNode, + onEditingComplete: () => focusNode.unfocus(), + onSubmitted: (_) => focusNode.unfocus(), + maxLines: null, + style: Theme.of(context).textTheme.bodyMedium, + textInputAction: TextInputAction.done, + decoration: InputDecoration( + contentPadding: padding, + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + disabledBorder: InputBorder.none, + isDense: true, + ), + ), + ), + Padding( + padding: EdgeInsets.symmetric( + horizontal: GridSize.cellVPadding, + ), + child: Consumer( + builder: ( + BuildContext context, + TranslateMouseNotifier notifier, + Widget? child, + ) { + if (notifier.onEnter) { + return TranslateCellAccessory( + viewId: bloc.cellController.viewId, + fieldId: bloc.cellController.fieldId, + rowId: bloc.cellController.rowId, + ); + } else { + return const SizedBox.shrink(); + } + }, + ), + ).positioned(right: 0, bottom: compactMode ? 4 : 8), + ], + ), + ), + ); + }, ); }, ); diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_url_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_url_cell.dart index 17a3519d3d..935716e686 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_url_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_grid/desktop_grid_url_cell.dart @@ -21,6 +21,7 @@ class DesktopGridURLSkin extends IEditableURLCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, URLCellBloc bloc, FocusNode focusNode, TextEditingController textEditingController, @@ -28,28 +29,36 @@ class DesktopGridURLSkin extends IEditableURLCellSkin { ) { return BlocSelector( selector: (state) => state.wrap, - builder: (context, wrap) => TextField( - controller: textEditingController, - focusNode: focusNode, - maxLines: wrap ? null : 1, - style: Theme.of(context).textTheme.bodyMedium?.copyWith( - color: Theme.of(context).colorScheme.primary, - decoration: TextDecoration.underline, + builder: (context, wrap) => ValueListenableBuilder( + valueListenable: compactModeNotifier, + builder: (context, compactMode, _) { + final padding = compactMode + ? GridSize.compactCellContentInsets + : GridSize.cellContentInsets; + return TextField( + controller: textEditingController, + focusNode: focusNode, + maxLines: wrap ? null : 1, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: Theme.of(context).colorScheme.primary, + decoration: TextDecoration.underline, + ), + decoration: InputDecoration( + contentPadding: padding, + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + errorBorder: InputBorder.none, + disabledBorder: InputBorder.none, + hintStyle: Theme.of(context) + .textTheme + .bodyMedium + ?.copyWith(color: Theme.of(context).hintColor), + isDense: true, ), - decoration: InputDecoration( - contentPadding: GridSize.cellContentInsets, - border: InputBorder.none, - focusedBorder: InputBorder.none, - enabledBorder: InputBorder.none, - errorBorder: InputBorder.none, - disabledBorder: InputBorder.none, - hintStyle: Theme.of(context) - .textTheme - .bodyMedium - ?.copyWith(color: Theme.of(context).hintColor), - isDense: true, - ), - onTapOutside: (_) => focusNode.unfocus(), + onTapOutside: (_) => focusNode.unfocus(), + ); + }, ), ); } diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_checkbox_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_checkbox_cell.dart index bb15cd5d9f..1e56c5160e 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_checkbox_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_checkbox_cell.dart @@ -1,6 +1,6 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; -import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:appflowy/plugins/database/application/cell/bloc/checkbox_cell_bloc.dart'; +import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; @@ -11,6 +11,7 @@ class DesktopRowDetailCheckboxCellSkin extends IEditableCheckboxCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, CheckboxCellBloc bloc, CheckboxCellState state, ) { diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_checklist_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_checklist_cell.dart index d7e1d64fc3..ab0533819a 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_checklist_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_checklist_cell.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database/application/cell/bloc/checklist_cell_bloc.dart'; @@ -16,6 +14,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:provider/provider.dart'; +import 'package:universal_platform/universal_platform.dart'; import '../editable_cell_skeleton/checklist.dart'; @@ -24,6 +23,7 @@ class DesktopRowDetailChecklistCellSkin extends IEditableChecklistCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, ChecklistCellBloc bloc, PopoverController popoverController, ) { @@ -200,19 +200,16 @@ class _ChecklistItems extends StatelessWidget { physics: const NeverScrollableScrollPhysics(), proxyDecorator: (child, index, _) => Material( color: Colors.transparent, - child: Stack( - children: [ - BlocProvider.value( + child: MouseRegion( + cursor: UniversalPlatform.isWindows + ? SystemMouseCursors.click + : SystemMouseCursors.grabbing, + child: IgnorePointer( + child: BlocProvider.value( value: context.read(), child: child, ), - MouseRegion( - cursor: Platform.isWindows - ? SystemMouseCursors.click - : SystemMouseCursors.grabbing, - child: const SizedBox.expand(), - ), - ], + ), ), ), buildDefaultDragHandles: false, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_date_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_date_cell.dart index f10f9a9279..f1b5f14975 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_date_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_date_cell.dart @@ -1,9 +1,9 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/date.dart'; -import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:appflowy/plugins/database/application/cell/bloc/date_cell_bloc.dart'; +import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/date.dart'; import 'package:appflowy/plugins/database/widgets/cell_editor/date_cell_editor.dart'; +import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; @@ -13,6 +13,7 @@ class DesktopRowDetailDateCellSkin extends IEditableDateCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, DateCellBloc bloc, DateCellState state, PopoverController popoverController, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_media_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_media_cell.dart index 34d8a18ad8..6e648eb187 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_media_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_media_cell.dart @@ -202,7 +202,7 @@ class _FilePreviewFeedback extends StatelessWidget { decoration: BoxDecoration( boxShadow: [ BoxShadow( - color: const Color(0xFF1F2329).withOpacity(.2), + color: const Color(0xFF1F2329).withValues(alpha: .2), blurRadius: 6, offset: const Offset(0, 3), ), @@ -431,7 +431,8 @@ class _FilePreviewRenderState extends State<_FilePreviewRender> { Positioned.fill( child: DecoratedBox( position: DecorationPosition.foreground, - decoration: BoxDecoration(color: Colors.black.withOpacity(0.5)), + decoration: + BoxDecoration(color: Colors.black.withValues(alpha: 0.5)), child: child, ), ), @@ -543,7 +544,7 @@ class _FilePreviewRenderState extends State<_FilePreviewRender> { setState(() => isSelected = true); controller.show(); }, - fillColor: Colors.black.withOpacity(0.4), + fillColor: Colors.black.withValues(alpha: 0.4), width: 18, radius: BorderRadius.circular(4), icon: const FlowySvg( diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_number_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_number_cell.dart index 97f8f80569..e90fc85549 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_number_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_number_cell.dart @@ -1,6 +1,6 @@ import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:appflowy/plugins/database/application/cell/bloc/number_cell_bloc.dart'; +import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -11,6 +11,7 @@ class DesktopRowDetailNumberCellSkin extends IEditableNumberCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, NumberCellBloc bloc, FocusNode focusNode, TextEditingController textEditingController, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_relation_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_relation_cell.dart index b3096d49e4..d760d3ac29 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_relation_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_relation_cell.dart @@ -1,7 +1,8 @@ import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; -import 'package:appflowy/plugins/database/widgets/cell_editor/relation_cell_editor.dart'; import 'package:appflowy/plugins/database/application/cell/bloc/relation_cell_bloc.dart'; +import 'package:appflowy/plugins/database/widgets/cell_editor/relation_cell_editor.dart'; +import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; +import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; @@ -15,10 +16,12 @@ class DesktopRowDetailRelationCellSkin extends IEditableRelationCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, RelationCellBloc bloc, RelationCellState state, PopoverController popoverController, ) { + final userWorkspaceBloc = context.read(); return AppFlowyPopover( controller: popoverController, direction: PopoverDirection.bottomWithLeftAligned, @@ -27,8 +30,11 @@ class DesktopRowDetailRelationCellSkin extends IEditableRelationCellSkin { asBarrier: true, onClose: () => cellContainerNotifier.isFocus = false, popupBuilder: (context) { - return BlocProvider.value( - value: bloc, + return MultiBlocProvider( + providers: [ + BlocProvider.value(value: userWorkspaceBloc), + BlocProvider.value(value: bloc), + ], child: const RelationCellEditor(), ); }, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_select_option_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_select_option_cell.dart index 3d41a824ce..ff84744c27 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_select_option_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_select_option_cell.dart @@ -1,5 +1,3 @@ -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database/application/cell/bloc/select_option_cell_bloc.dart'; import 'package:appflowy/plugins/database/widgets/cell_editor/extension.dart'; @@ -8,6 +6,7 @@ import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart' import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../editable_cell_skeleton/select_option.dart'; @@ -18,6 +17,7 @@ class DesktopRowDetailSelectOptionCellSkin Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, SelectOptionCellBloc bloc, PopoverController popoverController, ) { diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_summary_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_summary_cell.dart index d8a8902a8c..30cd54832d 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_summary_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_summary_cell.dart @@ -10,6 +10,7 @@ class DesktopRowDetailSummaryCellSkin extends IEditableSummaryCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, SummaryCellBloc bloc, FocusNode focusNode, TextEditingController textEditingController, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_text_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_text_cell.dart index b1e10e4da3..9511c2f871 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_text_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_text_cell.dart @@ -1,6 +1,6 @@ import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:appflowy/plugins/database/application/cell/bloc/text_cell_bloc.dart'; +import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -11,6 +11,7 @@ class DesktopRowDetailTextCellSkin extends IEditableTextCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, TextCellBloc bloc, FocusNode focusNode, TextEditingController textEditingController, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_timestamp_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_timestamp_cell.dart index af212c6dfd..6fc534f313 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_timestamp_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_timestamp_cell.dart @@ -1,5 +1,5 @@ -import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:appflowy/plugins/database/application/cell/bloc/timestamp_cell_bloc.dart'; +import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/widgets.dart'; @@ -10,6 +10,7 @@ class DesktopRowDetailTimestampCellSkin extends IEditableTimestampCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, TimestampCellBloc bloc, TimestampCellState state, ) { diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_url_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_url_cell.dart index 00d7372027..ee9d7e7300 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_url_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/desktop_row_detail_url_cell.dart @@ -1,8 +1,8 @@ import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/database/application/cell/bloc/url_cell_bloc.dart'; import 'package:appflowy/plugins/database/widgets/cell/desktop_grid/desktop_grid_url_cell.dart'; import 'package:appflowy/plugins/database/widgets/row/accessory/cell_accessory.dart'; import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; -import 'package:appflowy/plugins/database/application/cell/bloc/url_cell_bloc.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -14,6 +14,7 @@ class DesktopRowDetailURLSkin extends IEditableURLCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, URLCellBloc bloc, FocusNode focusNode, TextEditingController textEditingController, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/destop_row_detail_translate_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/destop_row_detail_translate_cell.dart index 1c7bab9f92..a374417b3d 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/destop_row_detail_translate_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/desktop_row_detail/destop_row_detail_translate_cell.dart @@ -10,6 +10,7 @@ class DesktopRowDetailTranslateCellSkin extends IEditableTranslateCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, TranslateCellBloc bloc, FocusNode focusNode, TextEditingController textEditingController, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/checkbox.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/checkbox.dart index 4b7bd2c442..ab421b8925 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/checkbox.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/checkbox.dart @@ -1,9 +1,9 @@ +import 'package:appflowy/plugins/database/application/cell/bloc/checkbox_cell_bloc.dart'; import 'package:appflowy/plugins/database/application/cell/cell_controller.dart'; import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart'; import 'package:appflowy/plugins/database/application/database_controller.dart'; -import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; -import 'package:appflowy/plugins/database/application/cell/bloc/checkbox_cell_bloc.dart'; import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart'; +import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -27,6 +27,7 @@ abstract class IEditableCheckboxCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, CheckboxCellBloc bloc, CheckboxCellState state, ); @@ -71,6 +72,7 @@ class _CheckboxCellState extends GridCellState { return widget.skin.build( context, widget.cellContainerNotifier, + widget.databaseController.compactModeNotifier, cellBloc, state, ); diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/checklist.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/checklist.dart index 4cdebee36d..fbed429642 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/checklist.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/checklist.dart @@ -1,9 +1,9 @@ +import 'package:appflowy/plugins/database/application/cell/bloc/checklist_cell_bloc.dart'; import 'package:appflowy/plugins/database/application/cell/cell_controller.dart'; import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart'; import 'package:appflowy/plugins/database/application/database_controller.dart'; -import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; -import 'package:appflowy/plugins/database/application/cell/bloc/checklist_cell_bloc.dart'; import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart'; +import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -28,6 +28,7 @@ abstract class IEditableChecklistCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, ChecklistCellBloc bloc, PopoverController popoverController, ); @@ -72,6 +73,7 @@ class GridChecklistCellState extends GridCellState { child: widget.skin.build( context, widget.cellContainerNotifier, + widget.databaseController.compactModeNotifier, cellBloc, _popover, ), diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/date.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/date.dart index 877b4c6dfb..e61c759f48 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/date.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/date.dart @@ -1,12 +1,12 @@ +import 'package:appflowy/plugins/database/application/cell/bloc/date_cell_bloc.dart'; import 'package:appflowy/plugins/database/application/cell/bloc/date_cell_editor_bloc.dart'; import 'package:appflowy/plugins/database/application/cell/cell_controller.dart'; import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart'; import 'package:appflowy/plugins/database/application/database_controller.dart'; import 'package:appflowy/plugins/database/application/field/field_info.dart'; import 'package:appflowy/plugins/database/application/field/type_option/type_option_data_parser.dart'; -import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; -import 'package:appflowy/plugins/database/application/cell/bloc/date_cell_bloc.dart'; import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart'; +import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flutter/material.dart'; @@ -33,6 +33,7 @@ abstract class IEditableDateCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, DateCellBloc bloc, DateCellState state, PopoverController popoverController, @@ -79,6 +80,7 @@ class _DateCellState extends GridCellState { return widget.skin.build( context, widget.cellContainerNotifier, + widget.databaseController.compactModeNotifier, cellBloc, state, _popover, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/number.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/number.dart index b218c78195..4d2bfdf627 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/number.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/number.dart @@ -1,11 +1,11 @@ import 'dart:async'; +import 'package:appflowy/plugins/database/application/cell/bloc/number_cell_bloc.dart'; import 'package:appflowy/plugins/database/application/cell/cell_controller.dart'; import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart'; import 'package:appflowy/plugins/database/application/database_controller.dart'; -import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; -import 'package:appflowy/plugins/database/application/cell/bloc/number_cell_bloc.dart'; import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart'; +import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -29,6 +29,7 @@ abstract class IEditableNumberCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, NumberCellBloc bloc, FocusNode focusNode, TextEditingController textEditingController, @@ -89,6 +90,7 @@ class _NumberCellState extends GridEditableTextCell { return widget.skin.build( context, widget.cellContainerNotifier, + widget.databaseController.compactModeNotifier, cellBloc, focusNode, _textEditingController, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/relation.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/relation.dart index 4e39900abf..67ca6275a6 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/relation.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/relation.dart @@ -1,9 +1,9 @@ +import 'package:appflowy/plugins/database/application/cell/bloc/relation_cell_bloc.dart'; import 'package:appflowy/plugins/database/application/cell/cell_controller.dart'; import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart'; import 'package:appflowy/plugins/database/application/database_controller.dart'; -import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart'; -import 'package:appflowy/plugins/database/application/cell/bloc/relation_cell_bloc.dart'; +import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -28,6 +28,7 @@ abstract class IEditableRelationCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, RelationCellBloc bloc, RelationCellState state, PopoverController popoverController, @@ -74,6 +75,7 @@ class _RelationCellState extends GridCellState { return widget.skin.build( context, widget.cellContainerNotifier, + widget.databaseController.compactModeNotifier, cellBloc, state, _popover, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/select_option.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/select_option.dart index b45018f4f5..f7e8b6f435 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/select_option.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/select_option.dart @@ -1,9 +1,9 @@ +import 'package:appflowy/plugins/database/application/cell/bloc/select_option_cell_bloc.dart'; import 'package:appflowy/plugins/database/application/cell/cell_controller.dart'; import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart'; import 'package:appflowy/plugins/database/application/database_controller.dart'; -import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; -import 'package:appflowy/plugins/database/application/cell/bloc/select_option_cell_bloc.dart'; import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart'; +import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flutter/material.dart'; @@ -31,6 +31,7 @@ abstract class IEditableSelectOptionCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, SelectOptionCellBloc bloc, PopoverController popoverController, ); @@ -79,6 +80,7 @@ class _SelectOptionCellState extends GridCellState { child: widget.skin.build( context, widget.cellContainerNotifier, + widget.databaseController.compactModeNotifier, cellBloc, _popover, ), diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/summary.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/summary.dart index d3b43b0d17..7a086b2a35 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/summary.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/summary.dart @@ -1,6 +1,3 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database/application/cell/bloc/summary_cell_bloc.dart'; @@ -22,6 +19,8 @@ import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart'; import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; abstract class IEditableSummaryCellSkin { @@ -39,6 +38,7 @@ abstract class IEditableSummaryCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, SummaryCellBloc bloc, FocusNode focusNode, TextEditingController textEditingController, @@ -98,6 +98,7 @@ class _SummaryCellState extends GridEditableTextCell { return widget.skin.build( context, widget.cellContainerNotifier, + widget.databaseController.compactModeNotifier, cellBloc, focusNode, _textEditingController, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/text.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/text.dart index 7666919c49..3ea622374e 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/text.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/text.dart @@ -1,11 +1,11 @@ import 'dart:async'; +import 'package:appflowy/plugins/database/application/cell/bloc/text_cell_bloc.dart'; import 'package:appflowy/plugins/database/application/cell/cell_controller.dart'; import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart'; import 'package:appflowy/plugins/database/application/database_controller.dart'; -import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; -import 'package:appflowy/plugins/database/application/cell/bloc/text_cell_bloc.dart'; import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart'; +import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -29,6 +29,7 @@ abstract class IEditableTextCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, TextCellBloc bloc, FocusNode focusNode, TextEditingController textEditingController, @@ -92,6 +93,7 @@ class _TextCellState extends GridEditableTextCell { return widget.skin.build( context, widget.cellContainerNotifier, + widget.databaseController.compactModeNotifier, cellBloc, focusNode, _textEditingController, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/timestamp.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/timestamp.dart index 6c00e8b4b4..2fc9d049cc 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/timestamp.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/timestamp.dart @@ -1,9 +1,9 @@ +import 'package:appflowy/plugins/database/application/cell/bloc/timestamp_cell_bloc.dart'; import 'package:appflowy/plugins/database/application/cell/cell_controller.dart'; import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart'; import 'package:appflowy/plugins/database/application/database_controller.dart'; -import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; -import 'package:appflowy/plugins/database/application/cell/bloc/timestamp_cell_bloc.dart'; import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart'; +import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -28,6 +28,7 @@ abstract class IEditableTimestampCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, TimestampCellBloc bloc, TimestampCellState state, ); @@ -74,6 +75,7 @@ class _TimestampCellState extends GridCellState { return widget.skin.build( context, widget.cellContainerNotifier, + widget.databaseController.compactModeNotifier, cellBloc, state, ); diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/translate.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/translate.dart index b4ff26d946..b273419aed 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/translate.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/translate.dart @@ -1,6 +1,3 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database/application/cell/bloc/translate_cell_bloc.dart'; @@ -22,6 +19,8 @@ import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart'; import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; abstract class IEditableTranslateCellSkin { @@ -39,6 +38,7 @@ abstract class IEditableTranslateCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, TranslateCellBloc bloc, FocusNode focusNode, TextEditingController textEditingController, @@ -98,6 +98,7 @@ class _TranslateCellState extends GridEditableTextCell { return widget.skin.build( context, widget.cellContainerNotifier, + widget.databaseController.compactModeNotifier, cellBloc, focusNode, _textEditingController, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/url.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/url.dart index ef18573e1a..39616dbcf8 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/url.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/url.dart @@ -4,13 +4,13 @@ import 'dart:io'; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart'; +import 'package:appflowy/plugins/database/application/cell/bloc/url_cell_bloc.dart'; import 'package:appflowy/plugins/database/application/cell/cell_controller.dart'; import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart'; import 'package:appflowy/plugins/database/application/database_controller.dart'; +import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart'; import 'package:appflowy/plugins/database/widgets/row/accessory/cell_accessory.dart'; import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; -import 'package:appflowy/plugins/database/application/cell/bloc/url_cell_bloc.dart'; -import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; @@ -40,6 +40,7 @@ abstract class IEditableURLCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, URLCellBloc bloc, FocusNode focusNode, TextEditingController textEditingController, @@ -121,6 +122,7 @@ class _GridURLCellState extends GridEditableTextCell { child: widget.skin.build( context, widget.cellContainerNotifier, + widget.databaseController.compactModeNotifier, cellBloc, focusNode, _textEditingController, @@ -193,7 +195,7 @@ class MobileURLEditor extends StatelessWidget { icon: FlowySvgs.url_s, text: LocaleKeys.grid_url_launch.tr(), ), - const Divider(height: 8.5, thickness: 0.5), + const MobileQuickActionDivider(), MobileQuickActionButton( enable: context.watch().state.content.isNotEmpty, onTap: () { @@ -201,7 +203,7 @@ class MobileURLEditor extends StatelessWidget { ClipboardData(text: textEditingController.text), ); Fluttertoast.showToast( - msg: LocaleKeys.grid_url_copiedNotification.tr(), + msg: LocaleKeys.message_copy_success.tr(), gravity: ToastGravity.BOTTOM, ); context.pop(); @@ -209,7 +211,6 @@ class MobileURLEditor extends StatelessWidget { icon: FlowySvgs.copy_s, text: LocaleKeys.grid_url_copy.tr(), ), - const Divider(height: 8.5, thickness: 0.5), ], ); } diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_checkbox_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_checkbox_cell.dart index 8859372c2a..e9ac19c874 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_checkbox_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_checkbox_cell.dart @@ -1,6 +1,6 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; -import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:appflowy/plugins/database/application/cell/bloc/checkbox_cell_bloc.dart'; +import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:flutter/material.dart'; import '../editable_cell_skeleton/checkbox.dart'; @@ -10,6 +10,7 @@ class MobileGridCheckboxCellSkin extends IEditableCheckboxCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, CheckboxCellBloc bloc, CheckboxCellState state, ) { diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_checklist_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_checklist_cell.dart index ff9f83319f..c56d28e1a7 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_checklist_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_checklist_cell.dart @@ -1,9 +1,9 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart'; -import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; -import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:appflowy/plugins/database/application/cell/bloc/checklist_cell_bloc.dart'; +import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; import 'package:appflowy/plugins/database/widgets/cell_editor/checklist_progress_bar.dart'; import 'package:appflowy/plugins/database/widgets/cell_editor/mobile_checklist_cell_editor.dart'; +import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -15,6 +15,7 @@ class MobileGridChecklistCellSkin extends IEditableChecklistCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, ChecklistCellBloc bloc, PopoverController popoverController, ) { diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_date_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_date_cell.dart index 43b6b7f347..5686e09295 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_date_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_date_cell.dart @@ -1,18 +1,18 @@ -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/database/date_picker/mobile_date_picker_screen.dart'; +import 'package:appflowy/plugins/database/application/cell/bloc/date_cell_bloc.dart'; import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/date.dart'; import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; -import 'package:appflowy/plugins/database/application/cell/bloc/date_cell_bloc.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; class MobileGridDateCellSkin extends IEditableDateCellSkin { @override Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, DateCellBloc bloc, DateCellState state, PopoverController popoverController, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_number_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_number_cell.dart index c02cf6aa8d..310c0b5692 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_number_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_number_cell.dart @@ -1,5 +1,5 @@ -import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:appflowy/plugins/database/application/cell/bloc/number_cell_bloc.dart'; +import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:flutter/material.dart'; import '../editable_cell_skeleton/number.dart'; @@ -9,6 +9,7 @@ class MobileGridNumberCellSkin extends IEditableNumberCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, NumberCellBloc bloc, FocusNode focusNode, TextEditingController textEditingController, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_relation_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_relation_cell.dart index 0951e2fb0d..69e9b20104 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_relation_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_relation_cell.dart @@ -1,6 +1,6 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart'; -import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:appflowy/plugins/database/application/cell/bloc/relation_cell_bloc.dart'; +import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; @@ -11,6 +11,7 @@ class MobileGridRelationCellSkin extends IEditableRelationCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, RelationCellBloc bloc, RelationCellState state, PopoverController popoverController, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_select_option_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_select_option_cell.dart index 61f67fec4f..010974e49a 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_select_option_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_select_option_cell.dart @@ -1,8 +1,8 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart'; -import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; +import 'package:appflowy/plugins/database/application/cell/bloc/select_option_cell_bloc.dart'; import 'package:appflowy/plugins/database/widgets/cell_editor/extension.dart'; import 'package:appflowy/plugins/database/widgets/cell_editor/mobile_select_option_editor.dart'; -import 'package:appflowy/plugins/database/application/cell/bloc/select_option_cell_bloc.dart'; +import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/select_option_entities.pb.dart'; import 'package:collection/collection.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; @@ -16,6 +16,7 @@ class MobileGridSelectOptionCellSkin extends IEditableSelectOptionCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, SelectOptionCellBloc bloc, PopoverController popoverController, ) { diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_summary_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_summary_cell.dart index 0da8d6bc64..e48c56d74d 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_summary_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_summary_cell.dart @@ -12,6 +12,7 @@ class MobileGridSummaryCellSkin extends IEditableSummaryCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, SummaryCellBloc bloc, FocusNode focusNode, TextEditingController textEditingController, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_text_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_text_cell.dart index 40e8c35319..43a4fe49d7 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_text_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_text_cell.dart @@ -11,6 +11,7 @@ class MobileGridTextCellSkin extends IEditableTextCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, TextCellBloc bloc, FocusNode focusNode, TextEditingController textEditingController, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_timestamp_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_timestamp_cell.dart index d9e020eece..68209e7e05 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_timestamp_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_timestamp_cell.dart @@ -1,5 +1,5 @@ -import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:appflowy/plugins/database/application/cell/bloc/timestamp_cell_bloc.dart'; +import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; @@ -10,6 +10,7 @@ class MobileGridTimestampCellSkin extends IEditableTimestampCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, TimestampCellBloc bloc, TimestampCellState state, ) { diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_translate_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_translate_cell.dart index 3a7b44cbc5..4288136734 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_translate_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_translate_cell.dart @@ -12,6 +12,7 @@ class MobileGridTranslateCellSkin extends IEditableTranslateCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, TranslateCellBloc bloc, FocusNode focusNode, TextEditingController textEditingController, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_url_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_url_cell.dart index cddb821943..0dbe5474c7 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_url_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_grid/mobile_grid_url_cell.dart @@ -13,6 +13,7 @@ class MobileGridURLCellSkin extends IEditableURLCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, URLCellBloc bloc, FocusNode focusNode, TextEditingController textEditingController, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_checkbox_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_checkbox_cell.dart index 279e790913..ade82e8c5c 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_checkbox_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_checkbox_cell.dart @@ -1,9 +1,8 @@ -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/plugins/database/application/cell/bloc/checkbox_cell_bloc.dart'; import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:flowy_infra/theme_extension.dart'; +import 'package:flutter/material.dart'; import '../editable_cell_skeleton/checkbox.dart'; @@ -12,6 +11,7 @@ class MobileRowDetailCheckboxCellSkin extends IEditableCheckboxCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, CheckboxCellBloc bloc, CheckboxCellState state, ) { diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_checklist_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_checklist_cell.dart index daf085e2ce..75eee9a560 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_checklist_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_checklist_cell.dart @@ -17,6 +17,7 @@ class MobileRowDetailChecklistCellSkin extends IEditableChecklistCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, ChecklistCellBloc bloc, PopoverController popoverController, ) { diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_date_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_date_cell.dart index 8671bacd8f..0256ee25cf 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_date_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_date_cell.dart @@ -2,9 +2,9 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/database/date_picker/mobile_date_picker_screen.dart'; +import 'package:appflowy/plugins/database/application/cell/bloc/date_cell_bloc.dart'; import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/date.dart'; import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; -import 'package:appflowy/plugins/database/application/cell/bloc/date_cell_bloc.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; @@ -14,6 +14,7 @@ class MobileRowDetailDateCellSkin extends IEditableDateCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, DateCellBloc bloc, DateCellState state, PopoverController popoverController, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_number_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_number_cell.dart index 6e32fbbbdc..430044fb5c 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_number_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_number_cell.dart @@ -1,6 +1,6 @@ import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:appflowy/plugins/database/application/cell/bloc/number_cell_bloc.dart'; +import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -11,6 +11,7 @@ class MobileRowDetailNumberCellSkin extends IEditableNumberCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, NumberCellBloc bloc, FocusNode focusNode, TextEditingController textEditingController, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_relation_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_relation_cell.dart index 61a39a867a..c3e8b82867 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_relation_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_relation_cell.dart @@ -1,7 +1,7 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart'; +import 'package:appflowy/plugins/database/application/cell/bloc/relation_cell_bloc.dart'; import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/relation.dart'; import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; -import 'package:appflowy/plugins/database/application/cell/bloc/relation_cell_bloc.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; @@ -10,6 +10,7 @@ class MobileRowDetailRelationCellSkin extends IEditableRelationCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, RelationCellBloc bloc, RelationCellState state, PopoverController popoverController, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_select_cell_option.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_select_cell_option.dart index 9dafb6afe0..7d4eb71f9d 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_select_cell_option.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_select_cell_option.dart @@ -1,9 +1,9 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart'; -import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; +import 'package:appflowy/plugins/database/application/cell/bloc/select_option_cell_bloc.dart'; import 'package:appflowy/plugins/database/widgets/cell_editor/extension.dart'; import 'package:appflowy/plugins/database/widgets/cell_editor/mobile_select_option_editor.dart'; -import 'package:appflowy/plugins/database/application/cell/bloc/select_option_cell_bloc.dart'; +import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/select_option_entities.pb.dart'; import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -19,6 +19,7 @@ class MobileRowDetailSelectOptionCellSkin Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, SelectOptionCellBloc bloc, PopoverController popoverController, ) { diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_summary_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_summary_cell.dart index 1e709bdeb9..9974220b96 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_summary_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_summary_cell.dart @@ -9,6 +9,7 @@ class MobileRowDetailSummaryCellSkin extends IEditableSummaryCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, SummaryCellBloc bloc, FocusNode focusNode, TextEditingController textEditingController, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_text_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_text_cell.dart index 1cdde84c27..fc8f816103 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_text_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_text_cell.dart @@ -1,6 +1,6 @@ import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:appflowy/plugins/database/application/cell/bloc/text_cell_bloc.dart'; +import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -11,6 +11,7 @@ class MobileRowDetailTextCellSkin extends IEditableTextCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, TextCellBloc bloc, FocusNode focusNode, TextEditingController textEditingController, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_timestamp_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_timestamp_cell.dart index 7ddda492c9..f3f800e994 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_timestamp_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_timestamp_cell.dart @@ -1,6 +1,6 @@ import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:appflowy/plugins/database/application/cell/bloc/timestamp_cell_bloc.dart'; +import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; @@ -12,6 +12,7 @@ class MobileRowDetailTimestampCellSkin extends IEditableTimestampCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, TimestampCellBloc bloc, TimestampCellState state, ) { diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_translate_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_translate_cell.dart index a1e4b4bf29..c2d84b3d2e 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_translate_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_translate_cell.dart @@ -9,6 +9,7 @@ class MobileRowDetailTranslateCellSkin extends IEditableTranslateCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, TranslateCellBloc bloc, FocusNode focusNode, TextEditingController textEditingController, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_url_cell.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_url_cell.dart index f87b225492..9bb91255aa 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_url_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/mobile_row_detail/mobile_row_detail_url_cell.dart @@ -4,9 +4,9 @@ import 'package:appflowy/plugins/database/application/cell/bloc/url_cell_bloc.da import 'package:appflowy/plugins/database/widgets/row/accessory/cell_accessory.dart'; import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/theme_extension.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flowy_infra/theme_extension.dart'; import '../editable_cell_skeleton/url.dart'; @@ -15,6 +15,7 @@ class MobileRowDetailURLCellSkin extends IEditableURLCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, URLCellBloc bloc, FocusNode focusNode, TextEditingController textEditingController, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/checklist_cell_editor.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/checklist_cell_editor.dart index b788d6bd38..9853f9c1bd 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/checklist_cell_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/checklist_cell_editor.dart @@ -14,6 +14,7 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:universal_platform/universal_platform.dart'; import '../../application/cell/bloc/checklist_cell_bloc.dart'; import 'checklist_cell_textfield.dart'; @@ -125,19 +126,16 @@ class ChecklistItemList extends StatelessWidget { shrinkWrap: true, proxyDecorator: (child, index, _) => Material( color: Colors.transparent, - child: Stack( - children: [ - BlocProvider.value( + child: MouseRegion( + cursor: UniversalPlatform.isWindows + ? SystemMouseCursors.click + : SystemMouseCursors.grabbing, + child: IgnorePointer( + child: BlocProvider.value( value: context.read(), child: child, ), - MouseRegion( - cursor: Platform.isWindows - ? SystemMouseCursors.click - : SystemMouseCursors.grabbing, - child: const SizedBox.expand(), - ), - ], + ), ), ), buildDefaultDragHandles: false, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/checklist_progress_bar.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/checklist_progress_bar.dart index dd831282cd..7e0b376f77 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/checklist_progress_bar.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/checklist_progress_bar.dart @@ -32,40 +32,38 @@ class _ChecklistProgressBarState extends State { return Row( children: [ Expanded( - child: Row( - children: [ - if (widget.tasks.isNotEmpty && - widget.tasks.length <= widget.segmentLimit) - ...List.generate( - widget.tasks.length, - (index) => Flexible( - child: Container( - decoration: BoxDecoration( - borderRadius: - const BorderRadius.all(Radius.circular(2)), - color: index < numFinishedTasks - ? completedTaskColor - : AFThemeExtension.of(context).progressBarBGColor, + child: widget.tasks.isNotEmpty && + widget.tasks.length <= widget.segmentLimit + ? Row( + children: [ + ...List.generate( + widget.tasks.length, + (index) => Flexible( + child: Container( + decoration: BoxDecoration( + borderRadius: + const BorderRadius.all(Radius.circular(2)), + color: index < numFinishedTasks + ? completedTaskColor + : AFThemeExtension.of(context) + .progressBarBGColor, + ), + margin: const EdgeInsets.symmetric(horizontal: 1), + height: 4.0, + ), ), - margin: const EdgeInsets.symmetric(horizontal: 1), - height: 4.0, ), - ), + ], ) - else - Expanded( - child: LinearPercentIndicator( - lineHeight: 4.0, - percent: widget.percent, - padding: EdgeInsets.zero, - progressColor: completedTaskColor, - backgroundColor: - AFThemeExtension.of(context).progressBarBGColor, - barRadius: const Radius.circular(2), - ), + : LinearPercentIndicator( + lineHeight: 4.0, + percent: widget.percent, + padding: EdgeInsets.zero, + progressColor: completedTaskColor, + backgroundColor: + AFThemeExtension.of(context).progressBarBGColor, + barRadius: const Radius.circular(2), ), - ], - ), ), SizedBox( width: 45, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/mobile_media_cell_editor.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/mobile_media_cell_editor.dart index 90e4916fa8..2960a6a34d 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/mobile_media_cell_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/mobile_media_cell_editor.dart @@ -11,6 +11,7 @@ import 'package:appflowy/plugins/database/widgets/media_file_type_ext.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/file/file_util.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/image/common.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/image_render.dart'; +import 'package:appflowy/shared/loading.dart'; import 'package:appflowy/workspace/presentation/widgets/image_viewer/interactive_image_viewer.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/file_entities.pbenum.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/media_entities.pb.dart'; @@ -21,8 +22,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; -import '../../../document/presentation/editor_plugins/openai/widgets/loading.dart'; - class MobileMediaCellEditor extends StatelessWidget { const MobileMediaCellEditor({super.key}); diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/relation_cell_editor.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/relation_cell_editor.dart index 3a739cc69c..7f6960de9d 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/relation_cell_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/relation_cell_editor.dart @@ -6,6 +6,7 @@ import 'package:appflowy/plugins/database/tab_bar/tab_bar_view.dart'; import 'package:appflowy/plugins/database/widgets/row/relation_row_detail.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart'; +import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart'; import 'package:appflowy_backend/log.dart'; @@ -112,8 +113,11 @@ class _RelationCellEditorContentState @override Widget build(BuildContext context) { - return BlocProvider.value( - value: bloc, + return MultiBlocProvider( + providers: [ + BlocProvider.value(value: bloc), + BlocProvider.value(value: context.read()), + ], child: BlocBuilder( buildWhen: (previous, current) => !listEquals(previous.filteredRows, current.filteredRows), @@ -252,7 +256,7 @@ class _CellEditorTitle extends StatelessWidget { } void _openRelatedDatbase(BuildContext context) { - FolderEventGetView(ViewIdPB(value: databaseMeta.inlineViewId)) + FolderEventGetView(ViewIdPB(value: databaseMeta.viewId)) .send() .then((result) { result.fold( @@ -316,13 +320,16 @@ class _SearchField extends StatelessWidget { FlowyOverlay.show( context: context, builder: (BuildContext overlayContext) { - return RelatedRowDetailPage( - databaseId: context - .read() - .state - .relatedDatabaseMeta! - .databaseId, - rowId: row.rowId, + return BlocProvider.value( + value: context.read(), + child: RelatedRowDetailPage( + databaseId: context + .read() + .state + .relatedDatabaseMeta! + .databaseId, + rowId: row.rowId, + ), ); }, ); @@ -391,13 +398,17 @@ class _RowListItem extends StatelessWidget { ), child: GestureDetector( onTap: () { + final userWorkspaceBloc = context.read(); if (isSelected) { FlowyOverlay.show( context: context, builder: (BuildContext overlayContext) { - return RelatedRowDetailPage( - databaseId: databaseId, - rowId: row.rowId, + return BlocProvider.value( + value: userWorkspaceBloc, + child: RelatedRowDetailPage( + databaseId: databaseId, + rowId: row.rowId, + ), ); }, ); diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/database_view_widget.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/database_view_widget.dart index c979ee0829..a218e1ed68 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/database_view_widget.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/database_view_widget.dart @@ -3,17 +3,25 @@ import 'package:appflowy/startup/plugin/plugin.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy/workspace/application/view/view_listener.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; class DatabaseViewWidget extends StatefulWidget { const DatabaseViewWidget({ super.key, required this.view, this.shrinkWrap = true, + required this.showActions, + required this.node, + this.actionBuilder, }); final ViewPB view; final bool shrinkWrap; + final BlockComponentActionBuilder? actionBuilder; + final bool showActions; + final Node node; @override State createState() => _DatabaseViewWidgetState(); @@ -50,14 +58,26 @@ class _DatabaseViewWidgetState extends State { @override Widget build(BuildContext context) { + double? horizontalPadding = 0.0; + final databasePluginWidgetBuilderSize = + Provider.of(context); + if (view.layout == ViewLayoutPB.Grid || view.layout == ViewLayoutPB.Board) { + horizontalPadding = 40.0; + } + if (databasePluginWidgetBuilderSize != null) { + horizontalPadding = databasePluginWidgetBuilderSize.horizontalPadding; + } + return ValueListenableBuilder( valueListenable: _layoutTypeChangeNotifier, builder: (_, __, ___) => viewPlugin.widgetBuilder.buildWidget( shrinkWrap: widget.shrinkWrap, context: PluginContext(), data: { - kDatabasePluginWidgetBuilderHorizontalPadding: - view.layout == ViewLayoutPB.Grid ? 40.0 : 0.0, + kDatabasePluginWidgetBuilderHorizontalPadding: horizontalPadding, + kDatabasePluginWidgetBuilderActionBuilder: widget.actionBuilder, + kDatabasePluginWidgetBuilderShowActions: widget.showActions, + kDatabasePluginWidgetBuilderNode: widget.node, }, ), ); diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/group/database_group.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/group/database_group.dart index 7a888b96ed..f1486094bf 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/group/database_group.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/group/database_group.dart @@ -57,25 +57,28 @@ class DatabaseGroupList extends StatelessWidget { final children = [ if (showHideUngroupedToggle) ...[ - SizedBox( - height: GridSize.popoverItemHeight, - child: Padding( - padding: - const EdgeInsets.symmetric(horizontal: 12, vertical: 4), - child: Row( - children: [ - Expanded( - child: FlowyText( - LocaleKeys.board_showUngrouped.tr(), - ), - ), - Toggle( - value: !state.layoutSettings.hideUngroupedColumn, - onChanged: (value) => - _updateLayoutSettings(state.layoutSettings, !value), - padding: EdgeInsets.zero, - ), - ], + Padding( + padding: const EdgeInsets.symmetric(horizontal: 6), + child: SizedBox( + height: GridSize.popoverItemHeight, + child: FlowyButton( + resetHoverOnRebuild: false, + text: FlowyText( + LocaleKeys.board_showUngrouped.tr(), + lineHeight: 1.0, + ), + onTap: () { + _updateLayoutSettings( + state.layoutSettings, + !state.layoutSettings.hideUngroupedColumn, + ); + }, + rightIcon: Toggle( + value: !state.layoutSettings.hideUngroupedColumn, + onChanged: (value) => + _updateLayoutSettings(state.layoutSettings, !value), + padding: EdgeInsets.zero, + ), ), ), ), diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/accessory/cell_accessory.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/accessory/cell_accessory.dart index 437d125f53..6e13cc5ecb 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/accessory/cell_accessory.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/accessory/cell_accessory.dart @@ -1,3 +1,4 @@ +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart'; @@ -8,7 +9,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; -import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; import 'package:styled_widget/styled_widget.dart'; import '../../cell/editable_cell_builder.dart'; @@ -188,6 +188,9 @@ class CellAccessoryContainer extends StatelessWidget { ); }).toList(); - return Wrap(spacing: 6, children: children); + return SeparatedRow( + separatorBuilder: () => const HSpace(6), + children: children, + ); } } diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/cells/cell_container.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/cells/cell_container.dart index 2e1260fe3d..333ff0fe96 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/cells/cell_container.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/cells/cell_container.dart @@ -1,6 +1,5 @@ import 'package:flowy_infra/theme_extension.dart'; import 'package:flutter/material.dart'; - import 'package:provider/provider.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -58,7 +57,7 @@ class CellContainer extends StatelessWidget { } }, child: Container( - constraints: BoxConstraints(maxWidth: width, minHeight: 36), + constraints: BoxConstraints(maxWidth: width, minHeight: 32), decoration: _makeBoxDecoration(context, isFocus), child: container, ), diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/relation_row_detail.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/relation_row_detail.dart index 256de6bc3c..1260641fdf 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/relation_row_detail.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/relation_row_detail.dart @@ -1,4 +1,5 @@ import 'package:appflowy/plugins/database/application/row/related_row_detail_bloc.dart'; +import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -22,14 +23,17 @@ class RelatedRowDetailPage extends StatelessWidget { initialRowId: rowId, ), child: BlocBuilder( - builder: (context, state) { + builder: (_, state) { return state.when( loading: () => const SizedBox.shrink(), ready: (databaseController, rowController) { - return RowDetailPage( - databaseController: databaseController, - rowController: rowController, - allowOpenAsFullPage: false, + return BlocProvider.value( + value: context.read(), + child: RowDetailPage( + databaseController: databaseController, + rowController: rowController, + allowOpenAsFullPage: false, + ), ); }, ); diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_banner.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_banner.dart index 243eb5f027..debbb467e7 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_banner.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_banner.dart @@ -1,6 +1,3 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart'; @@ -24,13 +21,15 @@ import 'package:appflowy/shared/af_image.dart'; import 'package:appflowy/shared/flowy_gradient_colors.dart'; import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; -import 'package:appflowy_backend/protobuf/flowy-user/auth.pbenum.dart'; import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-user/workspace.pb.dart'; import 'package:appflowy_editor/appflowy_editor.dart' hide UploadImageMenu; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/widget/rounded_button.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:string_validator/string_validator.dart'; @@ -70,8 +69,8 @@ class RowBanner extends StatefulWidget { class _RowBannerState extends State { final _isHovering = ValueNotifier(false); late final isLocalMode = - (widget.userProfile?.authenticator ?? AuthenticatorPB.Local) == - AuthenticatorPB.Local; + (widget.userProfile?.workspaceAuthType ?? AuthTypePB.Local) == + AuthTypePB.Local; @override void dispose() { @@ -278,8 +277,10 @@ class _RowCoverState extends State { onPressed: () => popoverController.show(), hoverColor: Theme.of(context).colorScheme.surface, textColor: Theme.of(context).colorScheme.tertiary, - fillColor: - Theme.of(context).colorScheme.surface.withOpacity(0.5), + fillColor: Theme.of(context) + .colorScheme + .surface + .withValues(alpha: 0.5), title: LocaleKeys.document_plugins_cover_changeCover.tr(), ), ), @@ -623,6 +624,7 @@ class _TitleSkin extends IEditableTextCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, TextCellBloc bloc, FocusNode focusNode, TextEditingController textEditingController, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_detail.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_detail.dart index 7b39335ec7..8bd181b427 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_detail.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_detail.dart @@ -1,7 +1,3 @@ -import 'package:appflowy/plugins/document/presentation/editor_drop_manager.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database/application/database_controller.dart'; @@ -10,6 +6,7 @@ import 'package:appflowy/plugins/database/domain/database_view_service.dart'; import 'package:appflowy/plugins/database/grid/application/row/row_detail_bloc.dart'; import 'package:appflowy/plugins/database/widgets/row/row_document.dart'; import 'package:appflowy/plugins/database_document/database_document_plugin.dart'; +import 'package:appflowy/plugins/document/presentation/editor_drop_manager.dart'; import 'package:appflowy/startup/plugin/plugin.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/user/application/reminder/reminder_bloc.dart'; @@ -17,11 +14,12 @@ import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart'; import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:provider/provider.dart'; import '../cell/editable_cell_builder.dart'; - import 'row_banner.dart'; import 'row_property.dart'; @@ -87,39 +85,51 @@ class _RowDetailPageState extends State { ], child: BlocBuilder( builder: (context, state) => Stack( + fit: StackFit.expand, children: [ - ListView( - controller: scrollController, - physics: const ClampingScrollPhysics(), - children: [ - RowBanner( - databaseController: widget.databaseController, - rowController: widget.rowController, - cellBuilder: cellBuilder, - allowOpenAsFullPage: widget.allowOpenAsFullPage, - userProfile: widget.userProfile, - ), - const VSpace(16), - Padding( - padding: const EdgeInsets.only(left: 40, right: 60), - child: RowPropertyList( - cellBuilder: cellBuilder, - viewId: widget.databaseController.viewId, - fieldController: - widget.databaseController.fieldController, - ), - ), - const VSpace(20), - const Padding( - padding: EdgeInsets.symmetric(horizontal: 60), - child: Divider(height: 1.0), - ), - const VSpace(20), - RowDocument( + Positioned.fill( + child: NestedScrollView( + controller: scrollController, + headerSliverBuilder: + (BuildContext context, bool innerBoxIsScrolled) { + return [ + SliverToBoxAdapter( + child: Column( + children: [ + RowBanner( + databaseController: widget.databaseController, + rowController: widget.rowController, + cellBuilder: cellBuilder, + allowOpenAsFullPage: widget.allowOpenAsFullPage, + userProfile: widget.userProfile, + ), + const VSpace(16), + Padding( + padding: + const EdgeInsets.only(left: 40, right: 60), + child: RowPropertyList( + cellBuilder: cellBuilder, + viewId: widget.databaseController.viewId, + fieldController: + widget.databaseController.fieldController, + ), + ), + const VSpace(20), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 60), + child: Divider(height: 1.0), + ), + const VSpace(20), + ], + ), + ), + ]; + }, + body: RowDocument( viewId: widget.rowController.viewId, rowId: widget.rowController.rowId, ), - ], + ), ), Positioned( top: calculateActionsOffset( diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_document.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_document.dart index e3d30249ac..436dbd085d 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_document.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_document.dart @@ -1,13 +1,16 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database/grid/application/row/row_document_bloc.dart'; +import 'package:appflowy/plugins/database/tab_bar/tab_bar_view.dart'; import 'package:appflowy/plugins/document/application/document_bloc.dart'; import 'package:appflowy/plugins/document/presentation/editor_drop_handler.dart'; import 'package:appflowy/plugins/document/presentation/editor_drop_manager.dart'; import 'package:appflowy/plugins/document/presentation/editor_page.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/ai/widgets/ai_writer_scroll_wrapper.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/shared_context/shared_context.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/transaction_handler/editor_transaction_service.dart'; import 'package:appflowy/plugins/document/presentation/editor_style.dart'; import 'package:appflowy/shared/flowy_error_page.dart'; +import 'package:appflowy/workspace/application/view/view_bloc.dart'; import 'package:appflowy/workspace/application/view_info/view_info_bloc.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; @@ -70,9 +73,16 @@ class _RowEditor extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocProvider( - create: (_) => - DocumentBloc(documentId: view.id)..add(const DocumentEvent.initial()), + return MultiBlocProvider( + providers: [ + BlocProvider( + create: (_) => DocumentBloc(documentId: view.id) + ..add(const DocumentEvent.initial()), + ), + BlocProvider( + create: (_) => ViewBloc(view: view)..add(const ViewEvent.initial()), + ), + ], child: BlocConsumer( listenWhen: (previous, current) => previous.isDocumentEmpty != current.isDocumentEmpty, @@ -102,16 +112,18 @@ class _RowEditor extends StatelessWidget { return BlocProvider( create: (context) => ViewInfoBloc(view: view), - child: IntrinsicHeight( - child: Container( - constraints: const BoxConstraints(minHeight: 300), - child: Provider( - create: (_) { - final context = SharedEditorContext(); - context.isInDatabaseRowPage = true; - return context; - }, - dispose: (_, editorContext) => editorContext.dispose(), + child: Container( + constraints: const BoxConstraints(minHeight: 300), + child: Provider( + create: (_) { + final context = SharedEditorContext(); + context.isInDatabaseRowPage = true; + return context; + }, + dispose: (_, editorContext) => editorContext.dispose(), + child: AiWriterScrollWrapper( + viewId: view.id, + editorState: editorState, child: EditorDropHandler( viewId: view.id, editorState: editorState, @@ -120,18 +132,23 @@ class _RowEditor extends StatelessWidget { child: EditorTransactionService( viewId: view.id, editorState: editorState, - child: AppFlowyEditorPage( - shrinkWrap: true, - autoFocus: false, - editorState: editorState, - styleCustomizer: EditorStyleCustomizer( - context: context, - padding: const EdgeInsets.only(left: 16, right: 54), + child: Provider( + create: (context) => DatabasePluginWidgetBuilderSize( + horizontalPadding: 0, + ), + child: AppFlowyEditorPage( + shrinkWrap: true, + autoFocus: false, + editorState: editorState, + styleCustomizer: EditorStyleCustomizer( + context: context, + padding: const EdgeInsets.only(left: 16, right: 54), + ), + showParagraphPlaceholder: (editorState, _) => + editorState.document.isEmpty, + placeholderText: (_) => + LocaleKeys.cardDetails_notesPlaceholder.tr(), ), - showParagraphPlaceholder: (editorState, _) => - editorState.document.isEmpty, - placeholderText: (_) => - LocaleKeys.cardDetails_notesPlaceholder.tr(), ), ), ), diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/database_layout_selector.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/database_layout_selector.dart index c8c23365de..b4ee4134c9 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/database_layout_selector.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/database_layout_selector.dart @@ -1,35 +1,33 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/database/application/database_controller.dart'; import 'package:appflowy/plugins/database/application/layout/layout_bloc.dart'; +import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; import 'package:appflowy/plugins/database/widgets/database_layout_ext.dart'; +import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pb.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import '../../grid/presentation/layout/sizes.dart'; - -class DatabaseLayoutSelector extends StatefulWidget { +class DatabaseLayoutSelector extends StatelessWidget { const DatabaseLayoutSelector({ super.key, required this.viewId, - required this.currentLayout, + required this.databaseController, }); final String viewId; - final DatabaseLayoutPB currentLayout; + final DatabaseController databaseController; - @override - State createState() => _DatabaseLayoutSelectorState(); -} - -class _DatabaseLayoutSelectorState extends State { @override Widget build(BuildContext context) { return BlocProvider( create: (context) => DatabaseLayoutBloc( - viewId: widget.viewId, - databaseLayout: widget.currentLayout, + viewId: viewId, + databaseLayout: databaseController.databaseLayout, )..add(const DatabaseLayoutEvent.initial()), child: BlocBuilder( builder: (context, state) { @@ -44,14 +42,57 @@ class _DatabaseLayoutSelectorState extends State { ), ) .toList(); - - return ListView.separated( - shrinkWrap: true, - itemCount: cells.length, + return Padding( padding: const EdgeInsets.symmetric(vertical: 6.0), - itemBuilder: (_, int index) => cells[index], - separatorBuilder: (_, __) => - VSpace(GridSize.typeOptionSeparatorHeight), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + ListView.separated( + shrinkWrap: true, + itemCount: cells.length, + padding: EdgeInsets.zero, + itemBuilder: (_, int index) => cells[index], + separatorBuilder: (_, __) => + VSpace(GridSize.typeOptionSeparatorHeight), + ), + Container( + height: 1, + margin: EdgeInsets.fromLTRB(8, 4, 8, 0), + color: AFThemeExtension.of(context).borderColor, + ), + Padding( + padding: const EdgeInsets.fromLTRB(8, 4, 8, 2), + child: SizedBox( + height: 30, + child: FlowyButton( + resetHoverOnRebuild: false, + text: FlowyText( + LocaleKeys.grid_settings_compactMode.tr(), + lineHeight: 1.0, + ), + onTap: () { + databaseController.setCompactMode( + !databaseController.compactModeNotifier.value, + ); + }, + rightIcon: ValueListenableBuilder( + valueListenable: databaseController.compactModeNotifier, + builder: (context, compactMode, child) { + return Toggle( + value: compactMode, + duration: Duration.zero, + onChanged: (value) => + databaseController.setCompactMode(value), + padding: EdgeInsets.zero, + ); + }, + ), + ), + ), + ), + ], + ), ); }, ), @@ -76,7 +117,7 @@ class DatabaseViewLayoutCell extends StatelessWidget { return Padding( padding: const EdgeInsets.symmetric(horizontal: 6), child: SizedBox( - height: GridSize.popoverItemHeight, + height: 30, child: FlowyButton( hoverColor: AFThemeExtension.of(context).lightGreyHover, text: FlowyText( diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/database_setting_action.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/database_setting_action.dart index 409454848a..c7bc286371 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/database_setting_action.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/database_setting_action.dart @@ -3,8 +3,8 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database/application/database_controller.dart'; import 'package:appflowy/plugins/database/calendar/presentation/toolbar/calendar_layout_setting.dart'; import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; -import 'package:appflowy/plugins/database/widgets/setting/database_layout_selector.dart'; import 'package:appflowy/plugins/database/widgets/group/database_group.dart'; +import 'package:appflowy/plugins/database/widgets/setting/database_layout_selector.dart'; import 'package:appflowy/plugins/database/widgets/setting/setting_property_list.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; @@ -24,11 +24,11 @@ extension DatabaseSettingActionExtension on DatabaseSettingAction { case DatabaseSettingAction.showProperties: return FlowySvgs.multiselect_s; case DatabaseSettingAction.showLayout: - return FlowySvgs.database_layout_m; + return FlowySvgs.database_layout_s; case DatabaseSettingAction.showGroup: return FlowySvgs.group_s; case DatabaseSettingAction.showCalendarLayout: - return FlowySvgs.calendar_layout_m; + return FlowySvgs.calendar_layout_s; } } @@ -53,7 +53,7 @@ extension DatabaseSettingActionExtension on DatabaseSettingAction { final popover = switch (this) { DatabaseSettingAction.showLayout => DatabaseLayoutSelector( viewId: databaseController.viewId, - currentLayout: databaseController.databaseLayout, + databaseController: databaseController, ), DatabaseSettingAction.showGroup => DatabaseGroupList( viewId: databaseController.viewId, @@ -88,6 +88,7 @@ extension DatabaseSettingActionExtension on DatabaseSettingAction { iconData(), color: Theme.of(context).iconTheme.color, ), + rightIcon: FlowySvg(FlowySvgs.database_settings_arrow_right_s), ), ), popupBuilder: (context) => popover, diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/setting_button.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/setting_button.dart index 4d31e5a79d..36a6436b2a 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/setting_button.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/setting/setting_button.dart @@ -1,13 +1,11 @@ -import 'package:flutter/material.dart'; - +import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database/application/database_controller.dart'; -import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; import 'package:appflowy/plugins/database/widgets/setting/database_settings_list.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; class SettingButton extends StatefulWidget { const SettingButton({super.key, required this.databaseController}); @@ -29,15 +27,17 @@ class _SettingButtonState extends State { direction: PopoverDirection.bottomWithCenterAligned, offset: const Offset(0, 8), triggerActions: PopoverTriggerFlags.none, - child: FlowyTextButton( - LocaleKeys.settings_title.tr(), - fontColor: Theme.of(context).hintColor, - fontSize: FontSizes.s12, - fillColor: Colors.transparent, - hoverColor: AFThemeExtension.of(context).lightGreyHover, - padding: GridSize.toolbarSettingButtonInsets, - radius: Corners.s4Border, - onPressed: _popoverController.show, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: FlowyIconButton( + tooltipText: LocaleKeys.settings_title.tr(), + width: 24, + height: 24, + iconPadding: const EdgeInsets.all(3), + hoverColor: AFThemeExtension.of(context).lightGreyHover, + icon: const FlowySvg(FlowySvgs.settings_s), + onPressed: _popoverController.show, + ), ), popupBuilder: (_) => DatabaseSettingsList(databaseController: widget.databaseController), diff --git a/frontend/appflowy_flutter/lib/plugins/database_document/database_document_page.dart b/frontend/appflowy_flutter/lib/plugins/database_document/database_document_page.dart index eaa82b22e9..ee52be8c26 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_document/database_document_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_document/database_document_page.dart @@ -1,3 +1,4 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database/application/row/related_row_detail_bloc.dart'; import 'package:appflowy/plugins/database/grid/application/row/row_detail_bloc.dart'; import 'package:appflowy/plugins/database/grid/presentation/widgets/common/type_option_separator.dart'; @@ -7,8 +8,10 @@ import 'package:appflowy/plugins/database/widgets/row/row_property.dart'; import 'package:appflowy/plugins/document/application/document_bloc.dart'; import 'package:appflowy/plugins/document/presentation/banner.dart'; import 'package:appflowy/plugins/document/presentation/editor_drop_handler.dart'; -import 'package:appflowy/plugins/document/presentation/editor_notification.dart'; import 'package:appflowy/plugins/document/presentation/editor_page.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/ai/widgets/ai_writer_scroll_wrapper.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/shared_context/shared_context.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/transaction_handler/editor_transaction_service.dart'; import 'package:appflowy/plugins/document/presentation/editor_style.dart'; import 'package:appflowy/shared/flowy_error_page.dart'; @@ -18,8 +21,12 @@ import 'package:appflowy/workspace/application/action_navigation/navigation_acti import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:provider/provider.dart'; + +import '../../workspace/application/view/view_bloc.dart'; // This widget is largely copied from `plugins/document/document_page.dart` intentionally instead of opting for an abstraction. We can make an abstraction after the view refactor is done and there's more clarity in that department. @@ -46,18 +53,6 @@ class DatabaseDocumentPage extends StatefulWidget { class _DatabaseDocumentPageState extends State { EditorState? editorState; - @override - void initState() { - super.initState(); - EditorNotification.addListener(_onEditorNotification); - } - - @override - void dispose() { - EditorNotification.removeListener(_onEditorNotification); - super.dispose(); - } - @override Widget build(BuildContext context) { return MultiBlocProvider( @@ -72,6 +67,10 @@ class _DatabaseDocumentPageState extends State { documentId: widget.documentId, )..add(const DocumentEvent.initial()), ), + BlocProvider( + create: (_) => + ViewBloc(view: widget.view)..add(const ViewEvent.initial()), + ), ], child: BlocBuilder( builder: (context, state) { @@ -98,7 +97,11 @@ class _DatabaseDocumentPageState extends State { return BlocListener( listener: _onNotificationAction, listenWhen: (_, curr) => curr.action != null, - child: _buildEditorPage(context, state), + child: AiWriterScrollWrapper( + viewId: widget.view.id, + editorState: editorState, + child: _buildEditorPage(context, state), + ), ); }, ), @@ -115,21 +118,34 @@ class _DatabaseDocumentPageState extends State { styleCustomizer: EditorStyleCustomizer( context: context, padding: EditorStyleCustomizer.documentPadding, + editorState: state.editorState!, ), header: _buildDatabaseDataContent(context, state.editorState!), initialSelection: widget.initialSelection, useViewInfoBloc: false, + placeholderText: (node) => + node.type == ParagraphBlockKeys.type && !node.isInTable + ? LocaleKeys.editor_slashPlaceHolder.tr() + : '', ), ); - return EditorTransactionService( - viewId: widget.view.id, - editorState: state.editorState!, - child: Column( - children: [ - if (state.isDeleted) _buildBanner(context), - Expanded(child: appflowyEditorPage), - ], + return Provider( + create: (_) { + final context = SharedEditorContext(); + context.isInDatabaseRowPage = true; + return context; + }, + dispose: (_, editorContext) => editorContext.dispose(), + child: EditorTransactionService( + viewId: widget.view.id, + editorState: state.editorState!, + child: Column( + children: [ + if (state.isDeleted) _buildBanner(context), + Expanded(child: appflowyEditorPage), + ], + ), ), ); } @@ -202,20 +218,6 @@ class _DatabaseDocumentPageState extends State { ); } - void _onEditorNotification(EditorNotificationType type) { - final editorState = this.editorState; - if (editorState == null) { - return; - } - if (type == EditorNotificationType.undo) { - undoCommand.execute(editorState); - } else if (type == EditorNotificationType.redo) { - redoCommand.execute(editorState); - } else if (type == EditorNotificationType.exitEditing) { - editorState.selection = null; - } - } - void _onNotificationAction( BuildContext context, ActionNavigationState state, diff --git a/frontend/appflowy_flutter/lib/plugins/database_document/database_document_plugin.dart b/frontend/appflowy_flutter/lib/plugins/database_document/database_document_plugin.dart index 07c2a4b5dc..fd238271b7 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_document/database_document_plugin.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_document/database_document_plugin.dart @@ -1,4 +1,4 @@ -library document_plugin; +library; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/database_document/presentation/database_document_title.dart b/frontend/appflowy_flutter/lib/plugins/database_document/presentation/database_document_title.dart index 04b8a30905..7f4493a999 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_document/presentation/database_document_title.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_document/presentation/database_document_title.dart @@ -7,6 +7,7 @@ import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/te import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/workspace/presentation/widgets/view_title_bar.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:collection/collection.dart'; @@ -140,6 +141,7 @@ class _TitleSkin extends IEditableTextCellSkin { Widget build( BuildContext context, CellContainerNotifier cellContainerNotifier, + ValueNotifier compactModeNotifier, TextCellBloc bloc, FocusNode focusNode, TextEditingController textEditingController, @@ -173,11 +175,13 @@ class _TitleSkin extends IEditableTextCellSkin { }, onUpdateName: (text) => bloc.add(TextCellEvent.updateText(text)), + tabs: const [PickerTabType.emoji], ); }, child: FlowyButton( useIntrinsicWidth: true, onTap: () {}, + margin: const EdgeInsets.symmetric(horizontal: 6), text: Row( children: [ if (state.icon != null) ...[ @@ -212,6 +216,7 @@ class RenameRowPopover extends StatefulWidget { required this.onUpdateName, required this.onUpdateIcon, required this.icon, + this.tabs = const [PickerTabType.emoji, PickerTabType.icon], }); final TextEditingController textController; @@ -219,6 +224,7 @@ class RenameRowPopover extends StatefulWidget { final ValueChanged onUpdateName; final ValueChanged onUpdateIcon; + final List tabs; @override State createState() => _RenameRowPopoverState(); @@ -244,10 +250,11 @@ class _RenameRowPopoverState extends State { direction: PopoverDirection.bottomWithCenterAligned, offset: const Offset(0, 18), defaultIcon: const FlowySvg(FlowySvgs.document_s), - onSubmitted: (emoji, _) { - widget.onUpdateIcon(emoji); - PopoverContainer.of(context).close(); + onSubmitted: (r, _) { + widget.onUpdateIcon(r.data); + if (!r.keepOpen) PopoverContainer.of(context).close(); }, + tabs: widget.tabs, ), const HSpace(6), SizedBox( diff --git a/frontend/appflowy_flutter/lib/plugins/document/application/document_bloc.dart b/frontend/appflowy_flutter/lib/plugins/document/application/document_bloc.dart index f34c9e0d6d..264ec4bb11 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/application/document_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/application/document_bloc.dart @@ -6,11 +6,13 @@ import 'package:appflowy/plugins/document/application/document_awareness_metadat import 'package:appflowy/plugins/document/application/document_collab_adapter.dart'; import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart'; import 'package:appflowy/plugins/document/application/document_listener.dart'; +import 'package:appflowy/plugins/document/application/document_rules.dart'; import 'package:appflowy/plugins/document/application/document_service.dart'; import 'package:appflowy/plugins/document/application/editor_transaction_adapter.dart'; import 'package:appflowy/plugins/trash/application/trash_service.dart'; import 'package:appflowy/shared/feature_flags.dart'; import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/startup/tasks/app_widget.dart'; import 'package:appflowy/startup/tasks/device_info_task.dart'; import 'package:appflowy/user/application/auth/auth_service.dart'; import 'package:appflowy/util/color_generator/color_generator.dart'; @@ -18,19 +20,14 @@ import 'package:appflowy/util/color_to_hex_string.dart'; import 'package:appflowy/util/debounce.dart'; import 'package:appflowy/util/throttle.dart'; import 'package:appflowy/workspace/application/view/view_listener.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-document/entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-document/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; import 'package:appflowy_editor/appflowy_editor.dart' - show - EditorState, - AppFlowyEditorLogLevel, - TransactionTime, - Selection, - Position, - paragraphNode; + show AppFlowyEditorLogLevel, EditorState, TransactionTime; import 'package:appflowy_result/appflowy_result.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -38,6 +35,11 @@ import 'package:freezed_annotation/freezed_annotation.dart'; part 'document_bloc.freezed.dart'; +/// Enable this flag to enable the internal log for +/// - document diff +/// - document integrity check +/// - document sync state +/// - document awareness states bool enableDocumentInternalLog = false; final Map _documentBlocMap = {}; @@ -83,6 +85,8 @@ class DocumentBloc extends Bloc { documentService: _documentService, ); + late final DocumentRules _documentRules; + StreamSubscription? _transactionSubscription; bool isClosing = false; @@ -97,8 +101,8 @@ class DocumentBloc extends Bloc { bool get isLocalMode { final userProfilePB = state.userProfilePB; - final type = userProfilePB?.authenticator ?? AuthenticatorPB.Local; - return type == AuthenticatorPB.Local; + final type = userProfilePB?.workspaceAuthType ?? AuthTypePB.Local; + return type == AuthTypePB.Local; } @override @@ -255,24 +259,27 @@ class DocumentBloc extends Bloc { final editorState = EditorState(document: document); _documentCollabAdapter = DocumentCollabAdapter(editorState, documentId); + _documentRules = DocumentRules(editorState: editorState); // subscribe to the document change from the editor _transactionSubscription = editorState.transactionStream.listen( - (event) async { - final time = event.$1; - final transaction = event.$2; - final options = event.$3; + (value) async { + final time = value.$1; + final transaction = value.$2; + final options = value.$3; if (time != TransactionTime.before) { return; } if (options.inMemoryUpdate) { - Log.info('skip transaction for in-memory update'); + if (enableDocumentInternalLog) { + Log.trace('skip transaction for in-memory update'); + } return; } if (enableDocumentInternalLog) { - Log.debug( + Log.trace( '[TransactionAdapter] 1. transaction before apply: ${transaction.hashCode}', ); } @@ -281,10 +288,10 @@ class DocumentBloc extends Bloc { await _transactionAdapter.apply(transaction, editorState); // check if the document is empty. - await _applyRules(); + await _documentRules.applyRules(value: value); if (enableDocumentInternalLog) { - Log.debug( + Log.trace( '[TransactionAdapter] 4. transaction after apply: ${transaction.hashCode}', ); } @@ -304,7 +311,7 @@ class DocumentBloc extends Bloc { ..level = AppFlowyEditorLogLevel.all ..handler = (log) { if (enableDocumentInternalLog) { - Log.info(log); + // Log.info(log); } }; } @@ -312,28 +319,6 @@ class DocumentBloc extends Bloc { return editorState; } - Future _applyRules() async { - await Future.wait([ - _ensureAtLeastOneParagraphExists(), - ]); - } - - Future _ensureAtLeastOneParagraphExists() async { - final editorState = state.editorState; - if (editorState == null) { - return; - } - final document = editorState.document; - if (document.root.children.isEmpty) { - final transaction = editorState.transaction; - transaction.insertNode([0], paragraphNode()); - transaction.afterSelection = Selection.collapsed( - Position(path: [0]), - ); - await editorState.apply(transaction); - } - } - Future _onDocumentStateUpdate(DocEventPB docEvent) async { if (!docEvent.isRemote || !FeatureFlag.syncDocument.isOn) { return; @@ -363,6 +348,9 @@ class DocumentBloc extends Bloc { } void _throttleSyncDoc(DocEventPB docEvent) { + if (enableDocumentInternalLog) { + Log.info('[DocumentBloc] throttle sync doc: ${docEvent.toProto3Json()}'); + } _syncThrottle.call(() { _onDocumentStateUpdate(docEvent); }); @@ -389,7 +377,7 @@ class DocumentBloc extends Bloc { final basicColor = ColorGenerator(id.toString()).toColor(); final metadata = DocumentAwarenessMetadata( cursorColor: basicColor.toHexString(), - selectionColor: basicColor.withOpacity(0.6).toHexString(), + selectionColor: basicColor.withValues(alpha: 0.6).toHexString(), userName: user.name, userAvatar: user.iconUrl, ); @@ -412,7 +400,7 @@ class DocumentBloc extends Bloc { final basicColor = ColorGenerator(id.toString()).toColor(); final metadata = DocumentAwarenessMetadata( cursorColor: basicColor.toHexString(), - selectionColor: basicColor.withOpacity(0.6).toHexString(), + selectionColor: basicColor.withValues(alpha: 0.6).toHexString(), userName: user.name, userAvatar: user.iconUrl, ); @@ -448,9 +436,16 @@ class DocumentBloc extends Bloc { if (!deepEqual) { Log.error('document integrity check failed'); // Enable it to debug the document integrity check failed - // Log.error('cloud doc: $cloudJson'); - // Log.error('local doc: $localJson'); - assert(false, 'document integrity check failed'); + Log.error('cloud doc: $cloudJson'); + Log.error('local doc: $localJson'); + + final context = AppGlobals.rootNavKey.currentContext; + if (context != null && context.mounted) { + showToastNotification( + message: 'document integrity check failed', + type: ToastificationType.error, + ); + } } } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/application/document_collab_adapter.dart b/frontend/appflowy_flutter/lib/plugins/document/application/document_collab_adapter.dart index a3e62d569c..f550093b54 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/application/document_collab_adapter.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/application/document_collab_adapter.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:appflowy/plugins/document/application/document_awareness_metadata.dart'; import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart'; +import 'package:appflowy/plugins/document/application/document_diff.dart'; import 'package:appflowy/plugins/document/application/prelude.dart'; import 'package:appflowy/shared/list_extension.dart'; import 'package:appflowy/startup/tasks/device_info_task.dart'; @@ -16,10 +17,14 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; class DocumentCollabAdapter { - DocumentCollabAdapter(this.editorState, this.docId); + DocumentCollabAdapter( + this.editorState, + this.docId, + ); final EditorState editorState; final String docId; + final DocumentDiff diff = const DocumentDiff(); final _service = DocumentService(); @@ -75,13 +80,14 @@ class DocumentCollabAdapter { return; } - final ops = diffNodes(editorState.document.root, document.root); + final ops = diff.diffDocument(editorState.document, document); if (ops.isEmpty) { return; } - // Use for debugging, DO NOT REMOVE - // prettyPrintJson(ops.map((op) => op.toJson()).toList()); + if (enableDocumentInternalLog) { + prettyPrintJson(ops.map((op) => op.toJson()).toList()); + } final transaction = editorState.transaction; for (final op in ops) { @@ -89,18 +95,19 @@ class DocumentCollabAdapter { } await editorState.apply(transaction, isRemote: true); - // Use for debugging, DO NOT REMOVE - // assert(() { - // final local = editorState.document.root.toJson(); - // final remote = document.root.toJson(); - // if (!const DeepCollectionEquality().equals(local, remote)) { - // Log.error('Invalid diff status'); - // Log.error('Local: $local'); - // Log.error('Remote: $remote'); - // return false; - // } - // return true; - // }()); + if (enableDocumentInternalLog) { + assert(() { + final local = editorState.document.root.toJson(); + final remote = document.root.toJson(); + if (!const DeepCollectionEquality().equals(local, remote)) { + Log.error('Invalid diff status'); + Log.error('Local: $local'); + Log.error('Remote: $remote'); + return false; + } + return true; + }()); + } } Future forceReload() async { diff --git a/frontend/appflowy_flutter/lib/plugins/document/application/document_collaborators_bloc.dart b/frontend/appflowy_flutter/lib/plugins/document/application/document_collaborators_bloc.dart index b6352b0430..a0678372cf 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/application/document_collaborators_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/application/document_collaborators_bloc.dart @@ -32,7 +32,7 @@ class DocumentCollaboratorsBloc emit( state.copyWith( shouldShowIndicator: - userProfile?.authenticator == AuthenticatorPB.AppFlowyCloud, + userProfile?.workspaceAuthType == AuthTypePB.Server, ), ); final deviceId = ApplicationInfo.deviceId; @@ -85,7 +85,11 @@ class DocumentCollaboratorsBloc final ids = {}; final sorted = states.value.values.toList() ..sort((a, b) => b.timestamp.compareTo(a.timestamp)) - ..retainWhere((e) => ids.add(e.user.uid.toString() + e.user.deviceId)); + // filter the duplicate users + ..retainWhere((e) => ids.add(e.user.uid.toString() + e.user.deviceId)) + // only keep version 1 and metadata is not empty + ..retainWhere((e) => e.version == 1) + ..retainWhere((e) => e.metadata.isNotEmpty); for (final state in sorted) { if (state.version != 1) { continue; diff --git a/frontend/appflowy_flutter/lib/plugins/document/application/document_data_pb_extension.dart b/frontend/appflowy_flutter/lib/plugins/document/application/document_data_pb_extension.dart index f9a80864f1..38bf2bcd14 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/application/document_data_pb_extension.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/application/document_data_pb_extension.dart @@ -13,12 +13,10 @@ import 'package:appflowy_editor/appflowy_editor.dart' NodeIterator, NodeExternalValues, HeadingBlockKeys, - QuoteBlockKeys, NumberedListBlockKeys, BulletedListBlockKeys, blockComponentDelta; import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; -import 'package:collection/collection.dart'; import 'package:nanoid/nanoid.dart'; class ExternalValues extends NodeExternalValues { @@ -105,7 +103,7 @@ extension DocumentDataPBFromTo on DocumentDataPB { final children = []; if (childrenIds != null && childrenIds.isNotEmpty) { - children.addAll(childrenIds.map((e) => buildNode(e)).whereNotNull()); + children.addAll(childrenIds.map((e) => buildNode(e)).nonNulls); } final node = block?.toNode( diff --git a/frontend/appflowy_flutter/lib/plugins/document/application/document_diff.dart b/frontend/appflowy_flutter/lib/plugins/document/application/document_diff.dart new file mode 100644 index 0000000000..e174d6671e --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/application/document_diff.dart @@ -0,0 +1,172 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; + +/// DocumentDiff compares two document states and generates operations needed +/// to transform one state into another. +class DocumentDiff { + const DocumentDiff({ + this.enableDebugLog = false, + }); + + final bool enableDebugLog; + + // Using DeepCollectionEquality for deep comparison of collections + static const _equality = DeepCollectionEquality(); + + /// Generates operations needed to transform oldDocument into newDocument. + /// Returns a list of operations (Insert, Delete, Update) that can be applied sequentially. + List diffDocument(Document oldDocument, Document newDocument) { + return diffNode(oldDocument.root, newDocument.root); + } + + /// Compares two nodes and their children recursively to generate transformation operations. + /// Returns a list of operations that will transform oldNode into newNode. + List diffNode(Node oldNode, Node newNode) { + final operations = []; + + // Compare and update node attributes if they're different. + // Using DeepCollectionEquality instead of == for deep comparison of collections + if (!_equality.equals(oldNode.attributes, newNode.attributes)) { + operations.add( + UpdateOperation(oldNode.path, newNode.attributes, oldNode.attributes), + ); + } + + final oldChildrenById = Map.fromEntries( + oldNode.children.map((child) => MapEntry(child.id, child)), + ); + final newChildrenById = Map.fromEntries( + newNode.children.map((child) => MapEntry(child.id, child)), + ); + + // Insertion or Update + for (final newChild in newNode.children) { + final oldChild = oldChildrenById[newChild.id]; + if (oldChild == null) { + // If the node doesn't exist in the old document, it's a new node. + operations.add(InsertOperation(newChild.path, [newChild])); + } else { + // If the node exists in the old document, recursively compare its children + operations.addAll(diffNode(oldChild, newChild)); + } + } + + // Deletion + for (final id in oldChildrenById.keys) { + // If the node doesn't exist in the new document, it's a deletion. + if (!newChildrenById.containsKey(id)) { + final oldChild = oldChildrenById[id]!; + operations.add(DeleteOperation(oldChild.path, [oldChild])); + } + } + + // Optimize operations by merging consecutive inserts and deletes + return _optimizeOperations(operations); + } + + /// Optimizes the list of operations by merging consecutive operations where possible. + /// This reduces the total number of operations that need to be applied. + List _optimizeOperations(List operations) { + // Optimize the insert operations first, then the delete operations + final optimizedOps = mergeDeleteOperations( + mergeInsertOperations( + operations, + ), + ); + return optimizedOps; + } + + /// Merges consecutive insert operations to reduce the number of operations. + /// Operations are merged if they target consecutive paths in the document. + List mergeInsertOperations(List operations) { + if (enableDebugLog) { + _logOperations('mergeInsertOperations[before]', operations); + } + + final copy = [...operations]; + final insertOperations = operations + .whereType() + .sorted(_descendingCompareTo) + .toList(); + + _mergeConsecutiveOperations( + insertOperations, + (prev, current) => InsertOperation( + prev.path, + [...prev.nodes, ...current.nodes], + ), + ); + + if (insertOperations.isNotEmpty) { + copy + ..removeWhere((op) => op is InsertOperation) + ..insertAll(0, insertOperations); // Insert ops must be at the start + } + + if (enableDebugLog) { + _logOperations('mergeInsertOperations[after]', copy); + } + + return copy; + } + + /// Merges consecutive delete operations to reduce the number of operations. + /// Operations are merged if they target consecutive paths in the document. + List mergeDeleteOperations(List operations) { + if (enableDebugLog) { + _logOperations('mergeDeleteOperations[before]', operations); + } + + final copy = [...operations]; + final deleteOperations = operations + .whereType() + .sorted(_descendingCompareTo) + .toList(); + + _mergeConsecutiveOperations( + deleteOperations, + (prev, current) => DeleteOperation( + prev.path, + [...prev.nodes, ...current.nodes], + ), + ); + + if (deleteOperations.isNotEmpty) { + copy + ..removeWhere((op) => op is DeleteOperation) + ..addAll(deleteOperations); // Delete ops must be at the end + } + + if (enableDebugLog) { + _logOperations('mergeDeleteOperations[after]', copy); + } + + return copy; + } + + /// Merge consecutive operations of the same type + void _mergeConsecutiveOperations( + List operations, + T Function(T prev, T current) merge, + ) { + for (var i = operations.length - 1; i > 0; i--) { + final op = operations[i]; + final previousOp = operations[i - 1]; + + if (op.path.equals(previousOp.path.next)) { + operations + ..removeAt(i) + ..[i - 1] = merge(previousOp, op); + } + } + } + + void _logOperations(String prefix, List operations) { + debugPrint('$prefix: ${operations.map((op) => op.toJson()).toList()}'); + } + + int _descendingCompareTo(Operation a, Operation b) { + return a.path > b.path ? 1 : -1; + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/application/document_rules.dart b/frontend/appflowy_flutter/lib/plugins/document/application/document_rules.dart new file mode 100644 index 0000000000..f530b1ef8d --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/application/document_rules.dart @@ -0,0 +1,140 @@ +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; + +/// Apply rules to the document +/// +/// 1. ensure there is at least one paragraph in the document, otherwise the user will be blocked from typing +/// 2. remove columns block if its children are empty +class DocumentRules { + DocumentRules({ + required this.editorState, + }); + + final EditorState editorState; + + Future applyRules({ + required EditorTransactionValue value, + }) async { + await Future.wait([ + _ensureAtLeastOneParagraphExists(value: value), + _removeColumnIfItIsEmpty(value: value), + ]); + } + + Future _ensureAtLeastOneParagraphExists({ + required EditorTransactionValue value, + }) async { + final document = editorState.document; + if (document.root.children.isEmpty) { + final transaction = editorState.transaction; + transaction + ..insertNode([0], paragraphNode()) + ..afterSelection = Selection.collapsed( + Position(path: [0]), + ); + await editorState.apply(transaction); + } + } + + Future _removeColumnIfItIsEmpty({ + required EditorTransactionValue value, + }) async { + final transaction = value.$2; + final options = value.$3; + + if (options.inMemoryUpdate) { + return; + } + + for (final operation in transaction.operations) { + final deleteColumnsTransaction = editorState.transaction; + if (operation is DeleteOperation) { + final path = operation.path; + final column = editorState.document.nodeAtPath(path.parent); + if (column != null && column.type == SimpleColumnBlockKeys.type) { + // check if the column is empty + final children = column.children; + if (children.isEmpty) { + // delete the column or the columns + final columns = column.parent; + + if (columns != null && + columns.type == SimpleColumnsBlockKeys.type) { + final nonEmptyColumnCount = columns.children.fold( + 0, + (p, c) => c.children.isEmpty ? p : p + 1, + ); + + // Example: + // columns + // - column 1 + // - paragraph 1-1 + // - paragraph 1-2 + // - column 2 + // - paragraph 2 + // - column 3 + // - paragraph 3 + // + // case 1: delete the paragraph 3 from column 3. + // because there is only one child in column 3, we should delete the column 3 as well. + // the result should be: + // columns + // - column 1 + // - paragraph 1-1 + // - paragraph 1-2 + // - column 2 + // - paragraph 2 + // + // case 2: delete the paragraph 3 from column 3 and delete the paragraph 2 from column 2. + // in this case, there will be only one column left, so we should delete the columns block and flatten the children. + // the result should be: + // paragraph 1-1 + // paragraph 1-2 + + // if there is only one empty column left, delete the columns block and flatten the children + if (nonEmptyColumnCount <= 1) { + // move the children in columns out of the column + final children = columns.children + .map((e) => e.children) + .expand((e) => e) + .map((e) => e.deepCopy()) + .toList(); + deleteColumnsTransaction.insertNodes(columns.path, children); + deleteColumnsTransaction.deleteNode(columns); + } else { + // otherwise, delete the column + deleteColumnsTransaction.deleteNode(column); + + final deletedColumnRatio = + column.attributes[SimpleColumnBlockKeys.ratio]; + if (deletedColumnRatio != null) { + // update the ratio of the columns + final columnsNode = column.columnsParent; + if (columnsNode != null) { + final length = columnsNode.children.length; + for (final columnNode in columnsNode.children) { + final ratio = + columnNode.attributes[SimpleColumnBlockKeys.ratio] ?? + 1.0 / length; + if (ratio != null) { + deleteColumnsTransaction.updateNode(columnNode, { + ...columnNode.attributes, + SimpleColumnBlockKeys.ratio: + ratio + deletedColumnRatio / (length - 1), + }); + } + } + } + } + } + } + } + } + } + + if (deleteColumnsTransaction.operations.isNotEmpty) { + await editorState.apply(deleteColumnsTransaction); + } + } + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/application/document_sync_bloc.dart b/frontend/appflowy_flutter/lib/plugins/document/application/document_sync_bloc.dart index 0fae90920d..7254539809 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/application/document_sync_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/application/document_sync_bloc.dart @@ -31,7 +31,7 @@ class DocumentSyncBloc extends Bloc { emit( state.copyWith( shouldShowIndicator: - userProfile?.authenticator == AuthenticatorPB.AppFlowyCloud, + userProfile?.workspaceAuthType == AuthTypePB.Server, ), ); _syncStateListener.start( diff --git a/frontend/appflowy_flutter/lib/plugins/document/application/editor_transaction_adapter.dart b/frontend/appflowy_flutter/lib/plugins/document/application/editor_transaction_adapter.dart index d66110d950..2094462d6d 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/application/editor_transaction_adapter.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/application/editor_transaction_adapter.dart @@ -4,23 +4,10 @@ import 'dart:convert'; import 'package:appflowy/plugins/document/application/document_bloc.dart'; import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart'; import 'package:appflowy/plugins/document/application/document_service.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/ask_ai_block_component.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-document/protobuf.dart'; -import 'package:appflowy_editor/appflowy_editor.dart' - show - EditorState, - Transaction, - Operation, - InsertOperation, - UpdateOperation, - DeleteOperation, - PathExtensions, - Node, - Path, - Delta, - composeAttributes, - blockComponentDelta; +import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:collection/collection.dart'; import 'package:nanoid/nanoid.dart'; @@ -126,7 +113,7 @@ class TransactionAdapter { ) { return transaction.operations .map((op) => op.toBlockAction(editorState, documentId)) - .whereNotNull() + .nonNulls .expand((element) => element) .toList(growable: false); // avoid lazy evaluation } @@ -176,7 +163,7 @@ extension on InsertOperation { Path currentPath = path; final List actions = []; for (final node in nodes) { - if (node.type == AskAIBlockKeys.type) { + if (node.type == AiWriterBlockKeys.type) { continue; } @@ -287,11 +274,6 @@ extension on UpdateOperation { // create the external text if the node contains the delta in its data. final prevDelta = oldAttributes[blockComponentDelta]; final delta = attributes[blockComponentDelta]; - final diff = prevDelta != null && delta != null - ? Delta.fromJson(prevDelta).diff( - Delta.fromJson(delta), - ) - : null; final composedAttributes = composeAttributes(oldAttributes, attributes); final composedDelta = composedAttributes?[blockComponentDelta]; @@ -312,12 +294,15 @@ extension on UpdateOperation { // to be compatible with the old version, we create a new text id if the text id is empty. final textId = nanoid(6); final textDelta = composedDelta ?? delta ?? prevDelta; - final textDeltaPayloadPB = textDelta == null + final correctedTextDelta = + textDelta != null ? _correctAttributes(textDelta) : null; + + final textDeltaPayloadPB = correctedTextDelta == null ? null : TextDeltaPayloadPB( documentId: documentId, textId: textId, - delta: jsonEncode(textDelta), + delta: jsonEncode(correctedTextDelta), ); node.externalValues = ExternalValues( @@ -342,12 +327,20 @@ extension on UpdateOperation { ), ); } else { - final textDeltaPayloadPB = delta == null + final diff = prevDelta != null && delta != null + ? Delta.fromJson(prevDelta).diff( + Delta.fromJson(delta), + ) + : null; + + final correctedDiff = diff != null ? _correctDelta(diff) : null; + + final textDeltaPayloadPB = correctedDiff == null ? null : TextDeltaPayloadPB( documentId: documentId, textId: textId, - delta: jsonEncode(diff), + delta: jsonEncode(correctedDiff), ); if (enableDocumentInternalLog) { @@ -370,6 +363,58 @@ extension on UpdateOperation { return actions; } + + // if the value in Delta's attributes is false, we should set the value to null instead. + // because on Yjs, canceling format must use the null value. If we use false, the update will be rejected. + List? _correctDelta(Delta delta) { + // if the value in diff's attributes is false, we should set the value to null instead. + // because on Yjs, canceling format must use the null value. If we use false, the update will be rejected. + final correctedOps = delta.map((op) { + final attributes = op.attributes?.map( + (key, value) => MapEntry( + key, + // if the value is false, we should set the value to null instead. + value == false ? null : value, + ), + ); + + if (attributes != null) { + if (op is TextRetain) { + return TextRetain(op.length, attributes: attributes); + } else if (op is TextInsert) { + return TextInsert(op.text, attributes: attributes); + } + // ignore the other operations that do not contain attributes. + } + + return op; + }); + + return correctedOps.toList(growable: false); + } + + // Refer to [_correctDelta] for more details. + List> _correctAttributes( + List> attributes, + ) { + final correctedAttributes = attributes.map((attribute) { + return attribute.map((key, value) { + if (value is bool) { + return MapEntry(key, value == false ? null : value); + } else if (value is Map) { + return MapEntry( + key, + value.map((key, value) { + return MapEntry(key, value == false ? null : value); + }), + ); + } + return MapEntry(key, value); + }); + }).toList(growable: false); + + return correctedAttributes; + } } extension on DeleteOperation { diff --git a/frontend/appflowy_flutter/lib/plugins/document/document.dart b/frontend/appflowy_flutter/lib/plugins/document/document.dart index dcfc1906a3..4ebc6f1b47 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/document.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/document.dart @@ -1,4 +1,4 @@ -library document_plugin; +library; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; @@ -9,6 +9,7 @@ import 'package:appflowy/plugins/document/presentation/document_collaborators.da import 'package:appflowy/plugins/shared/share/share_button.dart'; import 'package:appflowy/plugins/util.dart'; import 'package:appflowy/shared/feature_flags.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/startup/plugin/plugin.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy/workspace/application/view_info/view_info_bloc.dart'; @@ -107,6 +108,7 @@ class DocumentPluginWidgetBuilder extends PluginWidgetBuilder final ViewInfoBloc bloc; final ViewPluginNotifier notifier; + ViewPB get view => notifier.view; int? deletedViewIndex; final Selection? initialSelection; @@ -130,6 +132,12 @@ class DocumentPluginWidgetBuilder extends PluginWidgetBuilder final fixedTitle = data?[MobileDocumentScreen.viewFixedTitle]; final blockId = initialBlockId ?? data?[MobileDocumentScreen.viewBlockId]; + final tabs = data?[MobileDocumentScreen.viewSelectTabs] ?? + const [ + PickerTabType.emoji, + PickerTabType.icon, + PickerTabType.custom, + ]; return BlocProvider.value( value: bloc, @@ -141,6 +149,7 @@ class DocumentPluginWidgetBuilder extends PluginWidgetBuilder initialSelection: initialSelection, initialBlockId: blockId, fixedTitle: fixedTitle, + tabs: tabs, ), ), ); diff --git a/frontend/appflowy_flutter/lib/plugins/document/document_page.dart b/frontend/appflowy_flutter/lib/plugins/document/document_page.dart index 22080a94fc..8716bb7ae2 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/document_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/document_page.dart @@ -1,23 +1,28 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart'; import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart'; import 'package:appflowy/plugins/document/application/document_bloc.dart'; import 'package:appflowy/plugins/document/presentation/banner.dart'; import 'package:appflowy/plugins/document/presentation/editor_drop_handler.dart'; import 'package:appflowy/plugins/document/presentation/editor_page.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/ai/widgets/ai_writer_scroll_wrapper.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/shared_context/shared_context.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/transaction_handler/editor_transaction_service.dart'; import 'package:appflowy/plugins/document/presentation/editor_style.dart'; import 'package:appflowy/shared/flowy_error_page.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart'; import 'package:appflowy/workspace/application/action_navigation/navigation_action.dart'; import 'package:appflowy/workspace/application/view/prelude.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; +import 'package:appflowy/workspace/application/view/view_lock_status_bloc.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:provider/provider.dart'; @@ -28,6 +33,7 @@ class DocumentPage extends StatefulWidget { super.key, required this.view, required this.onDeleted, + required this.tabs, this.initialSelection, this.initialBlockId, this.fixedTitle, @@ -38,6 +44,7 @@ class DocumentPage extends StatefulWidget { final Selection? initialSelection; final String? initialBlockId; final String? fixedTitle; + final List tabs; @override State createState() => _DocumentPageState(); @@ -60,6 +67,7 @@ class _DocumentPageState extends State void dispose() { WidgetsBinding.instance.removeObserver(this); documentBloc.close(); + super.dispose(); } @@ -79,31 +87,65 @@ class _DocumentPageState extends State providers: [ BlocProvider.value(value: getIt()), BlocProvider.value(value: documentBloc), + BlocProvider.value( + value: ViewLockStatusBloc(view: widget.view) + ..add(ViewLockStatusEvent.initial()), + ), + BlocProvider( + create: (context) => + ViewBloc(view: widget.view)..add(const ViewEvent.initial()), + lazy: false, + ), ], - child: BlocBuilder( - buildWhen: shouldRebuildDocument, - builder: (context, state) { - if (state.isLoading) { - return const Center(child: CircularProgressIndicator.adaptive()); + child: BlocConsumer( + listenWhen: (prev, curr) => curr.isLocked != prev.isLocked, + listener: (context, lockStatusState) { + if (lockStatusState.isLoadingLockStatus) { + return; } + editorState?.editable = !lockStatusState.isLocked; + }, + builder: (context, lockStatusState) { + return BlocBuilder( + buildWhen: shouldRebuildDocument, + builder: (context, state) { + if (state.isLoading) { + return const Center( + child: CircularProgressIndicator.adaptive(), + ); + } - final editorState = state.editorState; - this.editorState = editorState; - final error = state.error; - if (error != null || editorState == null) { - Log.error(error); - return Center(child: AppFlowyErrorPage(error: error)); - } + final editorState = state.editorState; + this.editorState = editorState; + final error = state.error; + if (error != null || editorState == null) { + Log.error(error); + return Center(child: AppFlowyErrorPage(error: error)); + } - if (state.forceClose) { - widget.onDeleted(); - return const SizedBox.shrink(); - } + if (state.forceClose) { + widget.onDeleted(); + return const SizedBox.shrink(); + } - return BlocListener( - listenWhen: (_, curr) => curr.action != null, - listener: onNotificationAction, - child: buildEditorPage(context, state), + return MultiBlocListener( + listeners: [ + BlocListener( + listener: (context, state) => + editorState.editable = !state.isLocked, + ), + BlocListener( + listenWhen: (_, curr) => curr.action != null, + listener: onNotificationAction, + ), + ], + child: AiWriterScrollWrapper( + viewId: widget.view.id, + editorState: editorState, + child: buildEditorPage(context, state), + ), + ); + }, ); }, ), @@ -135,6 +177,7 @@ class _DocumentPageState extends State context: context, width: width, padding: EditorStyleCustomizer.documentPadding, + editorState: editorState, ), header: buildCoverAndIcon(context, state), initialSelection: initialSelection, @@ -153,9 +196,14 @@ class _DocumentPageState extends State context: context, width: width, padding: EditorStyleCustomizer.documentPadding, + editorState: editorState, ), header: buildCoverAndIcon(context, state), initialSelection: initialSelection, + placeholderText: (node) => + node.type == ParagraphBlockKeys.type && !node.isInTable + ? LocaleKeys.editor_slashPlaceHolder.tr() + : '', ), ); } @@ -178,7 +226,9 @@ class _DocumentPageState extends State editorState: state.editorState!, child: Column( children: [ - if (state.isDeleted) buildBanner(context), + // the banner only shows on desktop + if (state.isDeleted && UniversalPlatform.isDesktop) + buildBanner(context), Expanded(child: child), ], ), @@ -208,6 +258,7 @@ class _DocumentPageState extends State return DocumentImmersiveCover( fixedTitle: widget.fixedTitle, view: widget.view, + tabs: widget.tabs, userProfilePB: userProfilePB, ); } @@ -215,10 +266,11 @@ class _DocumentPageState extends State final page = editorState.document.root; return DocumentCoverWidget( node: page, + tabs: widget.tabs, editorState: editorState, view: widget.view, onIconChanged: (icon) async => ViewBackendService.updateViewIcon( - viewId: widget.view.id, + view: widget.view, viewIcon: icon, ), ); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/collaborator_avater_stack.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/collaborator_avater_stack.dart index 8fa15af8b2..1be5a41d81 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/collaborator_avater_stack.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/collaborator_avater_stack.dart @@ -46,7 +46,7 @@ class CollaboratorAvatarStack extends StatelessWidget { width: width, child: WidgetStack( positions: settings, - buildInfoWidget: (value) => plusWidgetBuilder(value, border), + buildInfoWidget: (value, _) => plusWidgetBuilder(value, border), stackedWidgets: avatars .map( (avatar) => CircleAvatar( diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/compact_mode_event.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/compact_mode_event.dart new file mode 100644 index 0000000000..eaee989bbc --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/compact_mode_event.dart @@ -0,0 +1,13 @@ +import 'package:event_bus/event_bus.dart'; + +EventBus compactModeEventBus = EventBus(); + +class CompactModeEvent { + CompactModeEvent({ + required this.id, + required this.enable, + }); + + final String id; + final bool enable; +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/document_collaborators.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/document_collaborators.dart index 42d05c0f9e..d4a6815e32 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/document_collaborators.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/document_collaborators.dart @@ -1,13 +1,13 @@ import 'package:appflowy/plugins/document/application/document_awareness_metadata.dart'; import 'package:appflowy/plugins/document/application/document_collaborators_bloc.dart'; import 'package:appflowy/plugins/document/presentation/collaborator_avater_stack.dart'; +import 'package:appflowy/workspace/presentation/widgets/user_avatar.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:avatar_stack/avatar_stack.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:string_validator/string_validator.dart'; +import 'package:universal_platform/universal_platform.dart'; class DocumentCollaborators extends StatelessWidget { const DocumentCollaborators({ @@ -93,39 +93,14 @@ class _UserAvatar extends StatelessWidget { @override Widget build(BuildContext context) { - final Widget child; - if (isURL(user.userAvatar)) { - child = _buildUrlAvatar(context); - } else { - child = _buildNameAvatar(context); - } return FlowyTooltip( message: user.userName, - child: child, - ); - } - - Widget _buildNameAvatar(BuildContext context) { - return CircleAvatar( - backgroundColor: user.cursorColor.tryToColor(), - child: FlowyText( - user.userName.characters.firstOrNull ?? ' ', - fontSize: fontSize, - color: Colors.black, - ), - ); - } - - Widget _buildUrlAvatar(BuildContext context) { - return ClipRRect( - borderRadius: BorderRadius.circular(width), - child: CircleAvatar( - backgroundColor: user.cursorColor.tryToColor(), - child: Image.network( - user.userAvatar, - fit: BoxFit.cover, - errorBuilder: (context, error, stackTrace) => - _buildNameAvatar(context), + child: IgnorePointer( + child: UserAvatar( + iconUrl: user.userAvatar, + name: user.userName, + size: 30.0, + fontSize: fontSize ?? (UniversalPlatform.isMobile ? 14 : 12), ), ), ); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_configuration.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_configuration.dart index 1d10c1f161..5e7eefc24e 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_configuration.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_configuration.dart @@ -6,7 +6,8 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/mo import 'package:appflowy/plugins/document/presentation/editor_plugins/code_block/code_block_copy_button.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy/plugins/document/presentation/editor_style.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor/appflowy_editor.dart' + hide QuoteBlockComponentBuilder, quoteNode, QuoteBlockKeys; import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; import 'package:easy_localization/easy_localization.dart' hide TextDirection; import 'package:flowy_infra/theme_extension.dart'; @@ -15,6 +16,17 @@ import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:universal_platform/universal_platform.dart'; +import 'editor_plugins/link_preview/custom_link_preview_block_component.dart'; +import 'editor_plugins/page_block/custom_page_block_component.dart'; + +/// A global configuration for the editor. +class EditorGlobalConfiguration { + /// Whether to enable the drag menu in the editor. + /// + /// Case 1, resizing the columns block in the desktop, then the drag menu will be disabled. + static ValueNotifier enableDragMenu = ValueNotifier(true); +} + /// The node types that support slash menu. final Set supportSlashMenuNodeTypes = { ParagraphBlockKeys.type, @@ -26,11 +38,16 @@ final Set supportSlashMenuNodeTypes = { NumberedListBlockKeys.type, QuoteBlockKeys.type, ToggleListBlockKeys.type, + CalloutBlockKeys.type, // Simple table SimpleTableBlockKeys.type, SimpleTableRowBlockKeys.type, SimpleTableCellBlockKeys.type, + + // Columns + SimpleColumnsBlockKeys.type, + SimpleColumnBlockKeys.type, }; /// Build the block component builders. @@ -52,6 +69,7 @@ Map buildBlockComponentBuilders({ ShowPlaceholder? showParagraphPlaceholder, String Function(Node)? placeholderText, EdgeInsets? customHeadingPadding, + bool alwaysDistributeSimpleTableColumnWidths = false, }) { final configuration = _buildDefaultConfiguration(context); final builders = _buildBlockComponentBuilderMap( @@ -61,6 +79,8 @@ Map buildBlockComponentBuilders({ styleCustomizer: styleCustomizer, showParagraphPlaceholder: showParagraphPlaceholder, placeholderText: placeholderText, + alwaysDistributeSimpleTableColumnWidths: + alwaysDistributeSimpleTableColumnWidths, ); // customize the action builder. actually, we can customize them in their own builder. Put them here just for convenience. @@ -102,9 +122,25 @@ BlockComponentConfiguration _buildDefaultConfiguration(BuildContext context) { return const EdgeInsets.symmetric(vertical: 5.0); }, - indentPadding: (node, textDirection) => textDirection == TextDirection.ltr - ? const EdgeInsets.only(left: 26.0) - : const EdgeInsets.only(right: 26.0), + indentPadding: (node, textDirection) { + double padding = 26.0; + + // only add indent padding for the top level node to align the children + if (UniversalPlatform.isMobile && node.level == 1) { + padding += EditorStyleCustomizer.nodeHorizontalPadding - 4; + } + + // in the quote block, we reduce the indent padding for the first level block. + // So we have to add more padding for the second level to avoid the drag menu overlay the quote icon. + if (node.parent?.type == QuoteBlockKeys.type && + UniversalPlatform.isDesktop) { + padding += 22; + } + + return textDirection == TextDirection.ltr + ? EdgeInsets.only(left: padding) + : EdgeInsets.only(right: padding); + }, ); return configuration; } @@ -179,6 +215,16 @@ void _customBlockOptionActions( ), ); + builder.actionTrailingBuilder = (context, state) { + if (context.node.parent?.type == QuoteBlockKeys.type) { + return const SizedBox( + width: 24, + height: 24, + ); + } + return const SizedBox.shrink(); + }; + builder.actionBuilder = (context, state) { double top = builder.configuration.padding(context.node).top; final type = context.node.type; @@ -193,24 +239,45 @@ void _customBlockOptionActions( } else { top += 2.0; } - return Padding( - padding: EdgeInsets.only(top: top), - child: BlockActionList( - blockComponentContext: context, - blockComponentState: state, - editorState: editorState, - blockComponentBuilder: builders, - actions: actions, - showSlashMenu: slashMenuItemsBuilder != null - ? () => customAppFlowySlashCommand( - itemsBuilder: slashMenuItemsBuilder, - shouldInsertSlash: false, - deleteKeywordsByDefault: true, - style: styleCustomizer.selectionMenuStyleBuilder(), - supportSlashMenuNodeTypes: supportSlashMenuNodeTypes, - ).handler.call(editorState) - : () {}, - ), + if (overflowTypes.contains(type)) { + top = top / 2; + } + return ValueListenableBuilder( + valueListenable: EditorGlobalConfiguration.enableDragMenu, + builder: (_, enableDragMenu, child) { + return ValueListenableBuilder( + valueListenable: editorState.editableNotifier, + builder: (_, editable, child) { + return IgnorePointer( + ignoring: !editable, + child: Opacity( + opacity: editable && enableDragMenu ? 1.0 : 0.0, + child: Padding( + padding: EdgeInsets.only(top: top), + child: BlockActionList( + blockComponentContext: context, + blockComponentState: state, + editorState: editorState, + blockComponentBuilder: builders, + actions: actions, + showSlashMenu: slashMenuItemsBuilder != null + ? () => customAppFlowySlashCommand( + itemsBuilder: slashMenuItemsBuilder, + shouldInsertSlash: false, + deleteKeywordsByDefault: true, + style: styleCustomizer + .selectionMenuStyleBuilder(), + supportSlashMenuNodeTypes: + supportSlashMenuNodeTypes, + ).handler.call(editorState) + : () {}, + ), + ), + ), + ); + }, + ); + }, ); }; } @@ -225,9 +292,10 @@ Map _buildBlockComponentBuilderMap( ShowPlaceholder? showParagraphPlaceholder, String Function(Node)? placeholderText, EdgeInsets? customHeadingPadding, + bool alwaysDistributeSimpleTableColumnWidths = false, }) { final customBlockComponentBuilderMap = { - PageBlockKeys.type: PageBlockComponentBuilder(), + PageBlockKeys.type: CustomPageBlockComponentBuilder(), ParagraphBlockKeys.type: _buildParagraphBlockComponentBuilder( context, configuration, @@ -302,11 +370,7 @@ Map _buildBlockComponentBuilderMap( configuration, styleCustomizer, ), - AIWriterBlockKeys.type: _buildAIWriterBlockComponentBuilder( - context, - configuration, - ), - AskAIBlockKeys.type: _buildAskAIBlockComponentBuilder( + AiWriterBlockKeys.type: _buildAIWriterBlockComponentBuilder( context, configuration, ), @@ -325,6 +389,11 @@ Map _buildBlockComponentBuilderMap( context, configuration, ), + // Flutter doesn't support the video widget, so we forward the video block to the link preview block + VideoBlockKeys.type: _buildLinkPreviewBlockComponentBuilder( + context, + configuration, + ), FileBlockKeys.type: _buildFileBlockComponentBuilder( context, configuration, @@ -340,14 +409,25 @@ Map _buildBlockComponentBuilderMap( SimpleTableBlockKeys.type: _buildSimpleTableBlockComponentBuilder( context, configuration, + alwaysDistributeColumnWidths: alwaysDistributeSimpleTableColumnWidths, ), SimpleTableRowBlockKeys.type: _buildSimpleTableRowBlockComponentBuilder( context, configuration, + alwaysDistributeColumnWidths: alwaysDistributeSimpleTableColumnWidths, ), SimpleTableCellBlockKeys.type: _buildSimpleTableCellBlockComponentBuilder( context, configuration, + alwaysDistributeColumnWidths: alwaysDistributeSimpleTableColumnWidths, + ), + SimpleColumnsBlockKeys.type: _buildSimpleColumnsBlockComponentBuilder( + context, + configuration, + ), + SimpleColumnBlockKeys.type: _buildSimpleColumnBlockComponentBuilder( + context, + configuration, ), }; @@ -361,8 +441,9 @@ Map _buildBlockComponentBuilderMap( SimpleTableBlockComponentBuilder _buildSimpleTableBlockComponentBuilder( BuildContext context, - BlockComponentConfiguration configuration, -) { + BlockComponentConfiguration configuration, { + bool alwaysDistributeColumnWidths = false, +}) { final copiedConfiguration = configuration.copyWith( padding: (node) { final padding = configuration.padding(node); @@ -373,21 +454,32 @@ SimpleTableBlockComponentBuilder _buildSimpleTableBlockComponentBuilder( } }, ); - return SimpleTableBlockComponentBuilder(configuration: copiedConfiguration); + return SimpleTableBlockComponentBuilder( + configuration: copiedConfiguration, + alwaysDistributeColumnWidths: alwaysDistributeColumnWidths, + ); } SimpleTableRowBlockComponentBuilder _buildSimpleTableRowBlockComponentBuilder( BuildContext context, - BlockComponentConfiguration configuration, -) { - return SimpleTableRowBlockComponentBuilder(configuration: configuration); + BlockComponentConfiguration configuration, { + bool alwaysDistributeColumnWidths = false, +}) { + return SimpleTableRowBlockComponentBuilder( + configuration: configuration, + alwaysDistributeColumnWidths: alwaysDistributeColumnWidths, + ); } SimpleTableCellBlockComponentBuilder _buildSimpleTableCellBlockComponentBuilder( BuildContext context, - BlockComponentConfiguration configuration, -) { - return SimpleTableCellBlockComponentBuilder(configuration: configuration); + BlockComponentConfiguration configuration, { + bool alwaysDistributeColumnWidths = false, +}) { + return SimpleTableCellBlockComponentBuilder( + configuration: configuration, + alwaysDistributeColumnWidths: alwaysDistributeColumnWidths, + ); } ParagraphBlockComponentBuilder _buildParagraphBlockComponentBuilder( @@ -399,10 +491,11 @@ ParagraphBlockComponentBuilder _buildParagraphBlockComponentBuilder( return ParagraphBlockComponentBuilder( configuration: configuration.copyWith( placeholderText: placeholderText, - textStyle: (node) => _buildTextStyleInTableCell( + textStyle: (node, {TextSpan? textSpan}) => _buildTextStyleInTableCell( context, node: node, configuration: configuration, + textSpan: textSpan, ), textAlign: (node) => _buildTextAlignInTableCell( context, @@ -421,10 +514,11 @@ TodoListBlockComponentBuilder _buildTodoListBlockComponentBuilder( return TodoListBlockComponentBuilder( configuration: configuration.copyWith( placeholderText: (_) => LocaleKeys.blockPlaceholders_todoList.tr(), - textStyle: (node) => _buildTextStyleInTableCell( + textStyle: (node, {TextSpan? textSpan}) => _buildTextStyleInTableCell( context, node: node, configuration: configuration, + textSpan: textSpan, ), textAlign: (node) => _buildTextAlignInTableCell( context, @@ -451,10 +545,11 @@ BulletedListBlockComponentBuilder _buildBulletedListBlockComponentBuilder( return BulletedListBlockComponentBuilder( configuration: configuration.copyWith( placeholderText: (_) => LocaleKeys.blockPlaceholders_bulletList.tr(), - textStyle: (node) => _buildTextStyleInTableCell( + textStyle: (node, {TextSpan? textSpan}) => _buildTextStyleInTableCell( context, node: node, configuration: configuration, + textSpan: textSpan, ), textAlign: (node) => _buildTextAlignInTableCell( context, @@ -473,10 +568,11 @@ NumberedListBlockComponentBuilder _buildNumberedListBlockComponentBuilder( return NumberedListBlockComponentBuilder( configuration: configuration.copyWith( placeholderText: (_) => LocaleKeys.blockPlaceholders_numberList.tr(), - textStyle: (node) => _buildTextStyleInTableCell( + textStyle: (node, {TextSpan? textSpan}) => _buildTextStyleInTableCell( context, node: node, configuration: configuration, + textSpan: textSpan, ), textAlign: (node) => _buildTextAlignInTableCell( context, @@ -507,16 +603,30 @@ QuoteBlockComponentBuilder _buildQuoteBlockComponentBuilder( return QuoteBlockComponentBuilder( configuration: configuration.copyWith( placeholderText: (_) => LocaleKeys.blockPlaceholders_quote.tr(), - textStyle: (node) => _buildTextStyleInTableCell( + textStyle: (node, {TextSpan? textSpan}) => _buildTextStyleInTableCell( context, node: node, configuration: configuration, + textSpan: textSpan, ), textAlign: (node) => _buildTextAlignInTableCell( context, node: node, configuration: configuration, ), + indentPadding: (node, textDirection) { + if (UniversalPlatform.isMobile) { + return configuration.indentPadding(node, textDirection); + } + + if (node.isInTable) { + return textDirection == TextDirection.ltr + ? EdgeInsets.only(left: 24) + : EdgeInsets.only(right: 24); + } + + return EdgeInsets.zero; + }, ), ); } @@ -529,10 +639,11 @@ HeadingBlockComponentBuilder _buildHeadingBlockComponentBuilder( ) { return HeadingBlockComponentBuilder( configuration: configuration.copyWith( - textStyle: (node) => _buildTextStyleInTableCell( + textStyle: (node, {TextSpan? textSpan}) => _buildTextStyleInTableCell( context, node: node, configuration: configuration, + textSpan: textSpan, ), padding: (node) { if (customHeadingPadding != null) { @@ -585,10 +696,14 @@ CustomImageBlockComponentBuilder _buildCustomImageBlockComponentBuilder( return CustomImageBlockComponentBuilder( configuration: configuration, showMenu: true, - menuBuilder: (node, state) => Positioned( + menuBuilder: (node, state, imageStateNotifier) => Positioned( top: 10, right: 10, - child: ImageMenu(node: node, state: state), + child: ImageMenu( + node: node, + state: state, + imageStateNotifier: imageStateNotifier, + ), ), ); } @@ -625,7 +740,14 @@ TableBlockComponentBuilder _buildTableBlockComponentBuilder( BlockComponentConfiguration configuration, ) { return TableBlockComponentBuilder( - menuBuilder: (node, editorState, position, dir, onBuild, onClose) => + menuBuilder: ( + node, + editorState, + position, + dir, + onBuild, + onClose, + ) => TableMenu( node: node, editorState: editorState, @@ -652,7 +774,14 @@ TableCellBlockComponentBuilder _buildTableCellBlockComponentBuilder( } return buildEditorCustomizedColor(context, node, colorString); }, - menuBuilder: (node, editorState, position, dir, onBuild, onClose) => + menuBuilder: ( + node, + editorState, + position, + dir, + onBuild, + onClose, + ) => TableMenu( node: node, editorState: editorState, @@ -670,7 +799,12 @@ DatabaseViewBlockComponentBuilder _buildDatabaseViewBlockComponentBuilder( ) { return DatabaseViewBlockComponentBuilder( configuration: configuration.copyWith( - padding: (_) => const EdgeInsets.symmetric(vertical: 10), + padding: (node) { + if (UniversalPlatform.isMobile) { + return configuration.padding(node); + } + return const EdgeInsets.symmetric(vertical: 10); + }, ), ); } @@ -693,13 +827,20 @@ CalloutBlockComponentBuilder _buildCalloutBlockComponentBuilder( node: node, configuration: configuration, ), - textStyle: (node) => _buildTextStyleInTableCell( + textStyle: (node, {TextSpan? textSpan}) => _buildTextStyleInTableCell( context, node: node, configuration: configuration, + textSpan: textSpan, ), + indentPadding: (node, _) => EdgeInsets.only(left: 42), ), - inlinePadding: const EdgeInsets.symmetric(vertical: 8.0), + inlinePadding: (node) { + if (node.children.isEmpty) { + return const EdgeInsets.symmetric(vertical: 8.0); + } + return EdgeInsets.only(top: 8.0, bottom: 2.0); + }, defaultColor: calloutBGColor, ); } @@ -751,13 +892,6 @@ AIWriterBlockComponentBuilder _buildAIWriterBlockComponentBuilder( return AIWriterBlockComponentBuilder(); } -AskAIBlockComponentBuilder _buildAskAIBlockComponentBuilder( - BuildContext context, - BlockComponentConfiguration configuration, -) { - return AskAIBlockComponentBuilder(); -} - ToggleListBlockComponentBuilder _buildToggleListBlockComponentBuilder( BuildContext context, BlockComponentConfiguration configuration, @@ -784,11 +918,12 @@ ToggleListBlockComponentBuilder _buildToggleListBlockComponentBuilder( return const EdgeInsets.only(top: 12.0, bottom: 4.0); }, - textStyle: (node) { + textStyle: (node, {TextSpan? textSpan}) { final textStyle = _buildTextStyleInTableCell( context, node: node, configuration: configuration, + textSpan: textSpan, ); final level = node.attributes[ToggleListBlockKeys.level] as int?; if (level == null) { @@ -823,35 +958,30 @@ OutlineBlockComponentBuilder _buildOutlineBlockComponentBuilder( ) { return OutlineBlockComponentBuilder( configuration: configuration.copyWith( - placeholderTextStyle: (_) => + placeholderTextStyle: (node, {TextSpan? textSpan}) => styleCustomizer.outlineBlockPlaceholderStyleBuilder(), - padding: (_) => const EdgeInsets.only(top: 12.0, bottom: 4.0), + padding: (node) { + if (UniversalPlatform.isMobile) { + return configuration.padding(node); + } + return const EdgeInsets.only(top: 12.0, bottom: 4.0); + }, ), ); } -LinkPreviewBlockComponentBuilder _buildLinkPreviewBlockComponentBuilder( +CustomLinkPreviewBlockComponentBuilder _buildLinkPreviewBlockComponentBuilder( BuildContext context, BlockComponentConfiguration configuration, ) { - return LinkPreviewBlockComponentBuilder( + return CustomLinkPreviewBlockComponentBuilder( configuration: configuration.copyWith( - padding: (_) => const EdgeInsets.symmetric(vertical: 10), - ), - cache: LinkPreviewDataCache(), - showMenu: true, - menuBuilder: (context, node, state) => Positioned( - top: 10, - right: 0, - child: LinkPreviewMenu(node: node, state: state), - ), - builder: (_, node, url, title, description, imageUrl) => - CustomLinkPreviewWidget( - node: node, - url: url, - title: title, - description: description, - imageUrl: imageUrl, + padding: (node) { + if (UniversalPlatform.isMobile) { + return configuration.padding(node); + } + return const EdgeInsets.symmetric(vertical: 10); + }, ), ); } @@ -872,7 +1002,42 @@ SubPageBlockComponentBuilder _buildSubPageBlockComponentBuilder( }) { return SubPageBlockComponentBuilder( configuration: configuration.copyWith( - textStyle: (node) => styleCustomizer.subPageBlockTextStyleBuilder(), + textStyle: (node, {TextSpan? textSpan}) => + styleCustomizer.subPageBlockTextStyleBuilder(), + padding: (node) { + if (UniversalPlatform.isMobile) { + return const EdgeInsets.symmetric(horizontal: 18); + } + return configuration.padding(node); + }, + ), + ); +} + +SimpleColumnsBlockComponentBuilder _buildSimpleColumnsBlockComponentBuilder( + BuildContext context, + BlockComponentConfiguration configuration, +) { + return SimpleColumnsBlockComponentBuilder( + configuration: configuration.copyWith( + padding: (node) { + if (UniversalPlatform.isMobile) { + return configuration.padding(node); + } + + return EdgeInsets.zero; + }, + ), + ); +} + +SimpleColumnBlockComponentBuilder _buildSimpleColumnBlockComponentBuilder( + BuildContext context, + BlockComponentConfiguration configuration, +) { + return SimpleColumnBlockComponentBuilder( + configuration: configuration.copyWith( + padding: (_) => EdgeInsets.zero, ), ); } @@ -881,8 +1046,14 @@ TextStyle _buildTextStyleInTableCell( BuildContext context, { required Node node, required BlockComponentConfiguration configuration, + required TextSpan? textSpan, }) { - TextStyle textStyle = configuration.textStyle(node); + TextStyle textStyle = configuration.textStyle(node, textSpan: textSpan); + + textStyle = textStyle.copyWith( + fontFamily: textSpan?.style?.fontFamily, + fontSize: textSpan?.style?.fontSize, + ); if (node.isInHeaderColumn || node.isInHeaderRow || @@ -895,6 +1066,11 @@ TextStyle _buildTextStyleInTableCell( final cellTextColor = node.textColorInColumn ?? node.textColorInRow; + // enable it if we need to support the text color of the text span + // final isTextSpanColorNull = textSpan?.style?.color == null; + // final isTextSpanChildrenColorNull = + // textSpan?.children?.every((e) => e.style?.color == null) ?? true; + if (cellTextColor != null) { textStyle = textStyle.copyWith( color: buildEditorCustomizedColor( diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_drop_handler.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_drop_handler.dart index 443db069d1..62810545dd 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_drop_handler.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_drop_handler.dart @@ -1,14 +1,14 @@ -import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; -import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; -import 'package:flutter/material.dart'; - import 'package:appflowy/plugins/document/presentation/editor_drop_manager.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_file.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_image.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy/shared/patterns/file_type_patterns.dart'; +import 'package:appflowy/workspace/presentation/widgets/draggable_item/draggable_item.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:desktop_drop/desktop_drop.dart'; +import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; const _excludeFromDropTarget = [ @@ -16,6 +16,9 @@ const _excludeFromDropTarget = [ CustomImageBlockKeys.type, MultiImageBlockKeys.type, FileBlockKeys.type, + SimpleTableBlockKeys.type, + SimpleTableCellBlockKeys.type, + SimpleTableRowBlockKeys.type, ]; class EditorDropHandler extends StatelessWidget { @@ -38,8 +41,13 @@ class EditorDropHandler extends StatelessWidget { Widget build(BuildContext context) { final childWidget = Consumer( builder: (context, dropState, _) => DragTarget( - onLeave: (_) => editorState.selectionService.removeDropTarget(), + onLeave: (_) { + editorState.selectionService.removeDropTarget(); + disableAutoScrollWhenDragging = false; + }, onMove: (details) { + disableAutoScrollWhenDragging = true; + if (details.data.id == viewId) { return; } @@ -58,12 +66,20 @@ class EditorDropHandler extends StatelessWidget { return true; }, onAcceptWithDetails: _onDragViewDone, - builder: (context, _, __) => DropTarget( - enable: dropState.isDropEnabled, - onDragExited: (_) => editorState.selectionService.removeDropTarget(), - onDragUpdated: (details) => _onDragUpdated(details.globalPosition), - onDragDone: _onDragDone, - child: child, + builder: (context, _, __) => ValueListenableBuilder( + valueListenable: enableDocumentDragNotifier, + builder: (context, value, _) { + final enableDocumentDrag = value; + return DropTarget( + enable: dropState.isDropEnabled && enableDocumentDrag, + onDragExited: (_) => + editorState.selectionService.removeDropTarget(), + onDragUpdated: (details) => + _onDragUpdated(details.globalPosition), + onDragDone: _onDragDone, + child: child, + ); + }, ), ), ); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_drop_manager.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_drop_manager.dart index 728dee766d..8b59809f3b 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_drop_manager.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_drop_manager.dart @@ -15,3 +15,5 @@ class EditorDropManagerState extends ChangeNotifier { bool get isDropEnabled => _draggedTypes.isEmpty; } + +final enableDocumentDragNotifier = ValueNotifier(true); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart index 0cf39e8bcc..edb19232be 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart @@ -1,5 +1,6 @@ import 'dart:ui' as ui; +import 'package:appflowy/core/helpers/url_launcher.dart'; import 'package:appflowy/plugins/document/application/document_bloc.dart'; import 'package:appflowy/plugins/document/presentation/editor_configuration.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/background_color/theme_background_color.dart'; @@ -14,9 +15,12 @@ import 'package:appflowy/plugins/inline_actions/inline_actions_service.dart'; import 'package:appflowy/shared/feature_flags.dart'; import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; import 'package:appflowy/workspace/application/settings/shortcuts/settings_shortcuts_service.dart'; +import 'package:appflowy/workspace/application/view/view_bloc.dart'; +import 'package:appflowy/workspace/application/view/view_lock_status_bloc.dart'; import 'package:appflowy/workspace/application/view_info/view_info_bloc.dart'; import 'package:appflowy/workspace/presentation/home/af_focus_manager.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor/appflowy_editor.dart' hide QuoteBlockKeys; +import 'package:appflowy_ui/appflowy_ui.dart'; import 'package:collection/collection.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flutter/material.dart'; @@ -24,6 +28,17 @@ import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:universal_platform/universal_platform.dart'; +import 'editor_plugins/desktop_toolbar/desktop_floating_toolbar.dart'; +import 'editor_plugins/toolbar_item/custom_format_toolbar_items.dart'; +import 'editor_plugins/toolbar_item/custom_hightlight_color_toolbar_item.dart'; +import 'editor_plugins/toolbar_item/custom_link_toolbar_item.dart'; +import 'editor_plugins/toolbar_item/custom_placeholder_toolbar_item.dart'; +import 'editor_plugins/toolbar_item/custom_text_align_toolbar_item.dart'; +import 'editor_plugins/toolbar_item/custom_text_color_toolbar_item.dart'; +import 'editor_plugins/toolbar_item/more_option_toolbar_item.dart'; +import 'editor_plugins/toolbar_item/text_heading_toolbar_item.dart'; +import 'editor_plugins/toolbar_item/text_suggestions_toolbar_item.dart'; + /// Wrapper for the appflowy editor. class AppFlowyEditorPage extends StatefulWidget { const AppFlowyEditorPage({ @@ -79,22 +94,25 @@ class _AppFlowyEditorPageState extends State ]; final List toolbarItems = [ - askAIItem..isActive = onlyShowInTextType, - paragraphItem..isActive = onlyShowInSingleTextTypeSelectionAndExcludeTable, - headingsToolbarItem - ..isActive = onlyShowInSingleTextTypeSelectionAndExcludeTable, - ...markdownFormatItems..forEach((e) => e.isActive = showInAnyTextType), - quoteItem..isActive = onlyShowInSingleTextTypeSelectionAndExcludeTable, - bulletedListItem - ..isActive = onlyShowInSingleTextTypeSelectionAndExcludeTable, - numberedListItem - ..isActive = onlyShowInSingleTextTypeSelectionAndExcludeTable, - inlineMathEquationItem, - linkItem, - alignToolbarItem, - buildTextColorItem()..isActive = showInAnyTextType, - buildHighlightColorItem()..isActive = showInAnyTextType, - customizeFontToolbarItem..isActive = showInAnyTextType, + improveWritingItem, + group0PaddingItem, + aiWriterItem, + customTextHeadingItem, + buildPaddingPlaceholderItem( + 1, + isActive: onlyShowInSingleTextTypeSelectionAndExcludeTable, + ), + ...customMarkdownFormatItems, + group1PaddingItem, + customTextColorItem, + group1PaddingItem, + customHighlightColorItem, + customInlineCodeItem, + suggestionsItem, + customLinkItem, + group4PaddingItem, + customTextAlignItem, + moreOptionItem, ]; List get characterShortcutEvents { @@ -111,6 +129,7 @@ class _AppFlowyEditorPageState extends State } EditorStyleCustomizer get styleCustomizer => widget.styleCustomizer; + DocumentBloc get documentBloc => context.read(); late final EditorScrollController editorScrollController; @@ -150,8 +169,24 @@ class _AppFlowyEditorPageState extends State InlineMathEquationKeys.formula, ]); - indentableBlockTypes.add(ToggleListBlockKeys.type); - convertibleBlockTypes.add(ToggleListBlockKeys.type); + indentableBlockTypes.addAll([ + ToggleListBlockKeys.type, + CalloutBlockKeys.type, + QuoteBlockKeys.type, + ]); + convertibleBlockTypes.addAll([ + ToggleListBlockKeys.type, + CalloutBlockKeys.type, + QuoteBlockKeys.type, + ]); + + editorLaunchUrl = (url) { + if (url != null) { + afLaunchUrlString(url, addingHttpSchemeWhenFailed: true); + } + + return Future.value(true); + }; effectiveScrollController = widget.scrollController ?? ScrollController(); // disable the color parse in the HTML decoder. @@ -314,11 +349,16 @@ class _AppFlowyEditorPageState extends State ); final isViewDeleted = context.read().state.isDeleted; + final isLocked = + context.read()?.state.isLocked ?? false; + final editor = Directionality( textDirection: textDirection, child: AppFlowyEditor( editorState: widget.editorState, - editable: !isViewDeleted, + editable: !isViewDeleted && !isLocked, + disableSelectionService: UniversalPlatform.isMobile && isLocked, + disableKeyboardService: UniversalPlatform.isMobile && isLocked, editorScrollController: editorScrollController, // setup the auto focus parameters autoFocus: widget.autoFocus ?? autoFocus, @@ -344,6 +384,9 @@ class _AppFlowyEditorPageState extends State contextMenuItems: customContextMenuItems, // customize the header and footer. header: widget.header, + autoScrollEdgeOffset: UniversalPlatform.isDesktopOrWeb + ? 250 + : appFlowyEditorAutoScrollEdgeOffset, footer: GestureDetector( behavior: HitTestBehavior.translucent, onTap: () async { @@ -352,11 +395,11 @@ class _AppFlowyEditorPageState extends State }, child: SizedBox( width: double.infinity, - height: UniversalPlatform.isDesktopOrWeb ? 200 : 400, + height: UniversalPlatform.isDesktopOrWeb ? 600 : 400, ), ), dropTargetStyle: AppFlowyDropTargetStyle( - color: Theme.of(context).colorScheme.primary.withOpacity(0.8), + color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.8), margin: const EdgeInsets.only(left: 44), ), ), @@ -382,26 +425,51 @@ class _AppFlowyEditorPageState extends State anchor: anchor, closeToolbar: closeToolbar, ), + floatingToolbarHeight: 32, child: editor, ), ); } - + final appTheme = AppFlowyTheme.of(context); return Center( - child: FloatingToolbar( - style: styleCustomizer.floatingToolbarStyleBuilder(), - items: toolbarItems, - editorState: editorState, - editorScrollController: editorScrollController, - textDirection: textDirection, - tooltipBuilder: (context, id, message, child) => - widget.styleCustomizer.buildToolbarItemTooltip( - context, - id, - message, - child, + child: BlocProvider.value( + value: context.read(), + child: FloatingToolbar( + floatingToolbarHeight: 40, + padding: EdgeInsets.symmetric(horizontal: 6), + style: FloatingToolbarStyle( + backgroundColor: Theme.of(context).cardColor, + toolbarActiveColor: Color(0xffe0f8fd), + ), + items: toolbarItems, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(appTheme.borderRadius.l), + color: appTheme.surfaceColorScheme.primary, + boxShadow: appTheme.shadow.small, + ), + toolbarBuilder: (_, child, onDismiss, isMetricsChanged) => + BlocProvider.value( + value: context.read(), + child: DesktopFloatingToolbar( + editorState: editorState, + onDismiss: onDismiss, + enableAnimation: !isMetricsChanged, + child: child, + ), + ), + placeHolderBuilder: (_) => customPlaceholderItem, + editorState: editorState, + editorScrollController: editorScrollController, + textDirection: textDirection, + tooltipBuilder: (context, id, message, child) => + widget.styleCustomizer.buildToolbarItemTooltip( + context, + id, + message, + child, + ), + child: editor, ), - child: editor, ), ); } @@ -412,11 +480,13 @@ class _AppFlowyEditorPageState extends State }) { final documentBloc = context.read(); final isLocalMode = documentBloc.isLocalMode; + final view = context.read().state.view; return slashMenuItemsBuilder( editorState: editorState, node: node, isLocalMode: isLocalMode, documentBloc: documentBloc, + view: view, ); } @@ -505,6 +575,10 @@ class _AppFlowyEditorPageState extends State Position(path: lastNode.path), ); } + + transaction.customSelectionType = SelectionType.inline; + transaction.reason = SelectionUpdateReason.uiEvent; + await editorState.apply(transaction); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_button.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_button.dart index aed2de9abf..9e0b241ab3 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_button.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_button.dart @@ -1,8 +1,7 @@ import 'dart:io'; import 'package:appflowy/generated/flowy_svgs.g.dart'; -import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; -import 'package:flowy_infra_ui/widget/ignore_parent_gesture.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; class BlockActionButton extends StatelessWidget { @@ -23,15 +22,17 @@ class BlockActionButton extends StatelessWidget { @override Widget build(BuildContext context) { - Widget child = MouseRegion( - cursor: Platform.isWindows - ? SystemMouseCursors.click - : SystemMouseCursors.grab, - child: IgnoreParentGestureWidget( - onPress: onPointerDown, - child: GestureDetector( - onTap: onTap, - behavior: HitTestBehavior.deferToChild, + return FlowyTooltip( + richMessage: showTooltip ? richMessage : null, + child: FlowyIconButton( + width: 18.0, + hoverColor: Colors.transparent, + iconColorOnHover: Theme.of(context).iconTheme.color, + onPressed: onTap, + icon: MouseRegion( + cursor: Platform.isWindows + ? SystemMouseCursors.click + : SystemMouseCursors.grab, child: FlowySvg( svg, size: const Size.square(18.0), @@ -40,16 +41,5 @@ class BlockActionButton extends StatelessWidget { ), ), ); - - if (showTooltip) { - child = FlowyTooltip( - richMessage: richMessage, - child: child, - ); - } - - return Align( - child: child, - ); } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_list.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_list.dart index fcbd7ea6ca..6323f675cc 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_list.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_list.dart @@ -42,7 +42,7 @@ class BlockActionList extends StatelessWidget { editorState: editorState, blockComponentBuilder: blockComponentBuilder, ), - const HSpace(8.0), + const HSpace(5.0), ], ); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_option_button.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_option_button.dart index 29dabd0da1..4efb1a55b2 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_option_button.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_option_button.dart @@ -50,7 +50,6 @@ class _BlockOptionButtonState extends State { child: BlocBuilder( builder: (context, _) => PopoverActionList( actions: _buildPopoverActions(context), - popoverMutex: PopoverMutex(), animationDuration: Durations.short3, slideDistance: 5, beginScaleFactor: 1.0, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_option_cubit.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_option_cubit.dart index 52b518a718..abed98136d 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_option_cubit.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/block_action_option_cubit.dart @@ -10,7 +10,8 @@ import 'package:appflowy/workspace/application/view/prelude.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor/appflowy_editor.dart' + hide QuoteBlockKeys, quoteNode; import 'package:flutter_bloc/flutter_bloc.dart'; class BlockActionOptionState {} @@ -258,11 +259,13 @@ class BlockActionOptionCubit extends Cubit { emit(BlockActionOptionState()); // Emit a new state to trigger UI update } - Future turnIntoBlock( + static Future turnIntoBlock( String type, - Node node, { + Node node, + EditorState editorState, { int? level, String? currentViewId, + bool keepSelection = false, }) async { final selection = editorState.selection; if (selection == null) { @@ -286,6 +289,8 @@ class BlockActionOptionCubit extends Cubit { type: toType, selectedNodes: selectedNodes, level: level, + editorState: editorState, + afterSelection: keepSelection ? selection : null, )) { return true; } @@ -297,6 +302,7 @@ class BlockActionOptionCubit extends Cubit { selectedNodes: selectedNodes, selection: selection, currentViewId: currentViewId, + editorState: editorState, )) { return true; } @@ -321,9 +327,8 @@ class BlockActionOptionCubit extends Cubit { }, ); - // heading block and callout block should not have children - if ([HeadingBlockKeys.type, CalloutBlockKeys.type, QuoteBlockKeys.type] - .contains(toType)) { + // heading block should not have children + if ([HeadingBlockKeys.type].contains(toType)) { afterNode = afterNode.copyWith(children: []); afterNode = await _handleSubPageNode(afterNode, node); insertedNode.add(afterNode); @@ -344,6 +349,7 @@ class BlockActionOptionCubit extends Cubit { insertedNode, ); transaction.deleteNodes(selectedNodes); + if (keepSelection) transaction.afterSelection = selection; await editorState.apply(transaction); return true; @@ -353,7 +359,7 @@ class BlockActionOptionCubit extends Cubit { /// /// Returns the altered [Node] with the delta as the Views' name. /// - Future _handleSubPageNode(Node node, Node subPageNode) async { + static Future _handleSubPageNode(Node node, Node subPageNode) async { if (subPageNode.type != SubPageBlockKeys.type) { return node; } @@ -370,7 +376,7 @@ class BlockActionOptionCubit extends Cubit { /// Returns the [Delta] from a SubPage [Node], where the /// [Delta] is the views' name. /// - Future _deltaFromSubPageNode(Node node) async { + static Future _deltaFromSubPageNode(Node node) async { if (node.type != SubPageBlockKeys.type) { return null; } @@ -400,9 +406,10 @@ class BlockActionOptionCubit extends Cubit { // - paragraph 1 // - paragraph 2 // when turning "Toggle Heading 1" into toggle heading, the bulleted items will be moved into the toggle heading - Future turnIntoSingleToggleHeading({ + static Future turnIntoSingleToggleHeading({ required String type, required List selectedNodes, + required EditorState editorState, int? level, Delta? delta, Selection? afterSelection, @@ -462,7 +469,7 @@ class BlockActionOptionCubit extends Cubit { blockComponentDelta: newDelta.toJson(), }, children: [ - ...node.children, + ...node.children.map((e) => e.deepCopy()), ...insertedNodes.map((e) => e.deepCopy()), ], ); @@ -492,11 +499,12 @@ class BlockActionOptionCubit extends Cubit { return true; } - Future turnIntoPage({ + static Future turnIntoPage({ required String type, required List selectedNodes, required Selection selection, required String currentViewId, + required EditorState editorState, }) async { if (type != SubPageBlockKeys.type || selectedNodes.isEmpty) { return false; @@ -552,7 +560,7 @@ class BlockActionOptionCubit extends Cubit { return true; } - Future _extractNameFromNodes(List? nodes) async { + static Future _extractNameFromNodes(List? nodes) async { if (nodes == null || nodes.isEmpty) { return ''; } @@ -602,7 +610,7 @@ class BlockActionOptionCubit extends Cubit { return name.substring(0, name.length > 30 ? 30 : name.length); } - List _extractChildViewIds(List nodes) { + static List _extractChildViewIds(List nodes) { final List viewIds = []; for (final node in nodes) { if (node.type == SubPageBlockKeys.type) { diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/drag_to_reorder/draggable_option_button.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/drag_to_reorder/draggable_option_button.dart index 8ab16aea4c..7bc1fba8d9 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/drag_to_reorder/draggable_option_button.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/drag_to_reorder/draggable_option_button.dart @@ -1,6 +1,7 @@ import 'package:appflowy/plugins/document/presentation/editor_notification.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/drag_to_reorder/util.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/drag_to_reorder/visual_drag_area.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flutter/material.dart'; @@ -9,7 +10,6 @@ import 'draggable_option_button_feedback.dart'; import 'option_button.dart'; // this flag is used to disable the tooltip of the block when it is dragged -@visibleForTesting ValueNotifier isDraggingAppFlowyEditorBlock = ValueNotifier(false); class DraggableOptionButton extends StatefulWidget { @@ -82,8 +82,40 @@ class _DraggableOptionButtonState extends State { void _onDragUpdate(DragUpdateDetails details) { isDraggingAppFlowyEditorBlock.value = true; + final offset = details.globalPosition; + widget.editorState.selectionService.renderDropTargetForOffset( - details.globalPosition, + offset, + interceptor: (context, targetNode) { + // if the cursor node is in a columns block or a column block, + // we will return the node's parent instead to support dragging a node to the inside of a columns block or a column block. + final parentColumnNode = targetNode.columnParent; + if (parentColumnNode != null) { + final position = getDragAreaPosition( + context, + targetNode, + offset, + ); + + if (position != null && position.$2 == HorizontalPosition.right) { + return parentColumnNode; + } + + if (position != null && + position.$2 == HorizontalPosition.left && + position.$1 == VerticalPosition.middle) { + return parentColumnNode; + } + } + + // return simple table block if the target node is in a simple table block + final parentSimpleTableNode = targetNode.parentTableNode; + if (parentSimpleTableNode != null) { + return parentSimpleTableNode; + } + + return targetNode; + }, builder: (context, data) { return VisualDragArea( editorState: widget.editorState, @@ -112,7 +144,38 @@ class _DraggableOptionButtonState extends State { final data = widget.editorState.selectionService.getDropTargetRenderData( globalPosition!, + interceptor: (context, targetNode) { + // if the cursor node is in a columns block or a column block, + // we will return the node's parent instead to support dragging a node to the inside of a columns block or a column block. + final parentColumnNode = targetNode.columnParent; + if (parentColumnNode != null) { + final position = getDragAreaPosition( + context, + targetNode, + globalPosition!, + ); + + if (position != null && position.$2 == HorizontalPosition.right) { + return parentColumnNode; + } + + if (position != null && + position.$2 == HorizontalPosition.left && + position.$1 == VerticalPosition.middle) { + return parentColumnNode; + } + } + + // return simple table block if the target node is in a simple table block + final parentSimpleTableNode = targetNode.parentTableNode; + if (parentSimpleTableNode != null) { + return parentSimpleTableNode; + } + + return targetNode; + }, ); + dragToMoveNode( context, node: widget.blockComponentContext.node, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/drag_to_reorder/util.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/drag_to_reorder/util.dart index ddfb6f3a42..ca99491b94 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/drag_to_reorder/util.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/drag_to_reorder/util.dart @@ -1,6 +1,7 @@ -import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy_backend/log.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor/appflowy_editor.dart' + hide QuoteBlockKeys, quoteNode; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -8,6 +9,15 @@ enum HorizontalPosition { left, center, right } enum VerticalPosition { top, middle, bottom } +List nodeTypesThatCanContainChildNode = [ + ParagraphBlockKeys.type, + BulletedListBlockKeys.type, + NumberedListBlockKeys.type, + QuoteBlockKeys.type, + TodoListBlockKeys.type, + ToggleListBlockKeys.type, +]; + Future dragToMoveNode( BuildContext context, { required Node node, @@ -26,6 +36,15 @@ Future dragToMoveNode( return; } + if (shouldIgnoreDragTarget( + editorState: editorState, + dragNode: node, + targetPath: acceptedPath, + )) { + Log.info('Drop ignored: node($node, ${node.path}), path($acceptedPath)'); + return; + } + final position = getDragAreaPosition(context, targetNode, dragOffset); if (position == null) { Log.info('position is null'); @@ -35,17 +54,111 @@ Future dragToMoveNode( final (verticalPosition, horizontalPosition, _) = position; Path newPath = targetNode.path; + // if the horizontal position is right, creating a column block to contain the target node and the drag node + if (horizontalPosition == HorizontalPosition.right) { + // 1. if the targetNode is a column block, it means we should create a column block to contain the node and insert the column node to the target node's parent + // 2. if the targetNode is not a column block, it means we should create a columns block to contain the target node and the drag node + final transaction = editorState.transaction; + final targetNodeParent = targetNode.columnsParent; + + if (targetNodeParent != null) { + final length = targetNodeParent.children.length; + final ratios = targetNodeParent.children + .map( + (e) => + e.attributes[SimpleColumnBlockKeys.ratio]?.toDouble() ?? + 1.0 / length, + ) + .map((e) => e * length / (length + 1)) + .toList(); + + final columnNode = simpleColumnNode( + children: [node.deepCopy()], + ratio: 1.0 / (length + 1), + ); + for (final (index, column) in targetNodeParent.children.indexed) { + transaction.updateNode(column, { + ...column.attributes, + SimpleColumnBlockKeys.ratio: ratios[index], + }); + } + + transaction.insertNode(targetNode.path.next, columnNode); + transaction.deleteNode(node); + } else { + final columnsNode = simpleColumnsNode( + children: [ + simpleColumnNode(children: [targetNode.deepCopy()], ratio: 0.5), + simpleColumnNode(children: [node.deepCopy()], ratio: 0.5), + ], + ); + + transaction.insertNode(newPath, columnsNode); + transaction.deleteNode(targetNode); + transaction.deleteNode(node); + } + + if (transaction.operations.isNotEmpty) { + await editorState.apply(transaction); + } + return; + } else if (horizontalPosition == HorizontalPosition.left && + verticalPosition == VerticalPosition.middle) { + // 1. if the target node is a column block, we should create a column block to contain the node and insert the column node to the target node's parent + // 2. if the target node is not a column block, we should create a columns block to contain the target node and the drag node + final transaction = editorState.transaction; + final targetNodeParent = targetNode.columnsParent; + if (targetNodeParent != null) { + // find the previous sibling node of the target node + final length = targetNodeParent.children.length; + final ratios = targetNodeParent.children + .map( + (e) => + e.attributes[SimpleColumnBlockKeys.ratio]?.toDouble() ?? + 1.0 / length, + ) + .map((e) => e * length / (length + 1)) + .toList(); + final columnNode = simpleColumnNode( + children: [node.deepCopy()], + ratio: 1.0 / (length + 1), + ); + + for (final (index, column) in targetNodeParent.children.indexed) { + transaction.updateNode(column, { + ...column.attributes, + SimpleColumnBlockKeys.ratio: ratios[index], + }); + } + + transaction.insertNode(targetNode.path.previous, columnNode); + transaction.deleteNode(node); + } else { + final columnsNode = simpleColumnsNode( + children: [ + simpleColumnNode(children: [node.deepCopy()], ratio: 0.5), + simpleColumnNode(children: [targetNode.deepCopy()], ratio: 0.5), + ], + ); + + transaction.insertNode(newPath, columnsNode); + transaction.deleteNode(targetNode); + transaction.deleteNode(node); + } + + if (transaction.operations.isNotEmpty) { + await editorState.apply(transaction); + } + return; + } // Determine the new path based on drop position // For VerticalPosition.top, we keep the target node's path if (verticalPosition == VerticalPosition.bottom) { if (horizontalPosition == HorizontalPosition.left) { newPath = newPath.next; - final node = editorState.document.nodeAtPath(newPath); - if (node == null) { - // if node is null, it means the node is the last one of the document. - newPath = targetNode.path; - } - } else { + } else if (horizontalPosition == HorizontalPosition.center && + nodeTypesThatCanContainChildNode.contains(targetNode.type)) { + // check if the target node can contain a child node newPath = newPath.child(0); } } @@ -103,18 +216,32 @@ Future dragToMoveNode( HorizontalPosition horizontalPosition = HorizontalPosition.left; VerticalPosition verticalPosition; - // Horizontal position + // | ----------------------------- block ----------------------------- | + // | 1. -- 88px --| 2. ---------------------------- | 3. ---- 1/5 ---- | + // 1. drag the node under the block as a sibling node + // 2. drag the node inside the block as a child node + // 3. create a column block to contain the node and the drag node + + // Horizontal position, please refer to the diagram above + // 88px is a hardcoded value, it can be changed based on the project's design if (dragOffset.dx < globalBlockRect.left + 88) { horizontalPosition = HorizontalPosition.left; - } else if (indentableBlockTypes.contains(dragTargetNode.type)) { - // For indentable blocks, it means the block can contain a child block. - // ignore the middle here, it's not used in this example + } else if (dragOffset.dx > globalBlockRect.right * 4.0 / 5.0) { horizontalPosition = HorizontalPosition.right; + } else if (nodeTypesThatCanContainChildNode.contains(dragTargetNode.type)) { + horizontalPosition = HorizontalPosition.center; } + // | ----------------------------------------------------------------- | <- if the drag position is in this area, the vertical position is top + // | ----------------------------- block ----------------------------- | <- if the drag position is in this area, the vertical position is middle + // | ----------------------------------------------------------------- | <- if the drag position is in this area, the vertical position is bottom + // Vertical position - if (dragOffset.dy < globalBlockRect.top + globalBlockRect.height / 2) { + final heightThird = globalBlockRect.height / 3; + if (dragOffset.dy < globalBlockRect.top + heightThird) { verticalPosition = VerticalPosition.top; + } else if (dragOffset.dy < globalBlockRect.top + heightThird * 2) { + verticalPosition = VerticalPosition.middle; } else { verticalPosition = VerticalPosition.bottom; } @@ -140,7 +267,9 @@ bool shouldIgnoreDragTarget({ } final targetNode = editorState.getNodeAtPath(targetPath); - if (targetNode != null && targetNode.isInTable) { + if (targetNode != null && + targetNode.isInTable && + targetNode.type != SimpleTableBlockKeys.type) { return true; } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/drag_to_reorder/visual_drag_area.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/drag_to_reorder/visual_drag_area.dart index f3499a9ea5..2be8710a8a 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/drag_to_reorder/visual_drag_area.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/drag_to_reorder/visual_drag_area.dart @@ -16,6 +16,7 @@ class VisualDragArea extends StatelessWidget { final DragAreaBuilderData data; final Node dragNode; final EditorState editorState; + @override Widget build(BuildContext context) { final targetNode = data.targetNode; @@ -57,7 +58,36 @@ class VisualDragArea extends StatelessWidget { color: Theme.of(context).colorScheme.primary, ); + // if the horizontal position is right, we need to show the indicator on the right side of the target node + // which represent moving the target node and drag node inside the column block. + if (horizontalPosition == HorizontalPosition.left && + verticalPosition == VerticalPosition.middle) { + return Positioned( + top: globalBlockRect.top, + height: globalBlockRect.height, + left: globalBlockRect.left + indicatorWidth, + child: Container( + width: 2, + color: Theme.of(context).colorScheme.primary, + ), + ); + } + if (horizontalPosition == HorizontalPosition.right) { + return Positioned( + top: globalBlockRect.top, + height: globalBlockRect.height, + left: globalBlockRect.right - 2, + child: Container( + width: 2, + color: Theme.of(context).colorScheme.primary, + ), + ); + } + + // If the horizontal position is center, we need to show two indicators + //which represent moving the block as the child of the target node. + if (horizontalPosition == HorizontalPosition.center) { const breakWidth = 22.0; const padding = 8.0; child = Row( diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/mobile_block_action_buttons.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/mobile_block_action_buttons.dart index 539dd313b1..a04190f8af 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/mobile_block_action_buttons.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/mobile_block_action_buttons.dart @@ -110,7 +110,6 @@ class MobileBlockActionButtons extends StatelessWidget { ), ); break; - default: } if (transaction.operations.isNotEmpty) { diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option/option_actions.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option/option_actions.dart index 14ec6773a6..571cb4baa0 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option/option_actions.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option/option_actions.dart @@ -1,7 +1,8 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor/appflowy_editor.dart' + hide QuoteBlockKeys, quoteNode; import 'package:easy_localization/easy_localization.dart' hide TextDirection; import 'package:easy_localization/easy_localization.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option/turn_into_option_action.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option/turn_into_option_action.dart index ac3774a511..c927fcf85f 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option/turn_into_option_action.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option/turn_into_option_action.dart @@ -2,10 +2,10 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_option_cubit.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; -import 'package:appflowy/startup/startup.dart'; -import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/toolbar_item/text_suggestions_toolbar_item.dart'; import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor/appflowy_editor.dart' + hide QuoteBlockKeys, quoteNode; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; @@ -148,213 +148,134 @@ class TurnIntoOptionMenu extends StatelessWidget { @override Widget build(BuildContext context) { + if (hasNonSupportedTypes) { + return buildItem( + pateItem, + textSuggestionItem, + context.read().editorState, + ); + } + + return _buildTurnIntoOptions(context, node); + } + + Widget _buildTurnIntoOptions(BuildContext context, Node node) { + final editorState = context.read().editorState; + SuggestionItem currentSuggestionItem = textSuggestionItem; + final List suggestionItems = suggestions.sublist(0, 4); + final List turnIntoItems = + suggestions.sublist(4, suggestions.length); + final textColor = Color(0xff99A1A8); + + void refreshSuggestions() { + final selection = editorState.selection; + if (selection == null || !selection.isSingle) return; + final node = editorState.getNodeAtPath(selection.start.path); + if (node == null || node.delta == null) return; + final nodeType = node.type; + SuggestionType? suggestionType; + if (nodeType == HeadingBlockKeys.type) { + final level = node.attributes[HeadingBlockKeys.level] ?? 1; + if (level == 1) { + suggestionType = SuggestionType.h1; + } else if (level == 2) { + suggestionType = SuggestionType.h2; + } else if (level == 3) { + suggestionType = SuggestionType.h3; + } + } else if (nodeType == ToggleListBlockKeys.type) { + final level = node.attributes[ToggleListBlockKeys.level]; + if (level == null) { + suggestionType = SuggestionType.toggle; + } else if (level == 1) { + suggestionType = SuggestionType.toggleH1; + } else if (level == 2) { + suggestionType = SuggestionType.toggleH2; + } else if (level == 3) { + suggestionType = SuggestionType.toggleH3; + } + } else { + suggestionType = nodeType2SuggestionType[nodeType]; + } + if (suggestionType == null) return; + suggestionItems.clear(); + turnIntoItems.clear(); + for (final item in suggestions) { + if (item.type.group == suggestionType.group && + item.type != suggestionType) { + suggestionItems.add(item); + } else { + turnIntoItems.add(item); + } + } + currentSuggestionItem = + suggestions.where((item) => item.type == suggestionType).first; + } + + refreshSuggestions(); + return Column( mainAxisSize: MainAxisSize.min, - children: _buildTurnIntoOptions(context, node), + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildSubTitle( + LocaleKeys.document_toolbar_suggestions.tr(), + textColor, + ), + ...List.generate(suggestionItems.length, (index) { + return buildItem( + suggestionItems[index], + currentSuggestionItem, + editorState, + ); + }), + buildSubTitle(LocaleKeys.document_toolbar_turnInto.tr(), textColor), + ...List.generate(turnIntoItems.length, (index) { + return buildItem( + turnIntoItems[index], + currentSuggestionItem, + editorState, + ); + }), + ], ); } - List _buildTurnIntoOptions(BuildContext context, Node node) { - final children = []; + Widget buildSubTitle(String text, Color color) { + return Container( + height: 32, + margin: EdgeInsets.symmetric(horizontal: 8), + child: Align( + alignment: Alignment.centerLeft, + child: FlowyText.semibold( + text, + color: color, + figmaLineHeight: 16, + ), + ), + ); + } - if (hasNonSupportedTypes) { - return children - ..add( - _TurnInfoButton( - type: SubPageBlockKeys.type, - node: node, - ), - ); - } - - for (final type in EditorOptionActionType.turnInto.supportTypes) { - if (type == ToggleListBlockKeys.type) { - // toggle list block and toggle heading block are the same type, - // but they have different attributes. - - // toggle list block - children.add( - _TurnInfoButton( - type: type, - node: node, - ), - ); - - // toggle heading block - for (final i in [1, 2, 3]) { - children.add( - _TurnInfoButton( - type: type, - node: node, - level: i, - ), - ); - } - } else if (type != HeadingBlockKeys.type) { - children.add( - _TurnInfoButton( - type: type, - node: node, - ), - ); - } else { - for (final i in [1, 2, 3]) { - children.add( - _TurnInfoButton( - type: type, - node: node, - level: i, - ), - ); - } - } - } - - return children; - } -} - -class _TurnInfoButton extends StatelessWidget { - const _TurnInfoButton({ - required this.type, - required this.node, - this.level, - }); - - final String type; - final Node node; - final int? level; - - @override - Widget build(BuildContext context) { - final name = _buildLocalization(type, level: level); - final leftIcon = _buildLeftIcon(type, level: level); - final rightIcon = _buildRightIcon(type, node, level: level); - - return HoverButton( - name: name, - leftIcon: FlowySvg(leftIcon), - rightIcon: rightIcon, - itemHeight: ActionListSizes.itemHeight, - onTap: () => context.read().turnIntoBlock( - type, - node, - level: level, - currentViewId: getIt().latestOpenView?.id, - ), - ); - } - - Widget? _buildRightIcon(String type, Node node, {int? level}) { - if (type != node.type) { - return null; - } - - if (node.type == HeadingBlockKeys.type) { - final nodeLevel = node.attributes[HeadingBlockKeys.level] ?? 1; - if (level != nodeLevel) { - return null; - } - } - - if (node.type == ToggleListBlockKeys.type) { - final nodeLevel = node.attributes[ToggleListBlockKeys.level]; - if (level != nodeLevel) { - return null; - } - } - - return const FlowySvg( - FlowySvgs.workspace_selected_s, - blendMode: null, - ); - } - - FlowySvgData _buildLeftIcon(String type, {int? level}) { - if (type == ParagraphBlockKeys.type) { - return FlowySvgs.slash_menu_icon_text_s; - } else if (type == HeadingBlockKeys.type) { - switch (level) { - case 1: - return FlowySvgs.slash_menu_icon_h1_s; - case 2: - return FlowySvgs.slash_menu_icon_h2_s; - case 3: - return FlowySvgs.slash_menu_icon_h3_s; - default: - return FlowySvgs.slash_menu_icon_text_s; - } - } else if (type == QuoteBlockKeys.type) { - return FlowySvgs.slash_menu_icon_quote_s; - } else if (type == BulletedListBlockKeys.type) { - return FlowySvgs.slash_menu_icon_bulleted_list_s; - } else if (type == NumberedListBlockKeys.type) { - return FlowySvgs.slash_menu_icon_numbered_list_s; - } else if (type == TodoListBlockKeys.type) { - return FlowySvgs.slash_menu_icon_checkbox_s; - } else if (type == CalloutBlockKeys.type) { - return FlowySvgs.slash_menu_icon_callout_s; - } else if (type == SubPageBlockKeys.type) { - return FlowySvgs.icon_document_s; - } else if (type == ToggleListBlockKeys.type) { - switch (level) { - case 1: - return FlowySvgs.toggle_heading1_s; - case 2: - return FlowySvgs.toggle_heading2_s; - case 3: - return FlowySvgs.toggle_heading3_s; - default: - return FlowySvgs.slash_menu_icon_toggle_s; - } - } - - throw UnimplementedError('Unsupported block type: $type'); - } - - String _buildLocalization( - String type, { - int? level, - }) { - switch (type) { - case ParagraphBlockKeys.type: - return LocaleKeys.document_slashMenu_name_text.tr(); - case HeadingBlockKeys.type: - switch (level) { - case 1: - return LocaleKeys.document_slashMenu_name_heading1.tr(); - case 2: - return LocaleKeys.document_slashMenu_name_heading2.tr(); - case 3: - return LocaleKeys.document_slashMenu_name_heading3.tr(); - default: - return LocaleKeys.document_slashMenu_name_text.tr(); - } - case QuoteBlockKeys.type: - return LocaleKeys.document_slashMenu_name_quote.tr(); - case BulletedListBlockKeys.type: - return LocaleKeys.document_slashMenu_name_bulletedList.tr(); - case NumberedListBlockKeys.type: - return LocaleKeys.document_slashMenu_name_numberedList.tr(); - case TodoListBlockKeys.type: - return LocaleKeys.document_slashMenu_name_todoList.tr(); - case CalloutBlockKeys.type: - return LocaleKeys.document_slashMenu_name_callout.tr(); - case SubPageBlockKeys.type: - return LocaleKeys.editor_page.tr(); - case ToggleListBlockKeys.type: - switch (level) { - case 1: - return LocaleKeys.document_slashMenu_name_toggleHeading1.tr(); - case 2: - return LocaleKeys.document_slashMenu_name_toggleHeading2.tr(); - case 3: - return LocaleKeys.document_slashMenu_name_toggleHeading3.tr(); - default: - return LocaleKeys.document_slashMenu_name_toggleList.tr(); - } - } - - throw UnimplementedError('Unsupported block type: $type'); + Widget buildItem( + SuggestionItem item, + SuggestionItem currentSuggestionItem, + EditorState state, + ) { + final isSelected = item.type == currentSuggestionItem.type; + return SizedBox( + height: 36, + child: FlowyButton( + leftIconSize: const Size.square(20), + leftIcon: FlowySvg(item.svg), + iconPadding: 12, + text: FlowyText( + item.title, + fontWeight: FontWeight.w400, + figmaLineHeight: 20, + ), + rightIcon: isSelected ? FlowySvg(FlowySvgs.toolbar_check_m) : null, + onTap: () => item.onTap.call(state, false), + ), + ); } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/ai_writer_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/ai_writer_block_component.dart new file mode 100644 index 0000000000..f78f7d35fd --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/ai_writer_block_component.dart @@ -0,0 +1,601 @@ +import 'package:appflowy/ai/ai.dart'; +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/ai_chat/presentation/message/ai_markdown_text.dart'; +import 'package:appflowy/plugins/document/application/document_bloc.dart'; +import 'package:appflowy/util/theme_extension.dart'; +import 'package:appflowy/workspace/application/view/view_bloc.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:universal_platform/universal_platform.dart'; + +import 'operations/ai_writer_cubit.dart'; +import 'operations/ai_writer_entities.dart'; +import 'operations/ai_writer_node_extension.dart'; +import 'widgets/ai_writer_suggestion_actions.dart'; +import 'widgets/ai_writer_prompt_input_more_button.dart'; + +class AiWriterBlockKeys { + const AiWriterBlockKeys._(); + + static const String type = 'ai_writer'; + + static const String isInitialized = 'is_initialized'; + static const String selection = 'selection'; + static const String command = 'command'; + + /// Sample usage: + /// + /// `attributes: { + /// 'ai_writer_delta_suggestion': 'original' + /// }` + static const String suggestion = 'ai_writer_delta_suggestion'; + static const String suggestionOriginal = 'original'; + static const String suggestionReplacement = 'replacement'; +} + +Node aiWriterNode({ + required Selection? selection, + required AiWriterCommand command, +}) { + return Node( + type: AiWriterBlockKeys.type, + attributes: { + AiWriterBlockKeys.isInitialized: false, + AiWriterBlockKeys.selection: selection?.toJson(), + AiWriterBlockKeys.command: command.index, + }, + ); +} + +class AIWriterBlockComponentBuilder extends BlockComponentBuilder { + AIWriterBlockComponentBuilder(); + + @override + BlockComponentWidget build(BlockComponentContext blockComponentContext) { + final node = blockComponentContext.node; + return AiWriterBlockComponent( + key: node.key, + node: node, + showActions: showActions(node), + actionBuilder: (context, state) => actionBuilder( + blockComponentContext, + state, + ), + actionTrailingBuilder: (context, state) => actionTrailingBuilder( + blockComponentContext, + state, + ), + ); + } + + @override + BlockComponentValidate get validate => (node) => + node.children.isEmpty && + node.attributes[AiWriterBlockKeys.isInitialized] is bool && + node.attributes[AiWriterBlockKeys.selection] is Map? && + node.attributes[AiWriterBlockKeys.command] is int; +} + +class AiWriterBlockComponent extends BlockComponentStatefulWidget { + const AiWriterBlockComponent({ + super.key, + required super.node, + super.showActions, + super.actionBuilder, + super.actionTrailingBuilder, + super.configuration = const BlockComponentConfiguration(), + }); + + @override + State createState() => _AIWriterBlockComponentState(); +} + +class _AIWriterBlockComponentState extends State { + final textController = TextEditingController(); + final overlayController = OverlayPortalController(); + final layerLink = LayerLink(); + final focusNode = FocusNode(); + + late final editorState = context.read(); + + @override + void initState() { + super.initState(); + + WidgetsBinding.instance.addPostFrameCallback((_) { + overlayController.show(); + context.read().register(widget.node); + }); + } + + @override + void dispose() { + textController.dispose(); + focusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (UniversalPlatform.isMobile) { + return const SizedBox.shrink(); + } + + final documentId = context.read()?.documentId; + + return BlocProvider( + create: (_) => AIPromptInputBloc( + predefinedFormat: null, + objectId: documentId ?? editorState.document.root.id, + ), + child: LayoutBuilder( + builder: (context, constraints) { + return OverlayPortal( + controller: overlayController, + overlayChildBuilder: (context) { + return Center( + child: CompositedTransformFollower( + link: layerLink, + showWhenUnlinked: false, + child: Container( + padding: const EdgeInsets.only( + left: 40.0, + bottom: 16.0, + ), + width: constraints.maxWidth, + child: Focus( + focusNode: focusNode, + child: OverlayContent( + editorState: editorState, + node: widget.node, + textController: textController, + ), + ), + ), + ), + ); + }, + child: CompositedTransformTarget( + link: layerLink, + child: BlocBuilder( + builder: (context, state) { + return SizedBox( + width: double.infinity, + height: 1.0, + ); + }, + ), + ), + ); + }, + ), + ); + } +} + +class OverlayContent extends StatefulWidget { + const OverlayContent({ + super.key, + required this.editorState, + required this.node, + required this.textController, + }); + + final EditorState editorState; + final Node node; + final TextEditingController textController; + + @override + State createState() => _OverlayContentState(); +} + +class _OverlayContentState extends State { + final showCommandsToggle = ValueNotifier(false); + + @override + void dispose() { + showCommandsToggle.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is IdleAiWriterState || + state is DocumentContentEmptyAiWriterState) { + return const SizedBox.shrink(); + } + + final command = (state as RegisteredAiWriter).command; + + final selection = widget.node.aiWriterSelection; + final hasSelection = selection != null && !selection.isCollapsed; + + final markdownText = switch (state) { + final ReadyAiWriterState ready => ready.markdownText, + final GeneratingAiWriterState generating => generating.markdownText, + _ => '', + }; + + final showSuggestedActions = + state is ReadyAiWriterState && !state.isFirstRun; + final isInitialReadyState = + state is ReadyAiWriterState && state.isFirstRun; + final showSuggestedActionsPopup = + showSuggestedActions && markdownText.isEmpty || + (markdownText.isNotEmpty && command != AiWriterCommand.explain); + final showSuggestedActionsWithin = showSuggestedActions && + markdownText.isNotEmpty && + command == AiWriterCommand.explain; + + final borderColor = Theme.of(context).isLightMode + ? Color(0x1F1F2329) + : Color(0xFF505469); + + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (showSuggestedActionsPopup) ...[ + Container( + padding: EdgeInsets.all(4.0), + decoration: _getModalDecoration( + context, + color: Theme.of(context).colorScheme.surface, + borderRadius: BorderRadius.all(Radius.circular(8.0)), + borderColor: borderColor, + ), + child: SuggestionActionBar( + currentCommand: command, + hasSelection: hasSelection, + onTap: (action) { + _onSelectSuggestionAction(context, action); + }, + ), + ), + const VSpace(4.0 + 1.0), + ], + Container( + decoration: _getModalDecoration( + context, + color: null, + borderColor: borderColor, + borderRadius: BorderRadius.all(Radius.circular(12.0)), + ), + constraints: BoxConstraints(maxHeight: 400), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (markdownText.isNotEmpty) ...[ + Flexible( + child: DecoratedBox( + decoration: _secondaryContentDecoration(context), + child: SecondaryContentArea( + markdownText: markdownText, + onSelectSuggestionAction: (action) { + _onSelectSuggestionAction(context, action); + }, + command: command, + showSuggestionActions: showSuggestedActionsWithin, + hasSelection: hasSelection, + ), + ), + ), + Divider(height: 1.0), + ], + DecoratedBox( + decoration: markdownText.isNotEmpty + ? _mainContentDecoration(context) + : _getSingleChildDeocoration(context), + child: MainContentArea( + textController: widget.textController, + isDocumentEmpty: _isDocumentEmpty(), + isInitialReadyState: isInitialReadyState, + showCommandsToggle: showCommandsToggle, + ), + ), + ], + ), + ), + ValueListenableBuilder( + valueListenable: showCommandsToggle, + builder: (context, value, child) { + if (!value || !isInitialReadyState) { + return const SizedBox.shrink(); + } + return Align( + alignment: AlignmentDirectional.centerEnd, + child: MoreAiWriterCommands( + hasSelection: hasSelection, + editorState: widget.editorState, + onSelectCommand: (command) { + final state = context.read().state; + final showPredefinedFormats = state.showPredefinedFormats; + final predefinedFormat = state.predefinedFormat; + final text = widget.textController.text; + + context.read().runCommand( + command, + text, + showPredefinedFormats ? predefinedFormat : null, + ); + }, + ), + ); + }, + ), + ], + ); + }, + ); + } + + BoxDecoration _getModalDecoration( + BuildContext context, { + required Color? color, + required Color borderColor, + required BorderRadius borderRadius, + }) { + return BoxDecoration( + color: color, + border: Border.all( + color: borderColor, + strokeAlign: BorderSide.strokeAlignOutside, + ), + borderRadius: borderRadius, + boxShadow: Theme.of(context).isLightMode + ? ShadowConstants.lightSmall + : ShadowConstants.darkSmall, + ); + } + + BoxDecoration _getSingleChildDeocoration(BuildContext context) { + return BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: BorderRadius.all(Radius.circular(12.0)), + ); + } + + BoxDecoration _secondaryContentDecoration(BuildContext context) { + return BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainerHighest, + borderRadius: BorderRadius.vertical(top: Radius.circular(12.0)), + ); + } + + BoxDecoration _mainContentDecoration(BuildContext context) { + return BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: BorderRadius.vertical(bottom: Radius.circular(12.0)), + ); + } + + void _onSelectSuggestionAction( + BuildContext context, + SuggestionAction action, + ) { + final predefinedFormat = + context.read().state.predefinedFormat; + context.read().runResponseAction( + action, + predefinedFormat, + ); + } + + bool _isDocumentEmpty() { + if (widget.editorState.isEmptyForContinueWriting()) { + final documentContext = widget.editorState.document.root.context; + if (documentContext == null) { + return true; + } + final view = documentContext.read().state.view; + if (view.name.isEmpty) { + return true; + } + } + return false; + } +} + +class SecondaryContentArea extends StatelessWidget { + const SecondaryContentArea({ + super.key, + required this.command, + required this.markdownText, + required this.showSuggestionActions, + required this.hasSelection, + required this.onSelectSuggestionAction, + }); + + final AiWriterCommand command; + final String markdownText; + final bool showSuggestionActions; + final bool hasSelection; + final void Function(SuggestionAction) onSelectSuggestionAction; + + @override + Widget build(BuildContext context) { + return SizedBox( + width: double.infinity, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const VSpace(8.0), + Container( + height: 24.0, + padding: EdgeInsets.symmetric(horizontal: 14.0), + alignment: AlignmentDirectional.centerStart, + child: FlowyText( + command.i18n, + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF666D76), + ), + ), + const VSpace(4.0), + Flexible( + child: SingleChildScrollView( + physics: ClampingScrollPhysics(), + padding: EdgeInsets.symmetric(horizontal: 14.0), + child: AIMarkdownText( + markdown: markdownText, + ), + ), + ), + if (showSuggestionActions) ...[ + const VSpace(4.0), + Padding( + padding: EdgeInsets.symmetric(horizontal: 8.0), + child: SuggestionActionBar( + currentCommand: command, + hasSelection: hasSelection, + onTap: onSelectSuggestionAction, + ), + ), + ], + const VSpace(8.0), + ], + ), + ); + } +} + +class MainContentArea extends StatelessWidget { + const MainContentArea({ + super.key, + required this.textController, + required this.isInitialReadyState, + required this.isDocumentEmpty, + required this.showCommandsToggle, + }); + + final TextEditingController textController; + final bool isInitialReadyState; + final bool isDocumentEmpty; + final ValueNotifier showCommandsToggle; + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + final cubit = context.read(); + + if (state is ReadyAiWriterState) { + return DesktopPromptInput( + isStreaming: false, + hideDecoration: true, + hideFormats: [ + AiWriterCommand.fixSpellingAndGrammar, + AiWriterCommand.improveWriting, + AiWriterCommand.makeLonger, + AiWriterCommand.makeShorter, + ].contains(state.command), + textController: textController, + onSubmitted: (message, format, _) { + cubit.runCommand(state.command, message, format); + }, + onStopStreaming: () => cubit.stopStream(), + selectedSourcesNotifier: cubit.selectedSourcesNotifier, + onUpdateSelectedSources: (sources) { + cubit.selectedSourcesNotifier.value = [ + ...sources, + ]; + }, + extraBottomActionButton: isInitialReadyState + ? ValueListenableBuilder( + valueListenable: showCommandsToggle, + builder: (context, value, _) { + return AiWriterPromptMoreButton( + isEnabled: !isDocumentEmpty, + isSelected: value, + onTap: () => showCommandsToggle.value = !value, + ); + }, + ) + : null, + ); + } + if (state is GeneratingAiWriterState) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + const HSpace(6.0), + Expanded( + child: AILoadingIndicator( + text: state.command == AiWriterCommand.explain + ? LocaleKeys.ai_analyzing.tr() + : LocaleKeys.ai_editing.tr(), + ), + ), + const HSpace(8.0), + PromptInputSendButton( + state: SendButtonState.streaming, + onSendPressed: () {}, + onStopStreaming: () => cubit.stopStream(), + ), + ], + ), + ); + } + if (state is ErrorAiWriterState) { + return Padding( + padding: EdgeInsets.all(8.0), + child: Row( + children: [ + const FlowySvg( + FlowySvgs.toast_error_filled_s, + blendMode: null, + ), + const HSpace(8.0), + Expanded( + child: FlowyText( + state.error.message, + maxLines: null, + ), + ), + const HSpace(8.0), + FlowyIconButton( + width: 32, + hoverColor: Colors.transparent, + icon: FlowySvg( + FlowySvgs.toast_close_s, + size: Size.square(20), + ), + onPressed: () => cubit.exit(), + ), + ], + ), + ); + } + if (state is LocalAIStreamingAiWriterState) { + final text = switch (state.state) { + LocalAIStreamingState.notReady => + LocaleKeys.settings_aiPage_keys_localAINotReadyRetryLater.tr(), + LocalAIStreamingState.disabled => + LocaleKeys.settings_aiPage_keys_localAIDisabled.tr(), + }; + return Padding( + padding: EdgeInsets.all(8.0), + child: Row( + children: [ + const HSpace(8.0), + Opacity( + opacity: 0.5, + child: FlowyText(text), + ), + ], + ), + ); + } + return const SizedBox.shrink(); + }, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/ai_writer_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/ai_writer_toolbar_item.dart new file mode 100644 index 0000000000..70d627d327 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/ai_writer_toolbar_item.dart @@ -0,0 +1,249 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; +import 'package:appflowy/plugins/document/presentation/editor_style.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; + +import 'operations/ai_writer_entities.dart'; + +const _improveWritingToolbarItemId = 'appflowy.editor.ai_improve_writing'; +const _aiWriterToolbarItemId = 'appflowy.editor.ai_writer'; + +final ToolbarItem improveWritingItem = ToolbarItem( + id: _improveWritingToolbarItemId, + group: 0, + isActive: onlyShowInTextTypeAndExcludeTable, + builder: (context, editorState, _, __, tooltipBuilder) => + ImproveWritingButton( + editorState: editorState, + tooltipBuilder: tooltipBuilder, + ), +); + +final ToolbarItem aiWriterItem = ToolbarItem( + id: _aiWriterToolbarItemId, + group: 0, + isActive: onlyShowInTextTypeAndExcludeTable, + builder: (context, editorState, _, __, tooltipBuilder) => + AiWriterToolbarActionList( + editorState: editorState, + tooltipBuilder: tooltipBuilder, + ), +); + +class AiWriterToolbarActionList extends StatefulWidget { + const AiWriterToolbarActionList({ + super.key, + required this.editorState, + this.tooltipBuilder, + }); + + final EditorState editorState; + final ToolbarTooltipBuilder? tooltipBuilder; + + @override + State createState() => + _AiWriterToolbarActionListState(); +} + +class _AiWriterToolbarActionListState extends State { + final popoverController = PopoverController(); + bool isSelected = false; + + @override + Widget build(BuildContext context) { + return AppFlowyPopover( + controller: popoverController, + direction: PopoverDirection.bottomWithLeftAligned, + offset: const Offset(0, 2.0), + onOpen: () => keepEditorFocusNotifier.increase(), + onClose: () { + setState(() { + isSelected = false; + }); + keepEditorFocusNotifier.decrease(); + }, + popupBuilder: (context) => buildPopoverContent(), + child: buildChild(context), + ); + } + + Widget buildPopoverContent() { + return SeparatedColumn( + mainAxisSize: MainAxisSize.min, + separatorBuilder: () => const VSpace(4.0), + children: [ + actionWrapper(AiWriterCommand.improveWriting), + actionWrapper(AiWriterCommand.userQuestion), + actionWrapper(AiWriterCommand.fixSpellingAndGrammar), + // actionWrapper(AiWriterCommand.summarize), + actionWrapper(AiWriterCommand.explain), + divider(), + actionWrapper(AiWriterCommand.makeLonger), + actionWrapper(AiWriterCommand.makeShorter), + ], + ); + } + + Widget actionWrapper(AiWriterCommand command) { + return SizedBox( + height: 36, + child: FlowyButton( + leftIconSize: const Size.square(20), + leftIcon: FlowySvg(command.icon), + iconPadding: 12, + text: FlowyText( + command.i18n, + figmaLineHeight: 20, + ), + onTap: () { + popoverController.close(); + _insertAiNode(widget.editorState, command); + }, + ), + ); + } + + Widget divider() { + return const Divider( + thickness: 1.0, + height: 1.0, + ); + } + + Widget buildChild(BuildContext context) { + final theme = AppFlowyTheme.of(context), iconScheme = theme.iconColorScheme; + final child = FlowyIconButton( + width: 48, + height: 32, + isSelected: isSelected, + hoverColor: EditorStyleCustomizer.toolbarHoverColor(context), + icon: Row( + mainAxisSize: MainAxisSize.min, + children: [ + FlowySvg( + FlowySvgs.toolbar_ai_writer_m, + size: Size.square(20), + color: iconScheme.primary, + ), + HSpace(4), + FlowySvg( + FlowySvgs.toolbar_arrow_down_m, + size: Size(12, 20), + color: iconScheme.primary, + ), + ], + ), + onPressed: () { + if (_isAIWriterEnabled(widget.editorState)) { + keepEditorFocusNotifier.increase(); + popoverController.show(); + setState(() { + isSelected = true; + }); + } else { + showToastNotification( + message: LocaleKeys.document_plugins_appflowyAIEditDisabled.tr(), + ); + } + }, + ); + + return widget.tooltipBuilder?.call( + context, + _aiWriterToolbarItemId, + _isAIWriterEnabled(widget.editorState) + ? LocaleKeys.document_plugins_aiWriter_userQuestion.tr() + : LocaleKeys.document_plugins_appflowyAIEditDisabled.tr(), + child, + ) ?? + child; + } +} + +class ImproveWritingButton extends StatelessWidget { + const ImproveWritingButton({ + super.key, + required this.editorState, + this.tooltipBuilder, + }); + + final EditorState editorState; + final ToolbarTooltipBuilder? tooltipBuilder; + + @override + Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); + final child = FlowyIconButton( + width: 36, + height: 32, + hoverColor: EditorStyleCustomizer.toolbarHoverColor(context), + icon: FlowySvg( + FlowySvgs.toolbar_ai_improve_writing_m, + size: Size.square(20.0), + color: theme.iconColorScheme.primary, + ), + onPressed: () { + if (_isAIWriterEnabled(editorState)) { + keepEditorFocusNotifier.increase(); + _insertAiNode(editorState, AiWriterCommand.improveWriting); + } else { + showToastNotification( + message: LocaleKeys.document_plugins_appflowyAIEditDisabled.tr(), + ); + } + }, + ); + + return tooltipBuilder?.call( + context, + _aiWriterToolbarItemId, + _isAIWriterEnabled(editorState) + ? LocaleKeys.document_plugins_aiWriter_improveWriting.tr() + : LocaleKeys.document_plugins_appflowyAIEditDisabled.tr(), + child, + ) ?? + child; + } +} + +void _insertAiNode(EditorState editorState, AiWriterCommand command) async { + final selection = editorState.selection?.normalized; + if (selection == null) { + return; + } + + final transaction = editorState.transaction + ..insertNode( + selection.end.path.next, + aiWriterNode( + selection: selection, + command: command, + ), + ) + ..selectionExtraInfo = {selectionExtraInfoDisableToolbar: true}; + + await editorState.apply( + transaction, + options: const ApplyOptions( + recordUndo: false, + inMemoryUpdate: true, + ), + withUpdateSelection: false, + ); +} + +bool _isAIWriterEnabled(EditorState editorState) { + return true; +} + +bool onlyShowInTextTypeAndExcludeTable( + EditorState editorState, +) { + return onlyShowInTextType(editorState) && notShowInTable(editorState); +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_block_operations.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_block_operations.dart new file mode 100644 index 0000000000..1b495a5b23 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_block_operations.dart @@ -0,0 +1,258 @@ +import 'dart:async'; + +import 'package:appflowy/shared/markdown_to_document.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; + +import '../ai_writer_block_component.dart'; +import 'ai_writer_entities.dart'; +import 'ai_writer_node_extension.dart'; + +Future setAiWriterNodeIsInitialized( + EditorState editorState, + Node node, +) async { + final transaction = editorState.transaction + ..updateNode(node, { + AiWriterBlockKeys.isInitialized: true, + }); + + await editorState.apply( + transaction, + options: const ApplyOptions( + recordUndo: false, + inMemoryUpdate: true, + ), + withUpdateSelection: false, + ); + + final selection = node.aiWriterSelection; + if (selection != null && !selection.isCollapsed) { + unawaited( + editorState.updateSelectionWithReason( + selection, + extraInfo: {selectionExtraInfoDisableToolbar: true}, + ), + ); + } +} + +Future removeAiWriterNode( + EditorState editorState, + Node node, +) async { + final transaction = editorState.transaction..deleteNode(node); + await editorState.apply( + transaction, + options: const ApplyOptions(recordUndo: false), + withUpdateSelection: false, + ); +} + +Future formatSelection( + EditorState editorState, + Selection selection, + ApplySuggestionFormatType formatType, +) async { + final nodes = editorState.getNodesInSelection(selection).toList(); + if (nodes.isEmpty) { + return; + } + final transaction = editorState.transaction; + + if (nodes.length == 1) { + final node = nodes.removeAt(0); + if (node.delta != null) { + final delta = Delta() + ..retain(selection.start.offset) + ..retain( + selection.length, + attributes: formatType.attributes, + ); + transaction.addDeltaToComposeMap(node, delta); + } + } else { + final firstNode = nodes.removeAt(0); + final lastNode = nodes.removeLast(); + + if (firstNode.delta != null) { + final text = firstNode.delta!.toPlainText(); + final remainderLength = text.length - selection.start.offset; + final delta = Delta() + ..retain(selection.start.offset) + ..retain(remainderLength, attributes: formatType.attributes); + transaction.addDeltaToComposeMap(firstNode, delta); + } + + if (lastNode.delta != null) { + final delta = Delta() + ..retain(selection.end.offset, attributes: formatType.attributes); + transaction.addDeltaToComposeMap(lastNode, delta); + } + + for (final node in nodes) { + if (node.delta == null) { + continue; + } + final length = node.delta!.length; + if (length != 0) { + final delta = Delta() + ..retain(length, attributes: formatType.attributes); + transaction.addDeltaToComposeMap(node, delta); + } + } + } + + transaction.compose(); + await editorState.apply( + transaction, + options: ApplyOptions( + inMemoryUpdate: true, + recordUndo: false, + ), + withUpdateSelection: false, + ); +} + +Future ensurePreviousNodeIsEmptyParagraph( + EditorState editorState, + Node aiWriterNode, +) async { + final previous = aiWriterNode.previous; + final needsEmptyParagraphNode = previous == null || + previous.type != ParagraphBlockKeys.type || + (previous.delta?.toPlainText().isNotEmpty ?? false); + + final Position position; + final transaction = editorState.transaction; + + if (needsEmptyParagraphNode) { + position = Position(path: aiWriterNode.path); + transaction.insertNode(aiWriterNode.path, paragraphNode()); + } else { + position = Position(path: previous.path); + } + transaction.afterSelection = Selection.collapsed(position); + + await editorState.apply( + transaction, + options: ApplyOptions( + inMemoryUpdate: true, + recordUndo: false, + ), + ); + + return position; +} + +extension SaveAIResponseExtension on EditorState { + Future insertBelow({ + required Node node, + required String markdownText, + }) async { + final selection = this.selection?.normalized; + if (selection == null) { + return; + } + + final nodes = customMarkdownToDocument( + markdownText, + tableWidth: 250.0, + ).root.children.map((e) => e.deepCopy()).toList(); + if (nodes.isEmpty) { + return; + } + + final insertedPath = selection.end.path.next; + final lastDeltaLength = nodes.lastOrNull?.delta?.length ?? 0; + + final transaction = this.transaction + ..insertNodes(insertedPath, nodes) + ..afterSelection = Selection( + start: Position(path: insertedPath), + end: Position( + path: insertedPath.nextNPath(nodes.length - 1), + offset: lastDeltaLength, + ), + ); + + await apply(transaction); + } + + Future replace({ + required Selection selection, + required String text, + }) async { + final trimmedText = text.trim(); + if (trimmedText.isEmpty) { + return; + } + await switch (kdefaultReplacementType) { + AskAIReplacementType.markdown => + _replaceWithMarkdown(selection, trimmedText), + AskAIReplacementType.plainText => + _replaceWithPlainText(selection, trimmedText), + }; + } + + Future _replaceWithMarkdown( + Selection selection, + String markdownText, + ) async { + final nodes = customMarkdownToDocument(markdownText) + .root + .children + .map((e) => e.deepCopy()) + .toList(); + if (nodes.isEmpty) { + return; + } + + final nodesInSelection = getNodesInSelection(selection); + final newSelection = Selection( + start: selection.start, + end: Position( + path: selection.start.path.nextNPath(nodes.length - 1), + offset: nodes.lastOrNull?.delta?.length ?? 0, + ), + ); + + final transaction = this.transaction + ..insertNodes(selection.start.path, nodes) + ..deleteNodes(nodesInSelection) + ..afterSelection = newSelection; + await apply(transaction); + } + + Future _replaceWithPlainText( + Selection selection, + String plainText, + ) async { + final nodes = getNodesInSelection(selection); + if (nodes.isEmpty || nodes.any((element) => element.delta == null)) { + return; + } + + final replaceTexts = plainText.split('\n') + ..removeWhere((element) => element.isEmpty); + final transaction = this.transaction + ..replaceTexts( + nodes, + selection, + replaceTexts, + ); + await apply(transaction); + + int endOffset = replaceTexts.last.length; + if (replaceTexts.length == 1) { + endOffset += selection.start.offset; + } + final end = Position( + path: [selection.start.path.first + replaceTexts.length - 1], + offset: endOffset, + ); + this.selection = Selection( + start: selection.start, + end: end, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_cubit.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_cubit.dart new file mode 100644 index 0000000000..4bc13321b8 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_cubit.dart @@ -0,0 +1,794 @@ +import 'dart:async'; + +import 'package:appflowy/ai/ai.dart'; +import 'package:appflowy/workspace/application/view/view_service.dart'; +import 'package:appflowy_backend/dispatch/dispatch.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_backend/protobuf/flowy-ai/protobuf.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_result/appflowy_result.dart'; +import 'package:bloc/bloc.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/foundation.dart'; + +import '../../base/markdown_text_robot.dart'; +import 'ai_writer_block_operations.dart'; +import 'ai_writer_entities.dart'; +import 'ai_writer_node_extension.dart'; + +/// Enable the debug log for the AiWriterCubit. +/// +/// This is useful for debugging the AI writer cubit. +const _aiWriterCubitDebugLog = true; + +class AiWriterCubit extends Cubit { + AiWriterCubit({ + required this.documentId, + required this.editorState, + this.onCreateNode, + this.onRemoveNode, + this.onAppendToDocument, + AppFlowyAIService? aiService, + }) : _aiService = aiService ?? AppFlowyAIService(), + _textRobot = MarkdownTextRobot(editorState: editorState), + selectedSourcesNotifier = ValueNotifier([documentId]), + super(IdleAiWriterState()); + + final String documentId; + final EditorState editorState; + final AppFlowyAIService _aiService; + final MarkdownTextRobot _textRobot; + final void Function()? onCreateNode; + final void Function()? onRemoveNode; + final void Function()? onAppendToDocument; + + Node? aiWriterNode; + + final List records = []; + final ValueNotifier> selectedSourcesNotifier; + + @override + Future close() async { + selectedSourcesNotifier.dispose(); + await super.close(); + } + + Future exit({ + bool withDiscard = true, + bool withUnformat = true, + }) async { + if (aiWriterNode == null) { + return; + } + if (withDiscard) { + await _textRobot.discard( + afterSelection: aiWriterNode!.aiWriterSelection, + ); + } + _textRobot.clear(); + _textRobot.reset(); + onRemoveNode?.call(); + records.clear(); + selectedSourcesNotifier.value = [documentId]; + emit(IdleAiWriterState()); + + if (withUnformat) { + final selection = aiWriterNode!.aiWriterSelection; + if (selection == null) { + return; + } + await formatSelection( + editorState, + selection, + ApplySuggestionFormatType.clear, + ); + } + if (aiWriterNode != null) { + await removeAiWriterNode(editorState, aiWriterNode!); + aiWriterNode = null; + } + } + + void register(Node node) async { + if (node.isAiWriterInitialized) { + return; + } + if (aiWriterNode != null && node.id != aiWriterNode!.id) { + await removeAiWriterNode(editorState, node); + return; + } + + aiWriterNode = node; + onCreateNode?.call(); + + await setAiWriterNodeIsInitialized(editorState, node); + + final command = node.aiWriterCommand; + final (run, prompt) = await _addSelectionTextToRecords(command); + + _aiWriterCubitLog( + 'command: $command, run: $run, prompt: $prompt', + ); + + if (!run) { + await exit(); + return; + } + + runCommand(command, prompt, null); + } + + void runCommand( + AiWriterCommand command, + String prompt, + PredefinedFormat? predefinedFormat, + ) async { + if (aiWriterNode == null) { + return; + } + + await _textRobot.discard(); + _textRobot.clear(); + + switch (command) { + case AiWriterCommand.continueWriting: + await _startContinueWriting( + command, + predefinedFormat, + ); + break; + case AiWriterCommand.fixSpellingAndGrammar: + case AiWriterCommand.improveWriting: + case AiWriterCommand.makeLonger: + case AiWriterCommand.makeShorter: + await _startSuggestingEdits(command, prompt, predefinedFormat); + break; + case AiWriterCommand.explain: + await _startInforming(command, prompt, predefinedFormat); + break; + case AiWriterCommand.userQuestion when prompt.isNotEmpty: + _startAskingQuestion(prompt, predefinedFormat); + break; + case AiWriterCommand.userQuestion: + emit( + ReadyAiWriterState(AiWriterCommand.userQuestion, isFirstRun: true), + ); + break; + } + } + + void _retry({ + required PredefinedFormat? predefinedFormat, + }) async { + final lastQuestion = + records.lastWhereOrNull((record) => record.role == AiRole.user); + + if (lastQuestion != null && state is RegisteredAiWriter) { + runCommand( + (state as RegisteredAiWriter).command, + lastQuestion.content, + lastQuestion.format, + ); + } + } + + Future stopStream() async { + if (aiWriterNode == null) { + return; + } + + if (state is GeneratingAiWriterState) { + final generatingState = state as GeneratingAiWriterState; + + await _textRobot.stop( + attributes: ApplySuggestionFormatType.replace.attributes, + ); + + if (_textRobot.hasAnyResult) { + records.add(AiWriterRecord.ai(content: _textRobot.markdownText)); + } + + await AIEventStopCompleteText( + CompleteTextTaskPB( + taskId: generatingState.taskId, + ), + ).send(); + + emit( + ReadyAiWriterState( + generatingState.command, + isFirstRun: false, + markdownText: generatingState.markdownText, + ), + ); + } + } + + void runResponseAction( + SuggestionAction action, [ + PredefinedFormat? predefinedFormat, + ]) async { + if (aiWriterNode == null) { + return; + } + + if (action case SuggestionAction.rewrite || SuggestionAction.tryAgain) { + _retry(predefinedFormat: predefinedFormat); + return; + } + if (action case SuggestionAction.discard || SuggestionAction.close) { + await exit(); + return; + } + + final selection = aiWriterNode?.aiWriterSelection; + if (selection == null) { + return; + } + + // Accept + // + // If the user clicks accept, we need to replace the selection with the AI's response + if (action case SuggestionAction.accept) { + // trim the markdown text to avoid extra new lines + final trimmedMarkdownText = _textRobot.markdownText.trim(); + + _aiWriterCubitLog( + 'trigger accept action, markdown text: $trimmedMarkdownText', + ); + + await formatSelection( + editorState, + selection, + ApplySuggestionFormatType.clear, + ); + + await _textRobot.deleteAINodes(); + + await _textRobot.replace( + selection: selection, + markdownText: trimmedMarkdownText, + ); + + await exit(withDiscard: false, withUnformat: false); + + return; + } + + if (action case SuggestionAction.keep) { + await _textRobot.persist(); + await exit(withDiscard: false); + return; + } + + if (action case SuggestionAction.insertBelow) { + if (state is! ReadyAiWriterState) { + return; + } + final command = (state as ReadyAiWriterState).command; + final markdownText = (state as ReadyAiWriterState).markdownText; + if (command == AiWriterCommand.explain && markdownText.isNotEmpty) { + final position = await ensurePreviousNodeIsEmptyParagraph( + editorState, + aiWriterNode!, + ); + _textRobot.start(position: position); + await _textRobot.persist(markdownText: markdownText); + } else if (_textRobot.hasAnyResult) { + await _textRobot.persist(); + } + + await formatSelection( + editorState, + selection, + ApplySuggestionFormatType.clear, + ); + await exit(withDiscard: false); + } + } + + bool hasUnusedResponse() { + return switch (state) { + ReadyAiWriterState( + isFirstRun: final isInitial, + markdownText: final markdownText, + ) => + !isInitial && (markdownText.isNotEmpty || _textRobot.hasAnyResult), + GeneratingAiWriterState() => true, + _ => false, + }; + } + + Future<(bool, String)> _addSelectionTextToRecords( + AiWriterCommand command, + ) async { + final node = aiWriterNode; + + // check the node is registered + if (node == null) { + return (false, ''); + } + + // check the selection is valid + final selection = node.aiWriterSelection?.normalized; + if (selection == null) { + return (false, ''); + } + + // if the command is continue writing, we don't need to get the selection text + if (command == AiWriterCommand.continueWriting) { + return (true, ''); + } + + // if the selection is collapsed, we don't need to get the selection text + if (selection.isCollapsed) { + return (true, ''); + } + + final selectionText = await editorState.getMarkdownInSelection(selection); + + if (command == AiWriterCommand.userQuestion) { + records.add( + AiWriterRecord.user(content: selectionText, format: null), + ); + + return (true, ''); + } else { + return (true, selectionText); + } + } + + Future _getDocumentContentFromTopToPosition(Position position) async { + final beginningToCursorSelection = Selection( + start: Position(path: [0]), + end: position, + ).normalized; + + final documentText = + (await editorState.getMarkdownInSelection(beginningToCursorSelection)) + .trim(); + + final view = await ViewBackendService.getView(documentId).toNullable(); + final viewName = view?.name ?? ''; + + return "$viewName\n$documentText".trim(); + } + + void _startAskingQuestion( + String prompt, + PredefinedFormat? format, + ) async { + if (aiWriterNode == null) { + return; + } + final command = AiWriterCommand.userQuestion; + + final stream = await _aiService.streamCompletion( + objectId: documentId, + text: prompt, + format: format, + history: records, + sourceIds: selectedSourcesNotifier.value, + completionType: command.toCompletionType(), + onStart: () async { + final position = await ensurePreviousNodeIsEmptyParagraph( + editorState, + aiWriterNode!, + ); + _textRobot.start(position: position); + records.add( + AiWriterRecord.user( + content: prompt, + format: format, + ), + ); + }, + processMessage: (text) async { + await _textRobot.appendMarkdownText( + text, + updateSelection: false, + attributes: ApplySuggestionFormatType.replace.attributes, + ); + onAppendToDocument?.call(); + }, + processAssistMessage: (text) async { + if (state case final GeneratingAiWriterState generatingState) { + emit( + GeneratingAiWriterState( + command, + taskId: generatingState.taskId, + markdownText: generatingState.markdownText + text, + ), + ); + } + }, + onEnd: () async { + if (state case final GeneratingAiWriterState generatingState) { + await _textRobot.stop( + attributes: ApplySuggestionFormatType.replace.attributes, + ); + emit( + ReadyAiWriterState( + command, + isFirstRun: false, + markdownText: generatingState.markdownText, + ), + ); + records.add( + AiWriterRecord.ai(content: _textRobot.markdownText), + ); + } + }, + onError: (error) async { + emit(ErrorAiWriterState(command, error: error)); + records.add( + AiWriterRecord.ai(content: _textRobot.markdownText), + ); + }, + onLocalAIStreamingStateChange: (state) { + emit(LocalAIStreamingAiWriterState(command, state: state)); + }, + ); + + if (stream != null) { + emit( + GeneratingAiWriterState( + command, + taskId: stream.$1, + ), + ); + } + } + + Future _startContinueWriting( + AiWriterCommand command, + PredefinedFormat? predefinedFormat, + ) async { + final position = aiWriterNode?.aiWriterSelection?.start; + if (position == null) { + return; + } + final text = await _getDocumentContentFromTopToPosition(position); + + if (text.isEmpty) { + final stateCopy = state; + emit(DocumentContentEmptyAiWriterState(command, onConfirm: exit)); + emit(stateCopy); + return; + } + + final stream = await _aiService.streamCompletion( + objectId: documentId, + text: text, + completionType: command.toCompletionType(), + history: records, + sourceIds: selectedSourcesNotifier.value, + format: predefinedFormat, + onStart: () async { + final position = await ensurePreviousNodeIsEmptyParagraph( + editorState, + aiWriterNode!, + ); + _textRobot.start(position: position); + records.add( + AiWriterRecord.user( + content: text, + format: predefinedFormat, + ), + ); + }, + processMessage: (text) async { + await _textRobot.appendMarkdownText( + text, + updateSelection: false, + attributes: ApplySuggestionFormatType.replace.attributes, + ); + onAppendToDocument?.call(); + }, + processAssistMessage: (text) async { + if (state case final GeneratingAiWriterState generatingState) { + emit( + GeneratingAiWriterState( + command, + taskId: generatingState.taskId, + markdownText: generatingState.markdownText + text, + ), + ); + } + }, + onEnd: () async { + if (state case final GeneratingAiWriterState generatingState) { + await _textRobot.stop( + attributes: ApplySuggestionFormatType.replace.attributes, + ); + emit( + ReadyAiWriterState( + command, + isFirstRun: false, + markdownText: generatingState.markdownText, + ), + ); + } + records.add( + AiWriterRecord.ai(content: _textRobot.markdownText), + ); + }, + onError: (error) async { + emit(ErrorAiWriterState(command, error: error)); + records.add( + AiWriterRecord.ai(content: _textRobot.markdownText), + ); + }, + onLocalAIStreamingStateChange: (state) { + emit(LocalAIStreamingAiWriterState(command, state: state)); + }, + ); + if (stream != null) { + emit( + GeneratingAiWriterState(command, taskId: stream.$1), + ); + } + } + + Future _startSuggestingEdits( + AiWriterCommand command, + String prompt, + PredefinedFormat? predefinedFormat, + ) async { + final selection = aiWriterNode?.aiWriterSelection; + if (selection == null) { + return; + } + if (prompt.isEmpty) { + prompt = records.removeAt(0).content; + } + + final stream = await _aiService.streamCompletion( + objectId: documentId, + text: prompt, + format: predefinedFormat, + completionType: command.toCompletionType(), + history: records, + sourceIds: selectedSourcesNotifier.value, + onStart: () async { + await formatSelection( + editorState, + selection, + ApplySuggestionFormatType.original, + ); + final position = await ensurePreviousNodeIsEmptyParagraph( + editorState, + aiWriterNode!, + ); + _textRobot.start(position: position, previousSelection: selection); + records.add( + AiWriterRecord.user( + content: prompt, + format: predefinedFormat, + ), + ); + }, + processMessage: (text) async { + await _textRobot.appendMarkdownText( + text, + updateSelection: false, + attributes: ApplySuggestionFormatType.replace.attributes, + ); + onAppendToDocument?.call(); + + _aiWriterCubitLog( + 'received message: $text', + ); + }, + processAssistMessage: (text) async { + if (state case final GeneratingAiWriterState generatingState) { + emit( + GeneratingAiWriterState( + command, + taskId: generatingState.taskId, + markdownText: generatingState.markdownText + text, + ), + ); + } + + _aiWriterCubitLog( + 'received assist message: $text', + ); + }, + onEnd: () async { + if (state case final GeneratingAiWriterState generatingState) { + await _textRobot.stop( + attributes: ApplySuggestionFormatType.replace.attributes, + ); + emit( + ReadyAiWriterState( + command, + isFirstRun: false, + markdownText: generatingState.markdownText, + ), + ); + records.add( + AiWriterRecord.ai(content: _textRobot.markdownText), + ); + + _aiWriterCubitLog( + 'returned response: ${_textRobot.markdownText}', + ); + } + }, + onError: (error) async { + emit(ErrorAiWriterState(command, error: error)); + records.add( + AiWriterRecord.ai(content: _textRobot.markdownText), + ); + }, + onLocalAIStreamingStateChange: (state) { + emit(LocalAIStreamingAiWriterState(command, state: state)); + }, + ); + if (stream != null) { + emit( + GeneratingAiWriterState(command, taskId: stream.$1), + ); + } + } + + Future _startInforming( + AiWriterCommand command, + String prompt, + PredefinedFormat? predefinedFormat, + ) async { + final selection = aiWriterNode?.aiWriterSelection; + if (selection == null) { + return; + } + if (prompt.isEmpty) { + prompt = records.removeAt(0).content; + } + + final stream = await _aiService.streamCompletion( + objectId: documentId, + text: prompt, + completionType: command.toCompletionType(), + history: records, + sourceIds: selectedSourcesNotifier.value, + format: predefinedFormat, + onStart: () async { + records.add( + AiWriterRecord.user( + content: prompt, + format: predefinedFormat, + ), + ); + }, + processMessage: (text) async { + if (state case final GeneratingAiWriterState generatingState) { + emit( + GeneratingAiWriterState( + command, + taskId: generatingState.taskId, + markdownText: generatingState.markdownText + text, + ), + ); + } + }, + processAssistMessage: (_) async {}, + onEnd: () async { + if (state case final GeneratingAiWriterState generatingState) { + emit( + ReadyAiWriterState( + command, + isFirstRun: false, + markdownText: generatingState.markdownText, + ), + ); + records.add( + AiWriterRecord.ai(content: generatingState.markdownText), + ); + } + }, + onError: (error) async { + if (state case final GeneratingAiWriterState generatingState) { + records.add( + AiWriterRecord.ai(content: generatingState.markdownText), + ); + } + emit(ErrorAiWriterState(command, error: error)); + }, + onLocalAIStreamingStateChange: (state) { + emit(LocalAIStreamingAiWriterState(command, state: state)); + }, + ); + if (stream != null) { + emit( + GeneratingAiWriterState(command, taskId: stream.$1), + ); + } + } + + void _aiWriterCubitLog(String message) { + if (_aiWriterCubitDebugLog) { + Log.debug('[AiWriterCubit] $message'); + } + } +} + +mixin RegisteredAiWriter { + AiWriterCommand get command; +} + +sealed class AiWriterState { + const AiWriterState(); +} + +class IdleAiWriterState extends AiWriterState { + const IdleAiWriterState(); +} + +class ReadyAiWriterState extends AiWriterState with RegisteredAiWriter { + const ReadyAiWriterState( + this.command, { + required this.isFirstRun, + this.markdownText = '', + }); + + @override + final AiWriterCommand command; + + final bool isFirstRun; + final String markdownText; +} + +class GeneratingAiWriterState extends AiWriterState with RegisteredAiWriter { + const GeneratingAiWriterState( + this.command, { + required this.taskId, + this.progress = '', + this.markdownText = '', + }); + + @override + final AiWriterCommand command; + + final String taskId; + final String progress; + final String markdownText; +} + +class ErrorAiWriterState extends AiWriterState with RegisteredAiWriter { + const ErrorAiWriterState( + this.command, { + required this.error, + }); + + @override + final AiWriterCommand command; + + final AIError error; +} + +class DocumentContentEmptyAiWriterState extends AiWriterState + with RegisteredAiWriter { + const DocumentContentEmptyAiWriterState( + this.command, { + required this.onConfirm, + }); + + @override + final AiWriterCommand command; + + final void Function() onConfirm; +} + +class LocalAIStreamingAiWriterState extends AiWriterState + with RegisteredAiWriter { + const LocalAIStreamingAiWriterState( + this.command, { + required this.state, + }); + + @override + final AiWriterCommand command; + + final LocalAIStreamingState state; +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_entities.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_entities.dart new file mode 100644 index 0000000000..f15c2e6d7f --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_entities.dart @@ -0,0 +1,159 @@ +import 'package:appflowy/ai/ai.dart'; +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy_backend/protobuf/flowy-ai/protobuf.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; + +import '../ai_writer_block_component.dart'; + +const kdefaultReplacementType = AskAIReplacementType.markdown; + +enum AskAIReplacementType { + markdown, + plainText, +} + +enum SuggestionAction { + accept, + discard, + close, + tryAgain, + rewrite, + keep, + insertBelow; + + String get i18n => switch (this) { + accept => LocaleKeys.suggestion_accept.tr(), + discard => LocaleKeys.suggestion_discard.tr(), + close => LocaleKeys.suggestion_close.tr(), + tryAgain => LocaleKeys.suggestion_tryAgain.tr(), + rewrite => LocaleKeys.suggestion_rewrite.tr(), + keep => LocaleKeys.suggestion_keep.tr(), + insertBelow => LocaleKeys.suggestion_insertBelow.tr(), + }; + + FlowySvg buildIcon(BuildContext context) { + final icon = switch (this) { + accept || keep => FlowySvgs.ai_fix_spelling_grammar_s, + discard || close => FlowySvgs.toast_close_s, + tryAgain || rewrite => FlowySvgs.ai_try_again_s, + insertBelow => FlowySvgs.suggestion_insert_below_s, + }; + + return FlowySvg( + icon, + size: Size.square(16.0), + color: switch (this) { + accept || keep => Color(0xFF278E42), + discard || close => Color(0xFFC40055), + _ => Theme.of(context).iconTheme.color, + }, + ); + } +} + +enum AiWriterCommand { + userQuestion, + explain, + // summarize, + continueWriting, + fixSpellingAndGrammar, + improveWriting, + makeShorter, + makeLonger; + + String defaultPrompt(String input) => switch (this) { + userQuestion => input, + explain => "Explain this phrase in a concise manner:\n\n$input", + // summarize => '$input\n\nTl;dr', + continueWriting => + 'Continue writing based on this existing text:\n\n$input', + fixSpellingAndGrammar => 'Correct this to standard English:\n\n$input', + improveWriting => 'Rewrite this in your own words:\n\n$input', + makeShorter => 'Make this text shorter:\n\n$input', + makeLonger => 'Make this text longer:\n\n$input', + }; + + String get i18n => switch (this) { + userQuestion => LocaleKeys.document_plugins_aiWriter_userQuestion.tr(), + explain => LocaleKeys.document_plugins_aiWriter_explain.tr(), + // summarize => LocaleKeys.document_plugins_aiWriter_summarize.tr(), + continueWriting => + LocaleKeys.document_plugins_aiWriter_continueWriting.tr(), + fixSpellingAndGrammar => + LocaleKeys.document_plugins_aiWriter_fixSpelling.tr(), + improveWriting => + LocaleKeys.document_plugins_smartEditImproveWriting.tr(), + makeShorter => LocaleKeys.document_plugins_aiWriter_makeShorter.tr(), + makeLonger => LocaleKeys.document_plugins_aiWriter_makeLonger.tr(), + }; + + FlowySvgData get icon => switch (this) { + userQuestion => FlowySvgs.ai_sparks_s, + explain => FlowySvgs.ai_explain_m, + // summarize => FlowySvgs.ai_summarize_s, + continueWriting || improveWriting => FlowySvgs.ai_improve_writing_s, + fixSpellingAndGrammar => FlowySvgs.ai_fix_spelling_grammar_s, + makeShorter => FlowySvgs.ai_make_shorter_s, + makeLonger => FlowySvgs.ai_make_longer_s, + }; + + CompletionTypePB toCompletionType() => switch (this) { + userQuestion => CompletionTypePB.UserQuestion, + explain => CompletionTypePB.ExplainSelected, + // summarize => CompletionTypePB.Summarize, + continueWriting => CompletionTypePB.ContinueWriting, + fixSpellingAndGrammar => CompletionTypePB.SpellingAndGrammar, + improveWriting => CompletionTypePB.ImproveWriting, + makeShorter => CompletionTypePB.MakeShorter, + makeLonger => CompletionTypePB.MakeLonger, + }; +} + +enum ApplySuggestionFormatType { + original(AiWriterBlockKeys.suggestionOriginal), + replace(AiWriterBlockKeys.suggestionReplacement), + clear(null); + + const ApplySuggestionFormatType(this.value); + final String? value; + + Map get attributes => {AiWriterBlockKeys.suggestion: value}; +} + +enum AiRole { + user, + system, + ai, +} + +class AiWriterRecord extends Equatable { + const AiWriterRecord.user({ + required this.content, + required this.format, + }) : role = AiRole.user; + + const AiWriterRecord.ai({ + required this.content, + }) : role = AiRole.ai, + format = null; + + final AiRole role; + final String content; + final PredefinedFormat? format; + + @override + List get props => [role, content, format]; + + CompletionRecordPB toPB() { + return CompletionRecordPB( + content: content, + role: switch (role) { + AiRole.user => ChatMessageTypePB.User, + AiRole.system || AiRole.ai => ChatMessageTypePB.System, + }, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_node_extension.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_node_extension.dart new file mode 100644 index 0000000000..881871b154 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_node_extension.dart @@ -0,0 +1,179 @@ +import 'package:appflowy/plugins/document/presentation/editor_plugins/parsers/document_markdown_parsers.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/parsers/sub_page_node_parser.dart'; +import 'package:appflowy/shared/markdown_to_document.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; + +import '../ai_writer_block_component.dart'; +import 'ai_writer_entities.dart'; + +extension AiWriterExtension on Node { + bool get isAiWriterInitialized { + return attributes[AiWriterBlockKeys.isInitialized]; + } + + Selection? get aiWriterSelection { + final selection = attributes[AiWriterBlockKeys.selection]; + if (selection == null) { + return null; + } + return Selection.fromJson(selection); + } + + AiWriterCommand get aiWriterCommand { + final index = attributes[AiWriterBlockKeys.command]; + return AiWriterCommand.values[index]; + } +} + +extension AiWriterNodeExtension on EditorState { + Future getMarkdownInSelection(Selection? selection) async { + selection ??= this.selection?.normalized; + if (selection == null || selection.isCollapsed) { + return ''; + } + + // if the selected nodes are not entirely selected, slice the nodes + final slicedNodes = []; + final List flattenNodes = getNodesInSelection(selection); + final List nodes = []; + + for (final node in flattenNodes) { + if (nodes.any((element) => element.isParentOf(node))) { + continue; + } + nodes.add(node); + } + + for (final node in nodes) { + final delta = node.delta; + if (delta == null) { + continue; + } + + final slicedDelta = delta.slice( + node == nodes.first ? selection.startIndex : 0, + node == nodes.last ? selection.endIndex : delta.length, + ); + + final copiedNode = node.copyWith( + attributes: { + ...node.attributes, + blockComponentDelta: slicedDelta.toJson(), + }, + ); + + slicedNodes.add(copiedNode); + } + + for (final (i, node) in slicedNodes.indexed) { + final childNodesShouldBeDeleted = []; + for (final child in node.children) { + if (!child.path.inSelection(selection)) { + childNodesShouldBeDeleted.add(child); + } + } + for (final child in childNodesShouldBeDeleted) { + slicedNodes[i] = node.copyWith( + children: node.children.where((e) => e.id != child.id).toList(), + type: selection.startIndex != 0 ? ParagraphBlockKeys.type : node.type, + ); + } + } + + // use \n\n as line break to improve the ai response + // using \n will cause the ai response treat the text as a single line + final markdown = await customDocumentToMarkdown( + Document.blank()..insert([0], slicedNodes), + lineBreak: '\n', + ); + + // trim the last \n if it exists + return markdown.trimRight(); + } + + List getPlainTextInSelection(Selection? selection) { + selection ??= this.selection?.normalized; + if (selection == null || selection.isCollapsed) { + return []; + } + + final res = []; + if (selection.isCollapsed) { + return res; + } + + final nodes = getNodesInSelection(selection); + + for (final node in nodes) { + final delta = node.delta; + if (delta == null) { + continue; + } + final startIndex = node == nodes.first ? selection.startIndex : 0; + final endIndex = node == nodes.last ? selection.endIndex : delta.length; + res.add(delta.slice(startIndex, endIndex).toPlainText()); + } + + return res; + } + + /// Determines whether the document is empty up to the selection + /// + /// If empty and the title is also empty, the continue writing option will be disabled. + bool isEmptyForContinueWriting({ + Selection? selection, + }) { + if (selection != null && !selection.isCollapsed) { + return false; + } + + final effectiveSelection = Selection( + start: Position(path: [0]), + end: selection?.normalized.end ?? + this.selection?.normalized.end ?? + Position(path: getLastSelectable()?.$1.path ?? [0]), + ); + + // if the selected nodes are not entirely selected, slice the nodes + final slicedNodes = []; + final nodes = getNodesInSelection(effectiveSelection); + + for (final node in nodes) { + final delta = node.delta; + if (delta == null) { + continue; + } + + final slicedDelta = delta.slice( + node == nodes.first ? effectiveSelection.startIndex : 0, + node == nodes.last ? effectiveSelection.endIndex : delta.length, + ); + + final copiedNode = node.copyWith( + attributes: { + ...node.attributes, + blockComponentDelta: slicedDelta.toJson(), + }, + ); + + slicedNodes.add(copiedNode); + } + + // using less custom parsers to avoid futures + final markdown = documentToMarkdown( + Document.blank()..insert([0], slicedNodes), + customParsers: [ + const MathEquationNodeParser(), + const CalloutNodeParser(), + const ToggleListNodeParser(), + const CustomParagraphNodeParser(), + const SubPageNodeParser(), + const SimpleTableNodeParser(), + const LinkPreviewNodeParser(), + const FileBlockNodeParser(), + ], + ); + + return markdown.trim().isEmpty; + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/widgets/ai_writer_gesture_detector.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/widgets/ai_writer_gesture_detector.dart new file mode 100644 index 0000000000..8a691acdfc --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/widgets/ai_writer_gesture_detector.dart @@ -0,0 +1,39 @@ +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; + +class AiWriterGestureDetector extends StatelessWidget { + const AiWriterGestureDetector({ + super.key, + required this.behavior, + required this.onPointerEvent, + this.child, + }); + + final HitTestBehavior behavior; + final void Function() onPointerEvent; + final Widget? child; + + @override + Widget build(BuildContext context) { + return RawGestureDetector( + behavior: behavior, + gestures: { + TapGestureRecognizer: + GestureRecognizerFactoryWithHandlers( + () => TapGestureRecognizer(), + (instance) => instance + ..onTapDown = ((_) => onPointerEvent()) + ..onSecondaryTapDown = ((_) => onPointerEvent()) + ..onTertiaryTapDown = ((_) => onPointerEvent()), + ), + ImmediateMultiDragGestureRecognizer: + GestureRecognizerFactoryWithHandlers< + ImmediateMultiDragGestureRecognizer>( + () => ImmediateMultiDragGestureRecognizer(), + (instance) => instance.onStart = (offset) => null, + ), + }, + child: child, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/widgets/ai_writer_prompt_input_more_button.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/widgets/ai_writer_prompt_input_more_button.dart new file mode 100644 index 0000000000..72b8d9560b --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/widgets/ai_writer_prompt_input_more_button.dart @@ -0,0 +1,151 @@ +import 'package:appflowy/ai/ai.dart'; +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/util/theme_extension.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/colorscheme/default_colorscheme.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flowy_infra_ui/style_widget/hover.dart'; +import 'package:flutter/material.dart'; + +import '../operations/ai_writer_entities.dart'; + +class AiWriterPromptMoreButton extends StatelessWidget { + const AiWriterPromptMoreButton({ + super.key, + required this.isEnabled, + required this.isSelected, + required this.onTap, + }); + + final bool isEnabled; + final bool isSelected; + final void Function() onTap; + + @override + Widget build(BuildContext context) { + return IgnorePointer( + ignoring: !isEnabled, + child: GestureDetector( + onTap: onTap, + behavior: HitTestBehavior.opaque, + child: SizedBox( + height: DesktopAIPromptSizes.actionBarButtonSize, + child: FlowyHover( + style: const HoverStyle( + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + isSelected: () => isSelected, + child: Padding( + padding: const EdgeInsetsDirectional.fromSTEB(6, 6, 4, 6), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + FlowyText( + LocaleKeys.ai_more.tr(), + fontSize: 12, + figmaLineHeight: 16, + color: isEnabled + ? Theme.of(context).hintColor + : Theme.of(context).disabledColor, + ), + const HSpace(2.0), + FlowySvg( + FlowySvgs.ai_source_drop_down_s, + color: Theme.of(context).hintColor, + size: const Size.square(8), + ), + ], + ), + ), + ), + ), + ), + ); + } +} + +class MoreAiWriterCommands extends StatelessWidget { + const MoreAiWriterCommands({ + super.key, + required this.hasSelection, + required this.editorState, + required this.onSelectCommand, + }); + + final EditorState editorState; + final bool hasSelection; + final void Function(AiWriterCommand) onSelectCommand; + + @override + Widget build(BuildContext context) { + return Container( + // add one here to take into account the border of the main message box. + // It is configured to be on the outside to hide some graphical + // artifacts. + margin: EdgeInsets.only(top: 4.0 + 1.0), + padding: EdgeInsets.all(8.0), + constraints: BoxConstraints(minWidth: 240.0), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + border: Border.all( + color: Theme.of(context).brightness == Brightness.light + ? ColorSchemeConstants.lightBorderColor + : ColorSchemeConstants.darkBorderColor, + strokeAlign: BorderSide.strokeAlignOutside, + ), + borderRadius: BorderRadius.all(Radius.circular(8.0)), + boxShadow: Theme.of(context).isLightMode + ? ShadowConstants.lightSmall + : ShadowConstants.darkSmall, + ), + child: IntrinsicWidth( + child: Column( + spacing: 4.0, + crossAxisAlignment: CrossAxisAlignment.start, + children: _getCommands( + hasSelection: hasSelection, + ), + ), + ), + ); + } + + List _getCommands({required bool hasSelection}) { + if (hasSelection) { + return [ + _bottomButton(AiWriterCommand.improveWriting), + _bottomButton(AiWriterCommand.fixSpellingAndGrammar), + _bottomButton(AiWriterCommand.explain), + const Divider(height: 1.0, thickness: 1.0), + _bottomButton(AiWriterCommand.makeLonger), + _bottomButton(AiWriterCommand.makeShorter), + ]; + } else { + return [ + _bottomButton(AiWriterCommand.continueWriting), + ]; + } + } + + Widget _bottomButton(AiWriterCommand command) { + return Builder( + builder: (context) { + return FlowyButton( + leftIcon: FlowySvg( + command.icon, + color: Theme.of(context).iconTheme.color, + ), + leftIconSize: const Size.square(20), + margin: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6.0), + text: FlowyText( + command.i18n, + figmaLineHeight: 20, + ), + onTap: () => onSelectCommand(command), + ); + }, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/widgets/ai_writer_scroll_wrapper.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/widgets/ai_writer_scroll_wrapper.dart new file mode 100644 index 0000000000..ef8ee81219 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/widgets/ai_writer_scroll_wrapper.dart @@ -0,0 +1,242 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/util/throttle.dart'; +import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../operations/ai_writer_cubit.dart'; +import 'ai_writer_gesture_detector.dart'; + +class AiWriterScrollWrapper extends StatefulWidget { + const AiWriterScrollWrapper({ + super.key, + required this.viewId, + required this.editorState, + required this.child, + }); + + final String viewId; + final EditorState editorState; + final Widget child; + + @override + State createState() => _AiWriterScrollWrapperState(); +} + +class _AiWriterScrollWrapperState extends State { + final overlayController = OverlayPortalController(); + late final throttler = Throttler(); + late final aiWriterCubit = AiWriterCubit( + documentId: widget.viewId, + editorState: widget.editorState, + onCreateNode: () { + aiWriterRegistered = true; + widget.editorState.service.keyboardService?.disableShortcuts(); + }, + onRemoveNode: () { + aiWriterRegistered = false; + widget.editorState.service.keyboardService?.enableShortcuts(); + widget.editorState.service.keyboardService?.enable(); + }, + onAppendToDocument: onAppendToDocument, + ); + + bool userHasScrolled = false; + bool aiWriterRegistered = false; + bool dialogShown = false; + + @override + void initState() { + super.initState(); + overlayController.show(); + } + + @override + void dispose() { + aiWriterCubit.close(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return BlocProvider.value( + value: aiWriterCubit, + child: NotificationListener( + onNotification: handleScrollNotification, + child: Focus( + autofocus: true, + onKeyEvent: handleKeyEvent, + child: MultiBlocListener( + listeners: [ + BlocListener( + listener: (context, state) { + if (state is DocumentContentEmptyAiWriterState) { + showConfirmDialog( + context: context, + title: + LocaleKeys.ai_continueWritingEmptyDocumentTitle.tr(), + description: LocaleKeys + .ai_continueWritingEmptyDocumentDescription + .tr(), + onConfirm: state.onConfirm, + ); + } + }, + ), + BlocListener( + listenWhen: (previous, current) => + previous is GeneratingAiWriterState && + current is ReadyAiWriterState, + listener: (context, state) { + widget.editorState.updateSelectionWithReason(null); + }, + ), + ], + child: OverlayPortal( + controller: overlayController, + overlayChildBuilder: (context) { + return BlocBuilder( + builder: (context, state) { + return AiWriterGestureDetector( + behavior: state is RegisteredAiWriter + ? HitTestBehavior.translucent + : HitTestBehavior.deferToChild, + onPointerEvent: () => onTapOutside(context), + ); + }, + ); + }, + child: widget.child, + ), + ), + ), + ), + ); + } + + bool handleScrollNotification(ScrollNotification notification) { + if (!aiWriterRegistered) { + return false; + } + + if (notification is UserScrollNotification) { + debounceResetUserHasScrolled(); + userHasScrolled = true; + throttler.cancel(); + } + + return false; + } + + void debounceResetUserHasScrolled() { + Debounce.debounce( + 'user_has_scrolled', + const Duration(seconds: 3), + () => userHasScrolled = false, + ); + } + + void onTapOutside(BuildContext context) { + final aiWriterCubit = context.read(); + + if (aiWriterCubit.hasUnusedResponse()) { + showConfirmDialog( + context: context, + title: LocaleKeys.button_discard.tr(), + description: LocaleKeys.document_plugins_discardResponse.tr(), + confirmLabel: LocaleKeys.button_discard.tr(), + style: ConfirmPopupStyle.cancelAndOk, + onConfirm: stopAndExit, + onCancel: () {}, + ); + } else { + stopAndExit(); + } + } + + KeyEventResult handleKeyEvent(FocusNode node, KeyEvent event) { + if (!aiWriterRegistered) { + return KeyEventResult.ignored; + } + if (dialogShown) { + return KeyEventResult.handled; + } + if (event is! KeyDownEvent) { + return KeyEventResult.ignored; + } + + switch (event.logicalKey) { + case LogicalKeyboardKey.escape: + if (aiWriterCubit.state case GeneratingAiWriterState _) { + aiWriterCubit.stopStream(); + } else if (aiWriterCubit.hasUnusedResponse()) { + dialogShown = true; + showConfirmDialog( + context: context, + title: LocaleKeys.button_discard.tr(), + description: LocaleKeys.document_plugins_discardResponse.tr(), + confirmLabel: LocaleKeys.button_discard.tr(), + style: ConfirmPopupStyle.cancelAndOk, + onConfirm: stopAndExit, + onCancel: () {}, + ).then((_) => dialogShown = false); + } else { + stopAndExit(); + } + return KeyEventResult.handled; + case LogicalKeyboardKey.keyC + when HardwareKeyboard.instance.isControlPressed: + if (aiWriterCubit.state case GeneratingAiWriterState _) { + aiWriterCubit.stopStream(); + } + return KeyEventResult.handled; + default: + break; + } + + return KeyEventResult.ignored; + } + + void onAppendToDocument() { + if (!aiWriterRegistered || userHasScrolled) { + return; + } + + throttler.call(() { + if (aiWriterCubit.aiWriterNode != null) { + final path = aiWriterCubit.aiWriterNode!.path; + + if (path.isEmpty) { + return; + } + + if (path.previous.isNotEmpty) { + final node = widget.editorState.getNodeAtPath(path.previous); + if (node != null && node.delta != null && node.delta!.isNotEmpty) { + widget.editorState.updateSelectionWithReason( + Selection.collapsed( + Position(path: path, offset: node.delta!.length), + ), + ); + return; + } + } + + widget.editorState.updateSelectionWithReason( + Selection.collapsed(Position(path: path)), + ); + } + }); + } + + void stopAndExit() { + Future(() async { + await aiWriterCubit.stopStream(); + await aiWriterCubit.exit(); + }); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/widgets/ai_writer_suggestion_actions.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/widgets/ai_writer_suggestion_actions.dart new file mode 100644 index 0000000000..d39ede2608 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/widgets/ai_writer_suggestion_actions.dart @@ -0,0 +1,110 @@ +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; + +import '../operations/ai_writer_entities.dart'; + +class SuggestionActionBar extends StatelessWidget { + const SuggestionActionBar({ + super.key, + required this.currentCommand, + required this.hasSelection, + required this.onTap, + }); + + final AiWriterCommand currentCommand; + final bool hasSelection; + final void Function(SuggestionAction) onTap; + + @override + Widget build(BuildContext context) { + return SeparatedRow( + mainAxisSize: MainAxisSize.min, + separatorBuilder: () => const HSpace(4.0), + children: _getSuggestedActions() + .map( + (action) => SuggestionActionButton( + action: action, + onTap: () => onTap(action), + ), + ) + .toList(), + ); + } + + List _getSuggestedActions() { + if (hasSelection) { + return switch (currentCommand) { + AiWriterCommand.userQuestion || AiWriterCommand.continueWriting => [ + SuggestionAction.keep, + SuggestionAction.discard, + SuggestionAction.rewrite, + ], + AiWriterCommand.explain => [ + SuggestionAction.insertBelow, + SuggestionAction.tryAgain, + SuggestionAction.close, + ], + AiWriterCommand.fixSpellingAndGrammar || + AiWriterCommand.improveWriting || + AiWriterCommand.makeShorter || + AiWriterCommand.makeLonger => + [ + SuggestionAction.accept, + SuggestionAction.discard, + SuggestionAction.insertBelow, + SuggestionAction.rewrite, + ], + }; + } else { + return switch (currentCommand) { + AiWriterCommand.userQuestion || AiWriterCommand.continueWriting => [ + SuggestionAction.keep, + SuggestionAction.discard, + SuggestionAction.rewrite, + ], + AiWriterCommand.explain => [ + SuggestionAction.insertBelow, + SuggestionAction.tryAgain, + SuggestionAction.close, + ], + _ => [ + SuggestionAction.keep, + SuggestionAction.discard, + SuggestionAction.rewrite, + ], + }; + } + } +} + +class SuggestionActionButton extends StatelessWidget { + const SuggestionActionButton({ + super.key, + required this.action, + required this.onTap, + }); + + final SuggestionAction action; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 28, + child: FlowyButton( + text: FlowyText( + action.i18n, + figmaLineHeight: 20, + ), + leftIcon: action.buildIcon(context), + iconPadding: 4.0, + margin: const EdgeInsets.symmetric( + horizontal: 6.0, + vertical: 4.0, + ), + onTap: onTap, + useIntrinsicWidth: true, + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/align_toolbar_item/align_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/align_toolbar_item/align_toolbar_item.dart index 09bdc06057..cceac56c0d 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/align_toolbar_item/align_toolbar_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/align_toolbar_item/align_toolbar_item.dart @@ -106,7 +106,7 @@ class _AlignmentButtonsState extends State<_AlignmentButtons> { child: FlowyButton( useIntrinsicWidth: true, text: widget.child, - hoverColor: Colors.grey.withOpacity(0.3), + hoverColor: Colors.grey.withValues(alpha: 0.3), onTap: () => controller.show(), ), ); @@ -167,7 +167,7 @@ class _AlignButton extends StatelessWidget { Widget build(BuildContext context) { return FlowyButton( useIntrinsicWidth: true, - hoverColor: Colors.grey.withOpacity(0.3), + hoverColor: Colors.grey.withValues(alpha: 0.3), onTap: onTap, text: FlowyTooltip( message: tooltips, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/align_toolbar_item/custom_text_align_command.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/align_toolbar_item/custom_text_align_command.dart index 9fe78591c3..bc3f5cffa1 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/align_toolbar_item/custom_text_align_command.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/align_toolbar_item/custom_text_align_command.dart @@ -1,9 +1,8 @@ -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; final List customTextAlignCommands = [ customTextLeftAlignCommand, @@ -26,8 +25,8 @@ final CommandShortcutEvent customTextLeftAlignCommand = CommandShortcutEvent( handler: (editorState) => _textAlignHandler(editorState, leftAlignmentKey), ); -/// Windows / Linux : ctrl + shift + e -/// macOS : ctrl + shift + e +/// Windows / Linux : ctrl + shift + c +/// macOS : ctrl + shift + c /// Allows the user to align text to the center /// /// - support @@ -36,7 +35,7 @@ final CommandShortcutEvent customTextLeftAlignCommand = CommandShortcutEvent( /// final CommandShortcutEvent customTextCenterAlignCommand = CommandShortcutEvent( key: 'Align text to the center', - command: 'ctrl+shift+e', + command: 'ctrl+shift+c', getDescription: LocaleKeys.settings_shortcutsPage_commands_textAlignCenter.tr, handler: (editorState) => _textAlignHandler(editorState, centerAlignmentKey), ); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/built_in_page_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/built_in_page_widget.dart index 9f25e4745d..090ecdce78 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/built_in_page_widget.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/built_in_page_widget.dart @@ -1,18 +1,13 @@ -import 'package:appflowy/generated/flowy_svgs.g.dart'; -import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/database/tab_bar/tab_bar_view.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; -import 'package:appflowy/startup/startup.dart'; -import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart'; -import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy/workspace/application/view/view_service.dart'; -import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pbserver.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_result/appflowy_result.dart'; -import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; class BuiltInPageWidget extends StatefulWidget { const BuiltInPageWidget({ @@ -79,18 +74,14 @@ class _BuiltInPageWidgetState extends State { return MouseRegion( onEnter: (_) => widget.editorState.service.scrollService?.disable(), onExit: (_) => widget.editorState.service.scrollService?.enable(), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildMenu(context, viewPB), - Flexible(child: _buildPage(context, viewPB)), - ], - ), + child: _buildPage(context, viewPB), ); } Widget _buildPage(BuildContext context, ViewPB view) { + final verticalPadding = + context.read()?.verticalPadding ?? + 0.0; return Focus( focusNode: focusNode, onFocusChange: (value) { @@ -98,64 +89,10 @@ class _BuiltInPageWidgetState extends State { widget.editorState.service.selectionService.clearSelection(); } }, - child: widget.builder(view), - ); - } - - Widget _buildMenu(BuildContext context, ViewPB view) { - return Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // information - FlowyIconButton( - tooltipText: LocaleKeys.tooltip_referencePage.tr( - namedArgs: {'name': view.layout.name}, - ), - width: 24, - height: 24, - iconPadding: const EdgeInsets.all(3), - icon: const FlowySvg( - FlowySvgs.information_s, - ), - ), - // setting - const Space(7, 0), - PopoverActionList<_ActionWrapper>( - direction: PopoverDirection.bottomWithCenterAligned, - actions: _ActionType.values - .map((action) => _ActionWrapper(action)) - .toList(), - buildChild: (controller) => FlowyIconButton( - tooltipText: LocaleKeys.tooltip_openMenu.tr(), - width: 24, - height: 24, - iconPadding: const EdgeInsets.all(3), - icon: const FlowySvg( - FlowySvgs.settings_s, - ), - onPressed: () => controller.show(), - ), - onSelected: (action, controller) async { - switch (action.inner) { - case _ActionType.viewDatabase: - getIt().add( - TabsEvent.openPlugin( - plugin: view.plugin(), - view: view, - ), - ); - break; - case _ActionType.delete: - final transaction = widget.editorState.transaction; - transaction.deleteNode(widget.node); - await widget.editorState.apply(transaction); - break; - } - controller.close(); - }, - ), - ], + child: Padding( + padding: EdgeInsets.symmetric(vertical: verticalPadding), + child: widget.builder(view), + ), ); } @@ -165,23 +102,3 @@ class _BuiltInPageWidgetState extends State { await widget.editorState.apply(transaction); } } - -enum _ActionType { viewDatabase, delete } - -class _ActionWrapper extends ActionCell { - _ActionWrapper(this.inner); - - final _ActionType inner; - - Widget? icon(Color iconColor) => null; - - @override - String get name { - switch (inner) { - case _ActionType.viewDatabase: - return LocaleKeys.tooltip_viewDataBase.tr(); - case _ActionType.delete: - return LocaleKeys.disclosureAction_delete.tr(); - } - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart index e2113cc1fb..93b45cf46a 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart @@ -1,6 +1,7 @@ import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart'; import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; @@ -21,13 +22,17 @@ class EmojiPickerButton extends StatelessWidget { this.enable = true, this.margin, this.buttonSize, + this.documentId, + this.tabs = const [PickerTabType.emoji, PickerTabType.icon], }); final EmojiIconData emoji; final double emojiSize; final Size emojiPickerSize; - final void Function(EmojiIconData emoji, PopoverController? controller) - onSubmitted; + final void Function( + SelectedEmojiIconResult result, + PopoverController? controller, + ) onSubmitted; final PopoverController popoverController = PopoverController(); final Widget? defaultIcon; final Offset? offset; @@ -37,6 +42,8 @@ class EmojiPickerButton extends StatelessWidget { final bool enable; final EdgeInsets? margin; final Size? buttonSize; + final String? documentId; + final List tabs; @override Widget build(BuildContext context) { @@ -53,6 +60,8 @@ class EmojiPickerButton extends StatelessWidget { showBorder: showBorder, enable: enable, buttonSize: buttonSize, + tabs: tabs, + documentId: documentId, ); } @@ -63,6 +72,8 @@ class EmojiPickerButton extends StatelessWidget { enable: enable, title: title, margin: margin, + tabs: tabs, + documentId: documentId, ); } } @@ -80,13 +91,17 @@ class _DesktopEmojiPickerButton extends StatelessWidget { this.showBorder = true, this.enable = true, this.buttonSize, + this.documentId, + this.tabs = const [PickerTabType.emoji, PickerTabType.icon], }); final EmojiIconData emoji; final double emojiSize; final Size emojiPickerSize; - final void Function(EmojiIconData emoji, PopoverController? controller) - onSubmitted; + final void Function( + SelectedEmojiIconResult result, + PopoverController? controller, + ) onSubmitted; final PopoverController popoverController = PopoverController(); final Widget? defaultIcon; final Offset? offset; @@ -95,6 +110,8 @@ class _DesktopEmojiPickerButton extends StatelessWidget { final bool showBorder; final bool enable; final Size? buttonSize; + final String? documentId; + final List tabs; @override Widget build(BuildContext context) { @@ -113,6 +130,9 @@ class _DesktopEmojiPickerButton extends StatelessWidget { height: emojiPickerSize.height, padding: const EdgeInsets.all(4.0), child: FlowyIconEmojiPicker( + initialType: emoji.type.toPickerTabType(), + tabs: tabs, + documentId: documentId, onSelectedEmoji: (r) { onSubmitted(r, popoverController); }, @@ -152,15 +172,21 @@ class _MobileEmojiPickerButton extends StatelessWidget { this.enable = true, this.title, this.margin, + this.documentId, + this.tabs = const [PickerTabType.emoji, PickerTabType.icon], }); final EmojiIconData emoji; final double emojiSize; - final void Function(EmojiIconData emoji, PopoverController? controller) - onSubmitted; + final void Function( + SelectedEmojiIconResult result, + PopoverController? controller, + ) onSubmitted; final String? title; final bool enable; final EdgeInsets? margin; + final String? documentId; + final List tabs; @override Widget build(BuildContext context) { @@ -177,11 +203,17 @@ class _MobileEmojiPickerButton extends StatelessWidget { final result = await context.push( Uri( path: MobileEmojiPickerScreen.routeName, - queryParameters: {MobileEmojiPickerScreen.pageTitle: title}, + queryParameters: { + MobileEmojiPickerScreen.pageTitle: title, + MobileEmojiPickerScreen.iconSelectedType: emoji.type.name, + MobileEmojiPickerScreen.uploadDocumentId: documentId, + MobileEmojiPickerScreen.selectTabs: + tabs.map((e) => e.name).toList().join('-'), + }, ).toString(), ); if (result != null) { - onSubmitted(result, null); + onSubmitted(result.toSelectedResult(), null); } } : null, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/format_arrow_character.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/format_arrow_character.dart index 6e6e111df1..8548b9354c 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/format_arrow_character.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/format_arrow_character.dart @@ -2,8 +2,13 @@ import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; const _greater = '>'; +const _dash = '-'; const _equals = '='; -const _arrow = '⇒'; +const _equalGreater = '⇒'; +const _dashGreater = '→'; + +const _hyphen = '-'; +const _emDash = '—'; // This is an em dash — not a single dash - !! /// format '=' + '>' into an ⇒ /// @@ -18,11 +23,47 @@ final CharacterShortcutEvent customFormatGreaterEqual = CharacterShortcutEvent( handler: (editorState) async => _handleDoubleCharacterReplacement( editorState: editorState, character: _greater, - replacement: _arrow, + replacement: _equalGreater, prefixCharacter: _equals, ), ); +/// format '-' + '>' into ⇒ +/// +/// - support +/// - desktop +/// - mobile +/// - web +/// +final CharacterShortcutEvent customFormatDashGreater = CharacterShortcutEvent( + key: 'format - + > into ->', + character: _greater, + handler: (editorState) async => _handleDoubleCharacterReplacement( + editorState: editorState, + character: _greater, + replacement: _dashGreater, + prefixCharacter: _dash, + ), +); + +/// format two hyphens into an em dash +/// +/// - support +/// - desktop +/// - mobile +/// - web +/// +final CharacterShortcutEvent customFormatDoubleHyphenEmDash = + CharacterShortcutEvent( + key: 'format double hyphen into an em dash', + character: _hyphen, + handler: (editorState) async => _handleDoubleCharacterReplacement( + editorState: editorState, + character: _hyphen, + replacement: _emDash, + ), +); + /// If [prefixCharacter] is null or empty, [character] is used Future _handleDoubleCharacterReplacement({ required EditorState editorState, @@ -61,11 +102,29 @@ Future _handleDoubleCharacterReplacement({ return false; } + // insert the greater character first and convert it to the replacement character to support undo + final insert = editorState.transaction + ..insertText( + node, + selection.end.offset, + character, + ); + + await editorState.apply( + insert, + skipHistoryDebounce: true, + ); + + final afterSelection = editorState.selection; + if (afterSelection == null) { + return false; + } + final replace = editorState.transaction ..replaceText( node, - selection.end.offset - 1, - 1, + afterSelection.end.offset - 2, + 2, replacement, ); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/insert_page_command.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/insert_page_command.dart index af605972de..11aed036d2 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/insert_page_command.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/insert_page_command.dart @@ -70,13 +70,12 @@ extension InsertDatabase on EditorState { node, selection.end.offset, 0, - r'$', - attributes: { - MentionBlockKeys.mention: { - MentionBlockKeys.type: MentionType.page.name, - MentionBlockKeys.pageId: view.id, - }, - }, + MentionBlockKeys.mentionChar, + attributes: MentionBlockKeys.buildMentionPageAttributes( + mentionType: MentionType.page, + pageId: view.id, + blockId: null, + ), ); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/link_to_page_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/link_to_page_widget.dart index ce4f44e72b..3f1440e100 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/link_to_page_widget.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/link_to_page_widget.dart @@ -9,6 +9,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; InlineActionsMenuService? _actionsMenuService; + Future showLinkToPageMenu( EditorState editorState, SelectionMenuService menuService, { @@ -60,7 +61,7 @@ Future showLinkToPageMenu( startCharAmount: 0, ); - _actionsMenuService?.show(); + await _actionsMenuService?.show(); } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/markdown_text_robot.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/markdown_text_robot.dart index 0cc9a50e5c..259777db94 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/markdown_text_robot.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/markdown_text_robot.dart @@ -1,137 +1,566 @@ import 'dart:convert'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/numbered_list/numbered_list_icon.dart'; import 'package:appflowy/shared/markdown_to_document.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; -import 'package:flutter/foundation.dart'; +import 'package:collection/collection.dart'; import 'package:synchronized/synchronized.dart'; +const _enableDebug = false; + class MarkdownTextRobot { MarkdownTextRobot({ required this.editorState, - this.enableDebug = true, }); final EditorState editorState; - final bool enableDebug; - final Lock lock = Lock(); + final Lock _lock = Lock(); - // The selection before the text robot is ready. - Selection? _startSelection; + /// The text position where new nodes will be inserted + Position? _insertPosition; - // The markdown text to be inserted. + /// The markdown text to be inserted String _markdownText = ''; - // Only for debug. Enable by [enableDebug]. - @visibleForTesting - final List debugMarkdownTexts = []; + /// The nodes inserted in the previous refresh. + Iterable _insertedNodes = []; - // The nodes inserted in the previous refresh. - Iterable _previousInsertedNodes = []; + /// Only for debug via [_enableDebug]. + final List _debugMarkdownTexts = []; - /// Start the text robot. - /// - /// Must call this function before using the text robot. - void start() { - _startSelection = editorState.selection; + /// Selection before the refresh. + Selection? _previousSelection; - if (enableDebug) { + bool get hasAnyResult => _markdownText.isNotEmpty; + + String get markdownText => _markdownText; + + Selection? getInsertedSelection() { + final position = _insertPosition; + if (position == null) { + Log.error("Expected non-null insert markdown text position"); + return null; + } + + if (_insertedNodes.isEmpty) { + return Selection.collapsed(position); + } + return Selection( + start: position, + end: Position( + path: position.path.nextNPath(_insertedNodes.length - 1), + ), + ); + } + + List getInsertedNodes() { + final selection = getInsertedSelection(); + return selection == null ? [] : editorState.getNodesInSelection(selection); + } + + void start({ + Selection? previousSelection, + Position? position, + }) { + _insertPosition = position ?? editorState.selection?.start; + _previousSelection = previousSelection ?? editorState.selection; + + if (_enableDebug) { Log.info( - 'MarkdownTextRobot prepare, current selection: $_startSelection', + 'MarkdownTextRobot start with insert text position: $_insertPosition', ); } } - /// Append the markdown text to the text robot. - /// - /// The text will be inserted into document but not persisted until the text - /// robot is stopped. - Future appendMarkdownText(String text) async { + /// The text will be inserted into the document but only in memory + Future appendMarkdownText( + String text, { + bool updateSelection = true, + Map? attributes, + }) async { _markdownText += text; - await lock.synchronized(() async { - await _refresh(); - }); - - if (enableDebug) { - debugMarkdownTexts.add(text); - Log.info('debug markdown texts: ${jsonEncode(debugMarkdownTexts)}'); - } - } - - /// Stop the text robot. - /// - /// The text will be persisted into document. - Future stop() async { - // persist the markdown text - await lock.synchronized(() async { - await _refresh(inMemoryUpdate: false); - }); - - _markdownText = ''; - - if (enableDebug) { - Log.info( - 'debug markdown texts: ${jsonEncode(debugMarkdownTexts)}', + await _lock.synchronized(() async { + await _refresh( + inMemoryUpdate: true, + updateSelection: updateSelection, + attributes: attributes, + ); + }); + + if (_enableDebug) { + _debugMarkdownTexts.add(text); + Log.info( + 'MarkdownTextRobot receive markdown: ${jsonEncode(_debugMarkdownTexts)}', ); - debugMarkdownTexts.clear(); } } - /// Refreshes the editor state with the current markdown text by: - /// - /// 1. Converting markdown to document nodes - /// 2. Replacing previously inserted nodes with new nodes - /// 3. Updating selection position - Future _refresh({bool inMemoryUpdate = true}) async { - final start = _startSelection?.start; + Future stop({ + Map? attributes, + }) async { + await _lock.synchronized(() async { + await _refresh( + inMemoryUpdate: true, + attributes: attributes, + ); + }); + } + + /// Persist the text into the document + Future persist({ + String? markdownText, + }) async { + if (markdownText != null) { + _markdownText = markdownText; + } + + await _lock.synchronized(() async { + await _refresh(inMemoryUpdate: false, updateSelection: true); + }); + + if (_enableDebug) { + Log.info('MarkdownTextRobot stop'); + _debugMarkdownTexts.clear(); + } + } + + /// Replace the selected content with the AI's response + Future replace({ + required Selection selection, + required String markdownText, + }) async { + if (selection.isSingle) { + await _replaceInSameLine( + selection: selection, + markdownText: markdownText, + ); + } else { + await _replaceInMultiLines( + selection: selection, + markdownText: markdownText, + ); + } + } + + /// Delete the temporary inserted AI nodes + Future deleteAINodes() async { + final nodes = getInsertedNodes(); + final transaction = editorState.transaction..deleteNodes(nodes); + await editorState.apply( + transaction, + options: const ApplyOptions(recordUndo: false), + ); + } + + /// Discard the inserted content + Future discard({ + Selection? afterSelection, + }) async { + final start = _insertPosition; if (start == null) { return; } - - final transaction = editorState.transaction; - - // Convert markdown and deep copy nodes - final nodes = customMarkdownToDocument(_markdownText).root.children.map( - (node) => node.deepCopy(), - ); // deep copy the nodes to avoid the linked entities being changed. - - // Insert new nodes at selection start - transaction.insertNodes(start.path, nodes); - - // Remove previously inserted nodes if they exist - if (_previousInsertedNodes.isNotEmpty) { - // fallback to the calculated position if the selection is null. - final end = editorState.selection?.end ?? - Position( - path: start.path.nextNPath(_previousInsertedNodes.length - 1), - ); - final deletedNodes = editorState.getNodesInSelection( - Selection(start: start, end: end), - ); - transaction.deleteNodes(deletedNodes); + if (_insertedNodes.isEmpty) { + return; } - // Update selection to end of inserted content if it contains text - final lastDelta = nodes.lastOrNull?.delta; - if (lastDelta != null) { - transaction.afterSelection = Selection.collapsed( - Position( - path: start.path.nextNPath(nodes.length - 1), - offset: lastDelta.length, - ), - ); - } + afterSelection ??= Selection.collapsed(start); + + // fallback to the calculated position if the selection is null. + final end = Position( + path: start.path.nextNPath(_insertedNodes.length - 1), + ); + final deletedNodes = editorState.getNodesInSelection( + Selection(start: start, end: end), + ); + final transaction = editorState.transaction + ..deleteNodes(deletedNodes) + ..afterSelection = afterSelection; await editorState.apply( transaction, + options: const ApplyOptions(recordUndo: false, inMemoryUpdate: true), + ); + + if (_enableDebug) { + Log.info('MarkdownTextRobot discard'); + } + } + + void clear() { + _markdownText = ''; + _insertedNodes = []; + } + + void reset() { + _insertPosition = null; + } + + Future _refresh({ + required bool inMemoryUpdate, + bool updateSelection = false, + Map? attributes, + }) async { + final position = _insertPosition; + if (position == null) { + Log.error("Expected non-null insert markdown text position"); + return; + } + + // Convert markdown and deep copy the nodes, prevent ing the linked + // entities from being changed + final documentNodes = customMarkdownToDocument( + _markdownText, + tableWidth: 250.0, + ).root.children; + + // check if the first selected node before the refresh is a numbered list node + final previousSelection = _previousSelection; + final previousSelectedNode = previousSelection == null + ? null + : editorState.getNodeAtPath(previousSelection.start.path); + final firstNodeIsNumberedList = previousSelectedNode != null && + previousSelectedNode.type == NumberedListBlockKeys.type; + + final newNodes = attributes == null + ? documentNodes + : documentNodes.mapIndexed((index, node) { + final n = _styleDelta(node: node, attributes: attributes); + n.externalValues = AINodeExternalValues( + isAINode: true, + ); + if (index == 0 && n.type == NumberedListBlockKeys.type) { + if (firstNodeIsNumberedList) { + final builder = NumberedListIndexBuilder( + editorState: editorState, + node: previousSelectedNode, + ); + final firstIndex = builder.indexInSameLevel; + n.updateAttributes({ + NumberedListBlockKeys.number: firstIndex, + }); + } + + n.externalValues = AINodeExternalValues( + isAINode: true, + isFirstNumberedListNode: true, + ); + } + return n; + }).toList(); + + if (newNodes.isEmpty) { + return; + } + + final deleteTransaction = editorState.transaction + ..deleteNodes(getInsertedNodes()); + + await editorState.apply( + deleteTransaction, options: ApplyOptions( inMemoryUpdate: inMemoryUpdate, recordUndo: false, ), ); - _previousInsertedNodes = nodes; + final insertTransaction = editorState.transaction + ..insertNodes(position.path, newNodes); + + final lastDelta = newNodes.lastOrNull?.delta; + if (lastDelta != null) { + insertTransaction.afterSelection = Selection.collapsed( + Position( + path: position.path.nextNPath(newNodes.length - 1), + offset: lastDelta.length, + ), + ); + } + + await editorState.apply( + insertTransaction, + options: ApplyOptions( + inMemoryUpdate: inMemoryUpdate, + recordUndo: !inMemoryUpdate, + ), + withUpdateSelection: updateSelection, + ); + + _insertedNodes = newNodes; + } + + Node _styleDelta({ + required Node node, + required Map attributes, + }) { + if (node.delta != null) { + final delta = node.delta!; + final attributeDelta = Delta() + ..retain(delta.length, attributes: attributes); + final newDelta = delta.compose(attributeDelta); + final newAttributes = node.attributes; + newAttributes['delta'] = newDelta.toJson(); + node.updateAttributes(newAttributes); + } + + List? children; + if (node.children.isNotEmpty) { + children = node.children + .map((child) => _styleDelta(node: child, attributes: attributes)) + .toList(); + } + + return node.copyWith( + children: children, + ); + } + + /// If the selected content is in the same line, + /// keep the selected node and replace the delta. + Future _replaceInSameLine({ + required Selection selection, + required String markdownText, + }) async { + if (markdownText.isEmpty) { + assert(false, 'Expected non-empty markdown text'); + Log.error('Expected non-empty markdown text'); + return; + } + + selection = selection.normalized; + + // If the selection is not a single node, do nothing. + if (!selection.isSingle) { + assert(false, 'Expected single node selection'); + Log.error('Expected single node selection'); + return; + } + + final startIndex = selection.startIndex; + final endIndex = selection.endIndex; + final length = endIndex - startIndex; + + // Get the selected node. + final node = editorState.getNodeAtPath(selection.start.path); + final delta = node?.delta; + if (node == null || delta == null) { + assert(false, 'Expected non-null node and delta'); + Log.error('Expected non-null node and delta'); + return; + } + + // Convert the markdown text to delta. + // Question: Why we need to convert the markdown to document first? + // Answer: Because the markdown text may contain the list item, + // if we convert the markdown to delta directly, the list item will be + // treated as a normal text node, and the delta will be incorrect. + // For example, the markdown text is: + // ``` + // 1. item1 + // ``` + // if we convert the markdown to delta directly, the delta will be: + // ``` + // [ + // { + // "insert": "1. item1" + // } + // ] + // ``` + // if we convert the markdown to document first, the document will be: + // ``` + // [ + // { + // "type": "numbered_list", + // "children": [ + // { + // "insert": "item1" + // } + // ] + // } + // ] + final document = customMarkdownToDocument(markdownText); + final nodes = document.root.children; + final decoder = DeltaMarkdownDecoder(); + final markdownDelta = + nodes.firstOrNull?.delta ?? decoder.convert(markdownText); + + if (markdownDelta.isEmpty) { + assert(false, 'Expected non-empty markdown delta'); + Log.error('Expected non-empty markdown delta'); + return; + } + + // Replace the delta of the selected node. + final transaction = editorState.transaction; + + // it means the user selected the entire sentence, we just replace the node + if (startIndex == 0 && length == node.delta?.length) { + if (nodes.isNotEmpty && node.children.isNotEmpty) { + // merge the children of the selected node and the first node of the ai response + nodes[0] = nodes[0].copyWith( + children: [ + ...node.children.map((e) => e.deepCopy()), + ...nodes[0].children, + ], + ); + } + transaction + ..insertNodes(node.path.next, nodes) + ..deleteNode(node); + } else { + // it means the user selected a part of the sentence, we need to delete the + // selected part and insert the new delta. + transaction + ..deleteText(node, startIndex, length) + ..insertTextDelta(node, startIndex, markdownDelta); + + // Add the remaining nodes to the document. + final remainingNodes = nodes.skip(1); + if (remainingNodes.isNotEmpty) { + transaction.insertNodes( + node.path.next, + remainingNodes, + ); + } + } + + await editorState.apply(transaction); + } + + /// If the selected content is in multiple lines + Future _replaceInMultiLines({ + required Selection selection, + required String markdownText, + }) async { + selection = selection.normalized; + + // If the selection is a single node, do nothing. + if (selection.isSingle) { + assert(false, 'Expected multi-line selection'); + Log.error('Expected multi-line selection'); + return; + } + + final markdownNodes = customMarkdownToDocument( + markdownText, + tableWidth: 250.0, + ).root.children; + + // Get the selected nodes. + final flattenNodes = editorState.getNodesInSelection(selection); + final nodes = []; + for (final node in flattenNodes) { + if (nodes.any((element) => element.isParentOf(node))) { + continue; + } + nodes.add(node); + } + + // Note: Don't change its order, otherwise the delta will be incorrect. + // step 1. merge the first selected node and the first node from the ai response + // step 2. merge the last selected node and the last node from the ai response + // step 3. insert the middle nodes from the ai response + // step 4. delete the middle nodes + final transaction = editorState.transaction; + + // step 1 + final firstNode = nodes.firstOrNull; + final delta = firstNode?.delta; + final firstMarkdownNode = markdownNodes.firstOrNull; + final firstMarkdownDelta = firstMarkdownNode?.delta; + if (firstNode != null && + delta != null && + firstMarkdownNode != null && + firstMarkdownDelta != null) { + final startIndex = selection.startIndex; + final length = delta.length - startIndex; + + transaction + ..deleteText(firstNode, startIndex, length) + ..insertTextDelta(firstNode, startIndex, firstMarkdownDelta); + + // if the first markdown node has children, we need to insert the children + // and delete the children of the first node that are in the selection. + if (firstMarkdownNode.children.isNotEmpty) { + transaction.insertNodes( + firstNode.path.child(0), + firstMarkdownNode.children.map((e) => e.deepCopy()), + ); + } + + final nodesToDelete = + firstNode.children.where((e) => e.path.inSelection(selection)); + transaction.deleteNodes(nodesToDelete); + } + + // step 2 + bool handledLastNode = false; + final lastNode = nodes.lastOrNull; + final lastDelta = lastNode?.delta; + final lastMarkdownNode = markdownNodes.lastOrNull; + final lastMarkdownDelta = lastMarkdownNode?.delta; + if (lastNode != null && + lastDelta != null && + lastMarkdownNode != null && + lastMarkdownDelta != null && + firstNode?.id != lastNode.id) { + handledLastNode = true; + + final endIndex = selection.endIndex; + + transaction.deleteText(lastNode, 0, endIndex); + + // if the last node is same as the first node, it means we have replaced the + // selected text in the first node. + if (lastMarkdownNode.id != firstMarkdownNode?.id) { + transaction.insertTextDelta(lastNode, 0, lastMarkdownDelta); + + if (lastMarkdownNode.children.isNotEmpty) { + transaction + ..insertNodes( + lastNode.path.child(0), + lastMarkdownNode.children.map((e) => e.deepCopy()), + ) + ..deleteNodes( + lastNode.children.where((e) => e.path.inSelection(selection)), + ); + } + } + } + + // step 3 + final insertedPath = selection.start.path.nextNPath(1); + final insertLength = handledLastNode ? 2 : 1; + if (markdownNodes.length > insertLength) { + transaction.insertNodes( + insertedPath, + markdownNodes + .skip(1) + .take(markdownNodes.length - insertLength) + .toList(), + ); + } + + // step 4 + final length = nodes.length - 2; + if (length > 0) { + final middleNodes = nodes.skip(1).take(length).toList(); + transaction.deleteNodes(middleNodes); + } + + await editorState.apply(transaction); } } + +class AINodeExternalValues extends NodeExternalValues { + const AINodeExternalValues({ + this.isAINode = false, + this.isFirstNumberedListNode = false, + }); + + final bool isAINode; + final bool isFirstNumberedListNode; +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/page_reference_commands.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/page_reference_commands.dart index 079a062837..007e4ea298 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/page_reference_commands.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/page_reference_commands.dart @@ -1,3 +1,4 @@ +import 'package:appflowy/mobile/presentation/inline_actions/mobile_inline_actions_menu.dart'; import 'package:appflowy/plugins/inline_actions/handlers/child_page.dart'; import 'package:appflowy/plugins/inline_actions/handlers/inline_page_reference.dart'; import 'package:appflowy/plugins/inline_actions/inline_actions_menu.dart'; @@ -47,6 +48,7 @@ CharacterShortcutEvent pageReferenceShortcutPlusSign( ); InlineActionsMenuService? selectionMenuService; + Future inlinePageReferenceCommandHandler( String character, BuildContext context, @@ -56,7 +58,7 @@ Future inlinePageReferenceCommandHandler( String? previousChar, }) async { final selection = editorState.selection; - if (UniversalPlatform.isMobile || selection == null) { + if (selection == null) { return false; } @@ -110,32 +112,48 @@ Future inlinePageReferenceCommandHandler( } if (context.mounted) { - selectionMenuService = InlineActionsMenu( - context: service.context!, - editorState: editorState, - service: service, - initialResults: initialResults, - style: style, - startCharAmount: previousChar != null ? 2 : 1, - cancelBySpaceHandler: () { - if (character == _plusChar) { - final currentSelection = editorState.selection; - if (currentSelection == null) { - return false; - } - // check if the space is after the character - if (currentSelection.isCollapsed && - currentSelection.start.offset == - selection.start.offset + character.length) { - _cancelInlinePageReferenceMenu(editorState); - return true; - } - } - return false; - }, - ); + keepEditorFocusNotifier.increase(); + selectionMenuService?.dismiss(); + selectionMenuService = UniversalPlatform.isMobile + ? MobileInlineActionsMenu( + context: service.context!, + editorState: editorState, + service: service, + initialResults: initialResults, + startCharAmount: previousChar != null ? 2 : 1, + style: style, + ) + : InlineActionsMenu( + context: service.context!, + editorState: editorState, + service: service, + initialResults: initialResults, + style: style, + startCharAmount: previousChar != null ? 2 : 1, + cancelBySpaceHandler: () { + if (character == _plusChar) { + final currentSelection = editorState.selection; + if (currentSelection == null) { + return false; + } + // check if the space is after the character + if (currentSelection.isCollapsed && + currentSelection.start.offset == + selection.start.offset + character.length) { + _cancelInlinePageReferenceMenu(editorState); + return true; + } + } + return false; + }, + ); + // disable the keyboard service + editorState.service.keyboardService?.disable(); - selectionMenuService?.show(); + await selectionMenuService?.show(); + + // enable the keyboard service + editorState.service.keyboardService?.enable(); } return true; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/selectable_item_list_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/selectable_item_list_menu.dart index 0b6382fc53..3c997bbdc4 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/selectable_item_list_menu.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/selectable_item_list_menu.dart @@ -1,9 +1,9 @@ import 'dart:math'; -import 'package:flutter/material.dart'; - +// ignore: implementation_imports +import 'package:appflowy_editor/src/flutter/scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; +import 'package:flutter/material.dart'; class SelectableItemListMenu extends StatelessWidget { const SelectableItemListMenu({ diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/toolbar_extension.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/toolbar_extension.dart index ebbfb27db6..77245a9f95 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/toolbar_extension.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/toolbar_extension.dart @@ -1,5 +1,14 @@ +import 'dart:ui'; + +import 'package:appflowy/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/toggle/toggle_block_component.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; +bool _isTableType(String type) { + return [TableBlockKeys.type, SimpleTableBlockKeys.type].contains(type); +} + bool notShowInTable(EditorState editorState) { final selection = editorState.selection; if (selection == null) { @@ -7,12 +16,12 @@ bool notShowInTable(EditorState editorState) { } final nodes = editorState.getNodesInSelection(selection); return nodes.every((element) { - if (element.type == TableBlockKeys.type) { + if (_isTableType(element.type)) { return false; } var parent = element.parent; while (parent != null) { - if (parent.type == TableBlockKeys.type) { + if (_isTableType(parent.type)) { return false; } parent = parent.parent; @@ -27,3 +36,31 @@ bool onlyShowInSingleTextTypeSelectionAndExcludeTable( return onlyShowInSingleSelectionAndTextType(editorState) && notShowInTable(editorState); } + +bool enableSuggestions(EditorState editorState) { + final selection = editorState.selection; + if (selection == null || !selection.isSingle) { + return false; + } + final node = editorState.getNodeAtPath(selection.start.path); + if (node == null) { + return false; + } + if (isNarrowWindow(editorState)) return false; + + return (node.delta != null && suggestionsItemTypes.contains(node.type)) && + notShowInTable(editorState); +} + +bool isNarrowWindow(EditorState editorState) { + final editorSize = editorState.renderBox?.size ?? Size.zero; + if (editorSize.width < 650) return true; + return false; +} + +final Set suggestionsItemTypes = { + ...toolbarItemWhiteList, + ToggleListBlockKeys.type, + TodoListBlockKeys.type, + CalloutBlockKeys.type, +}; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/bulleted_list/bulleted_list_icon.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/bulleted_list/bulleted_list_icon.dart index 43b84ddc1c..23b73e75a9 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/bulleted_list/bulleted_list_icon.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/bulleted_list/bulleted_list_icon.dart @@ -42,10 +42,8 @@ class BulletedListIcon extends StatelessWidget { size: Size.square(size * 0.8), ); return Container( - constraints: BoxConstraints( - minWidth: size, - minHeight: size, - ), + width: size, + height: size, margin: const EdgeInsets.only(right: 8.0), alignment: Alignment.center, child: icon, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart index 20cf6b5902..a7fcccd186 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart @@ -1,6 +1,8 @@ import 'package:appflowy/generated/locale_keys.g.dart' show LocaleKeys; import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/application/document_bloc.dart'; import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:easy_localization/easy_localization.dart' show StringTranslateExtension; @@ -33,17 +35,22 @@ class CalloutBlockKeys { /// /// The value is a String. static const String icon = 'icon'; + + /// the type of [FlowyIconType] + static const String iconType = 'icon_type'; } // The one is inserted through selection menu Node calloutNode({ Delta? delta, - String emoji = '📌', + EmojiIconData? emoji, Color? defaultColor, }) { + final defaultEmoji = emoji ?? EmojiIconData.emoji('📌'); final attributes = { CalloutBlockKeys.delta: (delta ?? Delta()).toJson(), - CalloutBlockKeys.icon: emoji, + CalloutBlockKeys.icon: defaultEmoji.emoji, + CalloutBlockKeys.iconType: defaultEmoji.type, CalloutBlockKeys.backgroundColor: defaultColor?.toHex(), }; return Node( @@ -74,7 +81,7 @@ class CalloutBlockComponentBuilder extends BlockComponentBuilder { }); final Color defaultColor; - final EdgeInsets inlinePadding; + final EdgeInsets Function(Node node) inlinePadding; @override BlockComponentWidget build(BlockComponentContext blockComponentContext) { @@ -90,12 +97,15 @@ class CalloutBlockComponentBuilder extends BlockComponentBuilder { blockComponentContext, state, ), + actionTrailingBuilder: (context, state) => actionTrailingBuilder( + blockComponentContext, + state, + ), ); } @override - BlockComponentValidate get validate => - (node) => node.delta != null && node.children.isEmpty; + BlockComponentValidate get validate => (node) => node.delta != null; } // the main widget for rendering the callout block @@ -105,13 +115,14 @@ class CalloutBlockComponentWidget extends BlockComponentStatefulWidget { required super.node, super.showActions, super.actionBuilder, + super.actionTrailingBuilder, super.configuration = const BlockComponentConfiguration(), required this.defaultColor, required this.inlinePadding, }); final Color defaultColor; - final EdgeInsets inlinePadding; + final EdgeInsets Function(Node node) inlinePadding; @override State createState() => @@ -126,7 +137,8 @@ class _CalloutBlockComponentWidgetState BlockComponentConfigurable, BlockComponentTextDirectionMixin, BlockComponentAlignMixin, - BlockComponentBackgroundColorMixin { + BlockComponentBackgroundColorMixin, + NestedBlockComponentStatefulWidgetMixin { // the key used to forward focus to the richtext child @override final forwardKey = GlobalKey(debugLabel: 'flowy_rich_text'); @@ -156,56 +168,117 @@ class _CalloutBlockComponentWidgetState } // get the emoji of the note block from the node's attributes or default to '📌' - String get emoji { + EmojiIconData get emoji { final icon = node.attributes[CalloutBlockKeys.icon]; - if (icon == null || icon.isEmpty) { - return '📌'; - } - return icon; + final type = + node.attributes[CalloutBlockKeys.iconType] ?? FlowyIconType.emoji; + EmojiIconData result = EmojiIconData.emoji('📌'); + try { + result = EmojiIconData(FlowyIconType.values.byName(type), icon); + } catch (_) {} + return result; } - // get access to the editor state via provider @override - late final editorState = Provider.of(context, listen: false); + Widget build(BuildContext context) { + Widget child = node.children.isEmpty + ? buildComponent(context) + : buildComponentWithChildren(context); + + if (UniversalPlatform.isDesktop) { + child = Padding( + padding: EdgeInsets.symmetric(vertical: 2.0), + child: child, + ); + } + + return child; + } + + @override + Widget buildComponentWithChildren(BuildContext context) { + Widget child = Stack( + children: [ + Positioned.fill( + left: UniversalPlatform.isMobile ? 0 : cachedLeft, + child: Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(6.0)), + color: backgroundColor, + ), + ), + ), + NestedListWidget( + indentPadding: indentPadding.copyWith(bottom: 8), + child: buildComponent(context, withBackgroundColor: false), + children: editorState.renderer.buildList( + context, + widget.node.children, + ), + ), + ], + ); + + if (UniversalPlatform.isMobile) { + child = Padding( + padding: padding, + child: child, + ); + } + + return child; + } // build the callout block widget @override - Widget build(BuildContext context) { + Widget buildComponent( + BuildContext context, { + bool withBackgroundColor = true, + }) { final textDirection = calculateTextDirection( layoutDirection: Directionality.maybeOf(context), ); final (emojiSize, emojiButtonSize) = calculateEmojiSize(); - + final documentId = context.read()?.documentId; Widget child = Container( decoration: BoxDecoration( - borderRadius: const BorderRadius.all(Radius.circular(8.0)), - color: backgroundColor, + borderRadius: const BorderRadius.all(Radius.circular(6.0)), + color: withBackgroundColor ? backgroundColor : null, ), - padding: widget.inlinePadding, + padding: widget.inlinePadding(widget.node), width: double.infinity, alignment: alignment, child: Row( - crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, textDirection: textDirection, children: [ - if (UniversalPlatform.isDesktopOrWeb) const HSpace(4.0), + const HSpace(6.0), // the emoji picker button for the note EmojiPickerButton( // force to refresh the popover state - key: ValueKey(widget.node.id + emoji), + key: ValueKey(widget.node.id + emoji.emoji), enable: editorState.editable, title: '', - emoji: EmojiIconData.emoji(emoji), + margin: UniversalPlatform.isMobile + ? const EdgeInsets.symmetric(vertical: 2.0, horizontal: 10.0) + : EdgeInsets.zero, + emoji: emoji, emojiSize: emojiSize, showBorder: false, buttonSize: emojiButtonSize, - onSubmitted: (emoji, controller) { - setEmoji(emoji.emoji); - controller?.close(); + documentId: documentId, + tabs: const [ + PickerTabType.emoji, + PickerTabType.icon, + PickerTabType.custom, + ], + onSubmitted: (r, controller) { + setEmojiIconData(r.data); + if (!r.keepOpen) controller?.close(); }, ), - if (UniversalPlatform.isDesktopOrWeb) const HSpace(4.0), + if (UniversalPlatform.isDesktopOrWeb) const HSpace(6.0), Flexible( child: Padding( padding: const EdgeInsets.symmetric(vertical: 4.0), @@ -217,17 +290,26 @@ class _CalloutBlockComponentWidgetState ), ); - child = Padding( - key: blockComponentKey, - padding: padding, - child: child, - ); + if (UniversalPlatform.isMobile && node.children.isEmpty) { + child = Padding( + key: blockComponentKey, + padding: padding, + child: child, + ); + } else { + child = Container( + key: blockComponentKey, + padding: EdgeInsets.zero, + child: child, + ); + } child = BlockSelectionContainer( node: node, delegate: this, listenable: editorState.selectionNotifier, blockColor: editorState.editorStyle.selectionColor, + selectionAboveBlock: true, supportTypes: const [ BlockSelectionType.block, ], @@ -238,6 +320,7 @@ class _CalloutBlockComponentWidgetState child = BlockComponentActionWrapper( node: widget.node, actionBuilder: widget.actionBuilder!, + actionTrailingBuilder: widget.actionTrailingBuilder, child: child, ); } @@ -258,10 +341,10 @@ class _CalloutBlockComponentWidgetState placeholderText: placeholderText, textAlign: alignment?.toTextAlign ?? textAlign, textSpanDecorator: (textSpan) => textSpan.updateTextStyle( - textStyle, + textStyleWithTextSpan(textSpan: textSpan), ), placeholderTextSpanDecorator: (textSpan) => textSpan.updateTextStyle( - placeholderTextStyle, + placeholderTextStyleWithTextSpan(textSpan: textSpan), ), textDirection: textDirection, cursorColor: editorState.editorStyle.cursorColor, @@ -270,10 +353,11 @@ class _CalloutBlockComponentWidgetState } // set the emoji of the note block - Future setEmoji(String emoji) async { + Future setEmojiIconData(EmojiIconData data) async { final transaction = editorState.transaction ..updateNode(node, { - CalloutBlockKeys.icon: emoji, + CalloutBlockKeys.icon: data.emoji, + CalloutBlockKeys.iconType: data.type.name, }) ..afterSelection = Selection.collapsed( Position(path: node.path, offset: node.delta?.length ?? 0), diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/callout/callout_block_shortcuts.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/callout/callout_block_shortcuts.dart index 3c50661071..842f3f59fd 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/callout/callout_block_shortcuts.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/callout/callout_block_shortcuts.dart @@ -32,9 +32,22 @@ CharacterShortcutEventHandler _insertNewLineHandler = (editorState) async { await editorState.deleteSelection(selection); if (HardwareKeyboard.instance.isShiftPressed) { - await editorState.insertNewLine(); - } else { - await editorState.insertTextAtCurrentSelection('\n'); + // ignore the shift+enter event, fallback to the default behavior + return false; + } else if (node.children.isEmpty) { + // insert a new paragraph within the callout block + final path = node.path.child(0); + final transaction = editorState.transaction; + transaction.insertNode( + path, + paragraphNode(), + ); + transaction.afterSelection = Selection.collapsed( + Position( + path: path, + ), + ); + await editorState.apply(transaction); } return true; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_copy_button.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_copy_button.dart index cc80119cb9..645de3b2f8 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_copy_button.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_copy_button.dart @@ -47,7 +47,6 @@ class _CopyButton extends StatelessWidget { if (context.mounted) { showToastNotification( - context, message: LocaleKeys.document_codeBlock_codeCopiedSnackbar.tr(), ); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_language_selector.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_language_selector.dart index 14b19a6927..c4c2e3e0ba 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_language_selector.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/code_block/code_block_language_selector.dart @@ -3,6 +3,8 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/base/selec import 'package:appflowy/plugins/document/presentation/editor_plugins/base/string_extension.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/code_block/code_language_screen.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; +// ignore: implementation_imports +import 'package:appflowy_editor/src/flutter/scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; @@ -10,7 +12,6 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:go_router/go_router.dart'; -import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:universal_platform/universal_platform.dart'; CodeBlockLanguagePickerBuilder codeBlockLanguagePickerBuilder = ( diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/columns/simple_column_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/columns/simple_column_block_component.dart new file mode 100644 index 0000000000..c426ad640f --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/columns/simple_column_block_component.dart @@ -0,0 +1,221 @@ +import 'package:appflowy/plugins/database/tab_bar/tab_bar_view.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/columns/simple_columns_block_constant.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +Node simpleColumnNode({ + List? children, + double? ratio, +}) { + return Node( + type: SimpleColumnBlockKeys.type, + children: children ?? [paragraphNode()], + attributes: { + SimpleColumnBlockKeys.ratio: ratio, + }, + ); +} + +extension SimpleColumnBlockAttributes on Node { + // get the next column node of the current column node + // if the current column node is the last column node, return null + Node? get nextColumn { + final index = path.last; + final parent = this.parent; + if (parent == null || index == parent.children.length - 1) { + return null; + } + return parent.children[index + 1]; + } + + // get the previous column node of the current column node + // if the current column node is the first column node, return null + Node? get previousColumn { + final index = path.last; + final parent = this.parent; + if (parent == null || index == 0) { + return null; + } + return parent.children[index - 1]; + } +} + +class SimpleColumnBlockKeys { + const SimpleColumnBlockKeys._(); + + static const String type = 'simple_column'; + + /// @Deprecated Use [SimpleColumnBlockKeys.ratio] instead. + /// + /// This field is no longer used since v0.6.9 + @Deprecated('Use [SimpleColumnBlockKeys.ratio] instead.') + static const String width = 'width'; + + /// The ratio of the column width. + /// + /// The value is a double number between 0 and 1. + static const String ratio = 'ratio'; +} + +class SimpleColumnBlockComponentBuilder extends BlockComponentBuilder { + SimpleColumnBlockComponentBuilder({ + super.configuration, + }); + + @override + BlockComponentWidget build(BlockComponentContext blockComponentContext) { + final node = blockComponentContext.node; + return SimpleColumnBlockComponent( + key: node.key, + node: node, + showActions: showActions(node), + configuration: configuration, + actionBuilder: (_, state) => actionBuilder(blockComponentContext, state), + ); + } + + @override + BlockComponentValidate get validate => (node) => node.children.isNotEmpty; +} + +class SimpleColumnBlockComponent extends BlockComponentStatefulWidget { + const SimpleColumnBlockComponent({ + super.key, + required super.node, + super.showActions, + super.actionBuilder, + super.actionTrailingBuilder, + super.configuration = const BlockComponentConfiguration(), + }); + + @override + State createState() => + SimpleColumnBlockComponentState(); +} + +class SimpleColumnBlockComponentState extends State + with SelectableMixin, BlockComponentConfigurable { + @override + BlockComponentConfiguration get configuration => widget.configuration; + + @override + Node get node => widget.node; + + RenderBox? get _renderBox => context.findRenderObject() as RenderBox?; + + final columnKey = GlobalKey(); + + late final EditorState editorState = context.read(); + + @override + Widget build(BuildContext context) { + Widget child = Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: node.children.map( + (e) { + Widget child = Provider( + create: (_) => DatabasePluginWidgetBuilderSize( + verticalPadding: 0, + horizontalPadding: 0, + ), + child: editorState.renderer.build(context, e), + ); + if (SimpleColumnsBlockConstants.enableDebugBorder) { + child = DecoratedBox( + decoration: BoxDecoration( + border: Border.all( + color: Colors.blue, + ), + ), + child: child, + ); + } + return child; + }, + ).toList(), + ); + + child = Padding( + key: columnKey, + padding: padding, + child: child, + ); + + if (SimpleColumnsBlockConstants.enableDebugBorder) { + child = Container( + color: Colors.green.withValues( + alpha: 0.3, + ), + child: child, + ); + } + + // the column block does not support the block actions and selection + // because the column block is a layout wrapper, it does not have a content + return child; + } + + @override + Position start() => Position(path: widget.node.path); + + @override + Position end() => Position(path: widget.node.path, offset: 1); + + @override + Position getPositionInOffset(Offset start) => end(); + + @override + bool get shouldCursorBlink => false; + + @override + CursorStyle get cursorStyle => CursorStyle.cover; + + @override + Rect getBlockRect({ + bool shiftWithBaseOffset = false, + }) { + return getRectsInSelection(Selection.invalid()).first; + } + + @override + Rect? getCursorRectInPosition( + Position position, { + bool shiftWithBaseOffset = false, + }) { + final rects = getRectsInSelection( + Selection.collapsed(position), + shiftWithBaseOffset: shiftWithBaseOffset, + ); + return rects.firstOrNull; + } + + @override + List getRectsInSelection( + Selection selection, { + bool shiftWithBaseOffset = false, + }) { + if (_renderBox == null) { + return []; + } + final parentBox = context.findRenderObject(); + final renderBox = columnKey.currentContext?.findRenderObject(); + if (parentBox is RenderBox && renderBox is RenderBox) { + return [ + renderBox.localToGlobal(Offset.zero, ancestor: parentBox) & + renderBox.size, + ]; + } + return [Offset.zero & _renderBox!.size]; + } + + @override + Selection getSelectionInRange(Offset start, Offset end) => + Selection.single(path: widget.node.path, startOffset: 0, endOffset: 1); + + @override + Offset localToGlobal(Offset offset, {bool shiftWithBaseOffset = false}) => + _renderBox!.localToGlobal(offset); +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/columns/simple_column_block_width_resizer.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/columns/simple_column_block_width_resizer.dart new file mode 100644 index 0000000000..69bec33c61 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/columns/simple_column_block_width_resizer.dart @@ -0,0 +1,164 @@ +import 'package:appflowy/plugins/document/presentation/editor_configuration.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/drag_to_reorder/draggable_option_button.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter/material.dart'; + +class SimpleColumnBlockWidthResizer extends StatefulWidget { + const SimpleColumnBlockWidthResizer({ + super.key, + required this.columnNode, + required this.editorState, + this.height, + }); + + final Node columnNode; + final EditorState editorState; + final double? height; + + @override + State createState() => + _SimpleColumnBlockWidthResizerState(); +} + +class _SimpleColumnBlockWidthResizerState + extends State { + bool isDragging = false; + + ValueNotifier isHovering = ValueNotifier(false); + + @override + void dispose() { + isHovering.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return MouseRegion( + onEnter: (_) => isHovering.value = true, + onExit: (_) { + // delay the hover state change to avoid flickering + Future.delayed(const Duration(milliseconds: 100), () { + if (!isDragging) { + isHovering.value = false; + } + }); + }, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onHorizontalDragStart: _onHorizontalDragStart, + onHorizontalDragUpdate: _onHorizontalDragUpdate, + onHorizontalDragEnd: _onHorizontalDragEnd, + onHorizontalDragCancel: _onHorizontalDragCancel, + child: ValueListenableBuilder( + valueListenable: isHovering, + builder: (context, isHovering, child) { + final hide = isDraggingAppFlowyEditorBlock.value || !isHovering; + return MouseRegion( + cursor: SystemMouseCursors.resizeLeftRight, + child: Container( + width: 2, + height: widget.height ?? 20, + margin: EdgeInsets.symmetric(horizontal: 2), + color: !hide + ? Theme.of(context).colorScheme.primary + : Colors.transparent, + ), + ); + }, + ), + ), + ); + } + + void _onHorizontalDragStart(DragStartDetails details) { + isDragging = true; + EditorGlobalConfiguration.enableDragMenu.value = false; + } + + void _onHorizontalDragUpdate(DragUpdateDetails details) { + if (!isDragging) { + return; + } + + // update the column width in memory + final columnNode = widget.columnNode; + final columnsNode = columnNode.columnsParent; + if (columnsNode == null) { + return; + } + final editorWidth = columnsNode.rect.width; + final rect = columnNode.rect; + final width = rect.width; + final originalRatio = columnNode.attributes[SimpleColumnBlockKeys.ratio]; + final newWidth = width + details.delta.dx; + + final transaction = widget.editorState.transaction; + final newRatio = newWidth / editorWidth; + transaction.updateNode(columnNode, { + ...columnNode.attributes, + SimpleColumnBlockKeys.ratio: newRatio, + }); + + if (newRatio < 0.1 && newRatio < originalRatio) { + return; + } + + final nextColumn = columnNode.nextColumn; + if (nextColumn != null) { + final nextColumnRect = nextColumn.rect; + final nextColumnWidth = nextColumnRect.width; + final newNextColumnWidth = nextColumnWidth - details.delta.dx; + final newNextColumnRatio = newNextColumnWidth / editorWidth; + if (newNextColumnRatio < 0.1) { + return; + } + transaction.updateNode(nextColumn, { + ...nextColumn.attributes, + SimpleColumnBlockKeys.ratio: newNextColumnRatio, + }); + } + + transaction.updateNode(columnsNode, { + ...columnsNode.attributes, + ColumnsBlockKeys.columnCount: columnsNode.children.length, + }); + + widget.editorState.apply( + transaction, + options: ApplyOptions(inMemoryUpdate: true), + ); + } + + void _onHorizontalDragEnd(DragEndDetails details) { + isHovering.value = false; + EditorGlobalConfiguration.enableDragMenu.value = true; + + if (!isDragging) { + return; + } + + // apply the transaction again to make sure the width is updated + final transaction = widget.editorState.transaction; + final columnsNode = widget.columnNode.columnsParent; + if (columnsNode == null) { + return; + } + for (final columnNode in columnsNode.children) { + transaction.updateNode(columnNode, { + ...columnNode.attributes, + }); + } + widget.editorState.apply(transaction); + + isDragging = false; + } + + void _onHorizontalDragCancel() { + isDragging = false; + isHovering.value = false; + EditorGlobalConfiguration.enableDragMenu.value = true; + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/columns/simple_column_node_extension.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/columns/simple_column_node_extension.dart new file mode 100644 index 0000000000..05389fb760 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/columns/simple_column_node_extension.dart @@ -0,0 +1,34 @@ +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; + +extension SimpleColumnNodeExtension on Node { + /// Returns the parent [Node] of the current node if it is a [SimpleColumnsBlock]. + Node? get columnsParent { + Node? currentNode = parent; + while (currentNode != null) { + if (currentNode.type == SimpleColumnsBlockKeys.type) { + return currentNode; + } + currentNode = currentNode.parent; + } + return null; + } + + /// Returns the parent [Node] of the current node if it is a [SimpleColumnBlock]. + Node? get columnParent { + Node? currentNode = parent; + while (currentNode != null) { + if (currentNode.type == SimpleColumnBlockKeys.type) { + return currentNode; + } + currentNode = currentNode.parent; + } + return null; + } + + /// Returns whether the current node is in a [SimpleColumnsBlock]. + bool get isInColumnsBlock => columnsParent != null; + + /// Returns whether the current node is in a [SimpleColumnBlock]. + bool get isInColumnBlock => columnParent != null; +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/columns/simple_columns_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/columns/simple_columns_block_component.dart new file mode 100644 index 0000000000..58ecde5f2f --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/columns/simple_columns_block_component.dart @@ -0,0 +1,275 @@ +import 'dart:math'; + +import 'package:appflowy/plugins/document/presentation/editor_plugins/columns/simple_columns_block_constant.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +// if the children is not provided, it will create two columns by default. +// if the columnCount is provided, it will create the specified number of columns. +Node simpleColumnsNode({ + List? children, + int? columnCount, + double? ratio, +}) { + columnCount ??= 2; + children ??= List.generate( + columnCount, + (index) => simpleColumnNode( + ratio: ratio, + children: [paragraphNode()], + ), + ); + + // check the type of children + for (final child in children) { + if (child.type != SimpleColumnBlockKeys.type) { + Log.error('the type of children must be column, but got ${child.type}'); + } + } + + return Node( + type: SimpleColumnsBlockKeys.type, + children: children, + ); +} + +class SimpleColumnsBlockKeys { + const SimpleColumnsBlockKeys._(); + + static const String type = 'simple_columns'; +} + +class SimpleColumnsBlockComponentBuilder extends BlockComponentBuilder { + SimpleColumnsBlockComponentBuilder({super.configuration}); + + @override + BlockComponentWidget build(BlockComponentContext blockComponentContext) { + final node = blockComponentContext.node; + return ColumnsBlockComponent( + key: node.key, + node: node, + showActions: showActions(node), + configuration: configuration, + actionBuilder: (_, state) => actionBuilder(blockComponentContext, state), + ); + } + + @override + BlockComponentValidate get validate => (node) => node.children.isNotEmpty; +} + +class ColumnsBlockComponent extends BlockComponentStatefulWidget { + const ColumnsBlockComponent({ + super.key, + required super.node, + super.showActions, + super.actionBuilder, + super.actionTrailingBuilder, + super.configuration = const BlockComponentConfiguration(), + }); + + @override + State createState() => ColumnsBlockComponentState(); +} + +class ColumnsBlockComponentState extends State + with SelectableMixin, BlockComponentConfigurable { + @override + BlockComponentConfiguration get configuration => widget.configuration; + + @override + Node get node => widget.node; + + RenderBox? get _renderBox => context.findRenderObject() as RenderBox?; + + final columnsKey = GlobalKey(); + + late final EditorState editorState = context.read(); + + final ScrollController scrollController = ScrollController(); + + final ValueNotifier heightValueNotifier = ValueNotifier(null); + + @override + void initState() { + super.initState(); + _updateColumnsBlock(); + } + + @override + void dispose() { + scrollController.dispose(); + heightValueNotifier.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + Widget child = Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildChildren(), + ); + + child = Align( + alignment: Alignment.topLeft, + child: child, + ); + + child = Padding( + key: columnsKey, + padding: padding, + child: child, + ); + + if (SimpleColumnsBlockConstants.enableDebugBorder) { + child = DecoratedBox( + decoration: BoxDecoration( + border: Border.all( + color: Colors.red, + width: 3.0, + ), + ), + child: child, + ); + } + + // the columns block does not support the block actions and selection + // because the columns block is a layout wrapper, it does not have a content + return NotificationListener( + onNotification: (v) => updateHeightValueNotifier(v), + child: SizeChangedLayoutNotifier(child: child), + ); + } + + List _buildChildren() { + final length = node.children.length; + final children = []; + for (var i = 0; i < length; i++) { + final childNode = node.children[i]; + final double ratio = + childNode.attributes[SimpleColumnBlockKeys.ratio]?.toDouble() ?? + 1.0 / length; + + Widget child = editorState.renderer.build(context, childNode); + + child = Expanded( + flex: (max(ratio, 0.1) * 10000).toInt(), + child: child, + ); + + children.add(child); + + if (i != length - 1) { + children.add( + ValueListenableBuilder( + valueListenable: heightValueNotifier, + builder: (context, height, child) { + return SimpleColumnBlockWidthResizer( + columnNode: childNode, + editorState: editorState, + height: height, + ); + }, + ), + ); + } + } + return children; + } + + // Update the existing columns block data + // if the column ratio is not existing, it will be set to 1.0 / columnCount + void _updateColumnsBlock() { + final transaction = editorState.transaction; + final length = node.children.length; + for (int i = 0; i < length; i++) { + final childNode = node.children[i]; + final ratio = childNode.attributes[SimpleColumnBlockKeys.ratio]; + if (ratio == null) { + transaction.updateNode(childNode, { + ...childNode.attributes, + SimpleColumnBlockKeys.ratio: 1.0 / length, + }); + } + } + if (transaction.operations.isNotEmpty) { + editorState.apply(transaction); + } + } + + bool updateHeightValueNotifier(SizeChangedLayoutNotification notification) { + if (!mounted) return true; + final height = _renderBox?.size.height; + if (heightValueNotifier.value == height) return true; + WidgetsBinding.instance.addPostFrameCallback((_) { + heightValueNotifier.value = height; + }); + return true; + } + + @override + Position start() => Position(path: widget.node.path); + + @override + Position end() => Position(path: widget.node.path, offset: 1); + + @override + Position getPositionInOffset(Offset start) => end(); + + @override + bool get shouldCursorBlink => false; + + @override + CursorStyle get cursorStyle => CursorStyle.cover; + + @override + Rect getBlockRect({ + bool shiftWithBaseOffset = false, + }) { + return getRectsInSelection(Selection.invalid()).first; + } + + @override + Rect? getCursorRectInPosition( + Position position, { + bool shiftWithBaseOffset = false, + }) { + final rects = getRectsInSelection( + Selection.collapsed(position), + shiftWithBaseOffset: shiftWithBaseOffset, + ); + return rects.firstOrNull; + } + + @override + List getRectsInSelection( + Selection selection, { + bool shiftWithBaseOffset = false, + }) { + if (_renderBox == null) { + return []; + } + final parentBox = context.findRenderObject(); + final renderBox = columnsKey.currentContext?.findRenderObject(); + if (parentBox is RenderBox && renderBox is RenderBox) { + return [ + renderBox.localToGlobal(Offset.zero, ancestor: parentBox) & + renderBox.size, + ]; + } + return [Offset.zero & _renderBox!.size]; + } + + @override + Selection getSelectionInRange(Offset start, Offset end) => + Selection.single(path: widget.node.path, startOffset: 0, endOffset: 1); + + @override + Offset localToGlobal(Offset offset, {bool shiftWithBaseOffset = false}) => + _renderBox!.localToGlobal(offset); +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/columns/simple_columns_block_constant.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/columns/simple_columns_block_constant.dart new file mode 100644 index 0000000000..d8820f8613 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/columns/simple_columns_block_constant.dart @@ -0,0 +1,6 @@ +class SimpleColumnsBlockConstants { + const SimpleColumnsBlockConstants._(); + + static const double minimumColumnWidth = 128.0; + static const bool enableDebugBorder = false; +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart index f4aac4fe2c..f108c7e26b 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart @@ -132,6 +132,7 @@ class ClipboardService { final html = await reader.readValue(Formats.htmlText); final inAppJson = await reader.readValue(inAppJsonFormat); final tableJson = await reader.readValue(tableJsonFormat); + final uri = await reader.readValue(Formats.uri); (String, Uint8List?)? image; if (reader.canProvide(Formats.png)) { image = ('png', await reader.readFile(Formats.png)); @@ -144,7 +145,7 @@ class ClipboardService { } return ClipboardServiceData( - plainText: plainText, + plainText: plainText ?? uri?.uri.toString(), html: html, image: image, inAppJson: inAppJson, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_copy_command.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_copy_command.dart index 99211920fa..e56ccfc941 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_copy_command.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_copy_command.dart @@ -1,8 +1,7 @@ import 'dart:convert'; import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/sub_page/sub_page_block_component.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flutter/material.dart'; @@ -98,7 +97,13 @@ Document _buildCopiedDocument( // if the node is a table cell, we will fetch its children instead. filteredNodes.addAll(node.children); } else if (node.type == SimpleTableRowBlockKeys.type) { - // if the node is a table row, we will fetch its children instead. + // if the node is a table row, we will fetch its children's children instead. + filteredNodes.addAll(node.children.expand((e) => e.children)); + } else if (node.type == SimpleColumnBlockKeys.type) { + // if the node is a column block, we will fetch its children instead. + filteredNodes.addAll(node.children); + } else if (node.type == SimpleColumnsBlockKeys.type) { + // if the node is a columns block, we will fetch its children's children instead. filteredNodes.addAll(node.children.expand((e) => e.children)); } else { filteredNodes.add(node); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_paste_command.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_paste_command.dart index ed353103fb..6399d3b11f 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_paste_command.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/custom_paste_command.dart @@ -14,7 +14,9 @@ import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:http/http.dart' as http; import 'package:string_validator/string_validator.dart'; +import 'package:universal_platform/universal_platform.dart'; /// - support /// - desktop @@ -143,6 +145,13 @@ Future doPaste(EditorState editorState) async { } if (plainText != null && plainText.isNotEmpty) { + final currentSelection = editorState.selection; + if (currentSelection == null) { + await editorState.updateSelectionWithReason( + selection, + reason: SelectionUpdateReason.uiEvent, + ); + } await editorState.pasteText(plainText); return Log.info('Pasted plain text'); } @@ -154,17 +163,15 @@ Future _pasteAsLinkPreview( EditorState editorState, String? text, ) async { - // 1. the url should contains a protocol - // 2. the url should not be an image url - if (text == null || - text.isImageUrl() || - !isURL(text, {'require_protocol': true})) { + final isMobile = UniversalPlatform.isMobile; + // the url should contain a protocol + if (text == null || !isURL(text, {'require_protocol': true})) { return false; } final selection = editorState.selection; // Apply the update only when the selection is collapsed - // and at the start of the current line + // and at the start of the current line if (selection == null || !selection.isCollapsed || selection.startIndex != 0) { @@ -173,44 +180,54 @@ Future _pasteAsLinkPreview( final node = editorState.getNodeAtPath(selection.start.path); // Apply the update only when the current node is a paragraph - // and the paragraph is empty + // and the paragraph is empty if (node == null || node.type != ParagraphBlockKeys.type || node.delta?.toPlainText().isNotEmpty == true) { return false; } + if (!isMobile) return false; + final bool isImageUrl; + try { + isImageUrl = await _isImageUrl(text); + } catch (e) { + Log.info('unable to get content header'); + return false; + } - // 1. insert the text with link format - // 2. convert it the link preview node - final textTransaction = editorState.transaction; - textTransaction.insertText( - node, - 0, - text, - attributes: {AppFlowyRichTextKeys.href: text}, - ); + if (!isImageUrl) return false; + + // insert the text with link format + final textTransaction = editorState.transaction + ..insertText( + node, + 0, + text, + attributes: {AppFlowyRichTextKeys.href: text}, + ); await editorState.apply( textTransaction, skipHistoryDebounce: true, ); - final linkPreviewTransaction = editorState.transaction; - final insertedNodes = [ - linkPreviewNode(url: text), + // convert it to image or link preview node + final replacementInsertedNodes = [ + isImageUrl ? imageNode(url: text) : linkPreviewNode(url: text), // if the next node is null, insert a empty paragraph node if (node.next == null) paragraphNode(), ]; - linkPreviewTransaction.insertNodes( - selection.start.path, - insertedNodes, - ); - linkPreviewTransaction.deleteNode(node); - linkPreviewTransaction.afterSelection = Selection.collapsed( - Position( - path: node.path.next, - ), - ); - await editorState.apply(linkPreviewTransaction); + + final replacementTransaction = editorState.transaction + ..insertNodes( + selection.start.path, + replacementInsertedNodes, + ) + ..deleteNode(node) + ..afterSelection = Selection.collapsed( + Position(path: node.path.next), + ); + + await editorState.apply(replacementTransaction); return true; } @@ -235,3 +252,18 @@ Future doPlainPaste(EditorState editorState) async { Log.info('unable to parse the clipboard content'); return; } + +Future _isImageUrl(String text) async { + if (isNotImageUrl(text)) return false; + final response = await http.head(Uri.parse(text)); + + if (response.statusCode == 200) { + final contentType = response.headers['content-type']; + if (contentType != null) { + return contentType.startsWith('image/') && + defaultImageExtensions.any(contentType.contains); + } + } + + throw 'bad status code'; +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_block_link.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_block_link.dart index fbd9914c1d..c47c0c967d 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_block_link.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_block_link.dart @@ -43,13 +43,11 @@ extension PasteFromBlockLink on EditorState { node, selection.startIndex, MentionBlockKeys.mentionChar, - attributes: { - MentionBlockKeys.mention: { - MentionBlockKeys.type: MentionType.page.name, - MentionBlockKeys.blockId: blockId, - MentionBlockKeys.pageId: pageId, - }, - }, + attributes: MentionBlockKeys.buildMentionPageAttributes( + mentionType: MentionType.page, + pageId: pageId, + blockId: blockId, + ), ); await apply(transaction); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_html.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_html.dart index 87259d5981..3f11759545 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_html.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_html.dart @@ -1,24 +1,117 @@ +import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_plain_text.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; +import 'package:appflowy/shared/markdown_to_document.dart'; +import 'package:appflowy/shared/patterns/common_patterns.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:html2md/html2md.dart' as html2md; extension PasteFromHtml on EditorState { Future pasteHtml(String html) async { - final nodes = htmlToDocument(html).root.children.toList(); - // remove the front and back empty line - while (nodes.isNotEmpty && nodes.first.delta?.isEmpty == true) { - nodes.removeAt(0); - } - while (nodes.isNotEmpty && nodes.last.delta?.isEmpty == true) { - nodes.removeLast(); - } + final nodes = convertHtmlToNodes(html); // if there's no nodes being converted successfully, return false if (nodes.isEmpty) { return false; } if (nodes.length == 1) { await pasteSingleLineNode(nodes.first); + checkToShowPasteAsMenu(nodes.first); } else { await pasteMultiLineNodes(nodes.toList()); } return true; } + + // Convert the html to document nodes. + // For the google docs table, it will be fallback to the markdown parser. + List convertHtmlToNodes(String html) { + List nodes = htmlToDocument(html).root.children.toList(); + + // 1. remove the front and back empty line + while (nodes.isNotEmpty && nodes.first.delta?.isEmpty == true) { + nodes.removeAt(0); + } + while (nodes.isNotEmpty && nodes.last.delta?.isEmpty == true) { + nodes.removeLast(); + } + + // 2. replace the legacy table nodes with the new simple table nodes + for (int i = 0; i < nodes.length; i++) { + final node = nodes[i]; + if (node.type == TableBlockKeys.type) { + nodes[i] = _convertTableToSimpleTable(node); + } + } + + // 3. verify the nodes is empty or contains google table flag + // The table from Google Docs will contain the flag 'Google Table' + const googleDocsFlag = 'docs-internal-guid-'; + final isPasteFromGoogleDocs = html.contains(googleDocsFlag); + final isPasteFromAppleNotes = appleNotesRegex.hasMatch(html); + final containsTable = nodes.any( + (node) => + [TableBlockKeys.type, SimpleTableBlockKeys.type].contains(node.type), + ); + if ((nodes.isEmpty || isPasteFromGoogleDocs || containsTable) && + !isPasteFromAppleNotes) { + // fallback to the markdown parser + final markdown = html2md.convert(html); + nodes = customMarkdownToDocument(markdown, tableWidth: 200) + .root + .children + .toList(); + } + + // 4. check if the first node and the last node is bold, because google docs will wrap the table with bold tags + if (isPasteFromGoogleDocs) { + if (nodes.isNotEmpty && nodes.first.delta?.toPlainText() == '**') { + nodes.removeAt(0); + } + if (nodes.isNotEmpty && nodes.last.delta?.toPlainText() == '**') { + nodes.removeLast(); + } + } + + return nodes; + } + + // convert the legacy table node to the new simple table node + // from type 'table' to type 'simple_table' + Node _convertTableToSimpleTable(Node node) { + if (node.type != TableBlockKeys.type) { + return node; + } + + // the table node should contains colsLen and rowsLen + final colsLen = node.attributes[TableBlockKeys.colsLen]; + final rowsLen = node.attributes[TableBlockKeys.rowsLen]; + if (colsLen == null || rowsLen == null) { + return node; + } + + final rows = >[]; + final children = node.children; + for (var i = 0; i < rowsLen; i++) { + final row = []; + for (var j = 0; j < colsLen; j++) { + final cell = children + .where( + (n) => + n.attributes[TableCellBlockKeys.rowPosition] == i && + n.attributes[TableCellBlockKeys.colPosition] == j, + ) + .firstOrNull; + row.add( + simpleTableCellBlockNode( + children: cell?.children.map((e) => e.deepCopy()).toList() ?? + [paragraphNode()], + ), + ); + } + rows.add(row); + } + + return simpleTableBlockNode( + children: rows.map((e) => simpleTableRowBlockNode(children: e)).toList(), + ); + } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_image.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_image.dart index 6e6c9b1772..d086f36bed 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_image.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_image.dart @@ -73,7 +73,6 @@ extension PasteFromImage on EditorState { Log.info('unsupported format: $format'); if (UniversalPlatform.isMobile) { showToastNotification( - context, message: LocaleKeys.document_imageBlock_error_invalidImageFormat.tr(), ); } @@ -112,7 +111,6 @@ extension PasteFromImage on EditorState { if (errorMessage != null && context.mounted) { showToastNotification( - context, message: errorMessage, ); return false; @@ -131,7 +129,6 @@ extension PasteFromImage on EditorState { Log.error('cannot copy image file', e); if (context.mounted) { showToastNotification( - context, message: LocaleKeys.document_imageBlock_error_invalidImage.tr(), ); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_in_app_json.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_in_app_json.dart index 70948cd962..d4db86d80c 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_in_app_json.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_in_app_json.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; @@ -7,6 +8,17 @@ extension PasteFromInAppJson on EditorState { Future pasteInAppJson(String inAppJson) async { try { final nodes = Document.fromJson(jsonDecode(inAppJson)).root.children; + + // skip pasting a table block to another table block + final containsTable = + nodes.any((node) => node.type == SimpleTableBlockKeys.type); + if (containsTable) { + final selectedNodes = getSelectedNodes(withCopy: false); + if (selectedNodes.any((node) => node.parentTableNode != null)) { + return false; + } + } + if (nodes.isEmpty) { Log.info('pasteInAppJson: nodes is empty'); return false; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_plain_text.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_plain_text.dart index 90ed451128..fcb12cefa5 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_plain_text.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_plain_text.dart @@ -1,6 +1,8 @@ +import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/paste_as/paste_as_menu.dart'; import 'package:appflowy/shared/markdown_to_document.dart'; import 'package:appflowy/shared/patterns/common_patterns.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:universal_platform/universal_platform.dart'; extension PasteFromPlainText on EditorState { Future pastePlainText(String plainText) async { @@ -32,12 +34,16 @@ extension PasteFromPlainText on EditorState { await deleteSelectionIfNeeded(); + /// try to parse the plain text as markdown final nodes = customMarkdownToDocument(plainText).root.children; if (nodes.isEmpty) { + /// if the markdown parser failed, fallback to the plain text parser + await pastePlainText(plainText); return; } if (nodes.length == 1) { await pasteSingleLineNode(nodes.first); + checkToShowPasteAsMenu(nodes.first); } else { await pasteMultiLineNodes(nodes.toList()); } @@ -62,6 +68,29 @@ extension PasteFromPlainText on EditorState { AppFlowyRichTextKeys.href: plainText, }); await apply(transaction); + checkToShowPasteAsMenu(node); return true; } + + void checkToShowPasteAsMenu(Node node) { + if (selection == null || !selection!.isCollapsed) return; + if (UniversalPlatform.isMobile) return; + final href = _getLinkFromNode(node); + if (href != null) { + final context = document.root.context; + if (context != null && context.mounted) { + PasteAsMenuService(context: context, editorState: this).show(href); + } + } + } + + String? _getLinkFromNode(Node node) { + final delta = node.delta; + if (delta == null) return null; + final inserts = delta.whereType(); + if (inserts.isEmpty || inserts.length > 1) return null; + final link = inserts.first.attributes?.href; + if (link != null) return inserts.first.text; + return null; + } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart index 05c891bd1e..96d39d7500 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart @@ -11,6 +11,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style import 'package:appflowy/shared/appflowy_network_image.dart'; import 'package:appflowy/shared/flowy_gradient_colors.dart'; import 'package:appflowy/shared/google_fonts_extension.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/util/string_extension.dart'; import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart'; import 'package:appflowy/workspace/application/view/view_bloc.dart'; @@ -36,12 +37,14 @@ class DocumentImmersiveCover extends StatefulWidget { super.key, required this.view, required this.userProfilePB, + required this.tabs, this.fixedTitle, }); final ViewPB view; final UserProfilePB userProfilePB; final String? fixedTitle; + final List tabs; @override State createState() => _DocumentImmersiveCoverState(); @@ -227,11 +230,14 @@ class _DocumentImmersiveCoverState extends State { value: pageStyleIconBloc, child: Expanded( child: FlowyIconEmojiPicker( + initialType: icon.type.toPickerTabType(), + tabs: widget.tabs, + documentId: widget.view.id, onSelectedEmoji: (r) { pageStyleIconBloc.add( - PageStyleIconEvent.updateIcon(r, true), + PageStyleIconEvent.updateIcon(r.data, true), ); - Navigator.pop(context); + if (!r.keepOpen) Navigator.pop(context); }, ), ), diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/cover/document_immersive_cover_bloc.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/cover/document_immersive_cover_bloc.dart index ee8952dc11..3006fc3104 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/cover/document_immersive_cover_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/cover/document_immersive_cover_bloc.dart @@ -1,6 +1,7 @@ import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy/workspace/application/view/view_listener.dart'; +import 'package:appflowy/workspace/application/view/view_service.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -19,9 +20,13 @@ class DocumentImmersiveCoverBloc (event, emit) async { await event.when( initial: () async { + final latestView = await ViewBackendService.getView(view.id); add( DocumentImmersiveCoverEvent.updateCoverAndIcon( - view.cover, + latestView.fold( + (s) => s.cover, + (e) => view.cover, + ), EmojiIconData.fromViewIconPB(view.icon), view.name, ), diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/database/database_view_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/database/database_view_block_component.dart index cd150ff1c1..87c2815091 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/database/database_view_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/database/database_view_block_component.dart @@ -1,5 +1,9 @@ +import 'dart:async'; + import 'package:appflowy/plugins/database/widgets/database_view_widget.dart'; +import 'package:appflowy/plugins/document/presentation/compact_mode_event.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/base/built_in_page_widget.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -13,8 +17,14 @@ class DatabaseBlockKeys { static const String parentID = 'parent_id'; static const String viewID = 'view_id'; + static const String enableCompactMode = 'enable_compact_mode'; } +const overflowTypes = { + DatabaseBlockKeys.gridType, + DatabaseBlockKeys.boardType, +}; + class DatabaseViewBlockComponentBuilder extends BlockComponentBuilder { DatabaseViewBlockComponentBuilder({ super.configuration, @@ -32,6 +42,10 @@ class DatabaseViewBlockComponentBuilder extends BlockComponentBuilder { blockComponentContext, state, ), + actionTrailingBuilder: (context, state) => actionTrailingBuilder( + blockComponentContext, + state, + ), ); } @@ -48,6 +62,7 @@ class DatabaseBlockComponentWidget extends BlockComponentStatefulWidget { required super.node, super.showActions, super.actionBuilder, + super.actionTrailingBuilder, super.configuration = const BlockComponentConfiguration(), }); @@ -65,32 +80,65 @@ class _DatabaseBlockComponentWidgetState @override BlockComponentConfiguration get configuration => widget.configuration; + late StreamSubscription compactModeSubscription; + EditorState? editorState; + + @override + void initState() { + super.initState(); + compactModeSubscription = + compactModeEventBus.on().listen((event) { + if (event.id != node.id) return; + final newAttributes = { + ...node.attributes, + DatabaseBlockKeys.enableCompactMode: event.enable, + }; + final theEditorState = editorState; + if (theEditorState == null) return; + final transaction = theEditorState.transaction; + transaction.updateNode(node, newAttributes); + theEditorState.apply(transaction); + }); + } + + @override + void dispose() { + super.dispose(); + compactModeSubscription.cancel(); + editorState = null; + } + @override Widget build(BuildContext context) { final editorState = Provider.of(context, listen: false); + this.editorState = editorState; Widget child = BuiltInPageWidget( node: widget.node, editorState: editorState, - builder: (view) => DatabaseViewWidget(key: ValueKey(view.id), view: view), - ); - - child = Padding( - padding: padding, - child: FocusScope( - skipTraversal: true, - onFocusChange: (value) { - if (value && keepEditorFocusNotifier.value == 0) { - context.read().selection = null; - } - }, - child: child, + builder: (view) => Provider.value( + value: ReferenceState(true), + child: DatabaseViewWidget( + key: ValueKey(view.id), + view: view, + actionBuilder: widget.actionBuilder, + showActions: widget.showActions, + node: widget.node, + ), ), ); - if (widget.showActions && widget.actionBuilder != null) { - child = BlockComponentActionWrapper( - node: widget.node, - actionBuilder: widget.actionBuilder!, + child = FocusScope( + skipTraversal: true, + onFocusChange: (value) { + if (value && keepEditorFocusNotifier.value == 0) { + context.read().selection = null; + } + }, + child: child, + ); + + if (!editorState.editable) { + child = IgnorePointer( child: child, ); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/delta/text_delta_extension.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/delta/text_delta_extension.dart index 5beba66c32..905c033bda 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/delta/text_delta_extension.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/delta/text_delta_extension.dart @@ -1,3 +1,4 @@ +import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/custom_link_parser.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; @@ -27,9 +28,15 @@ extension TextDeltaExtension on Delta { if (op.text == MentionBlockKeys.mentionChar) { final mention = attributes?[MentionBlockKeys.mention]; final mentionPageId = mention?[MentionBlockKeys.pageId]; + final mentionType = mention?[MentionBlockKeys.type]; if (mentionPageId != null) { text += await getMentionPageName(mentionPageId); continue; + } else if (mentionType == MentionType.externalLink.name) { + final url = mention?[MentionBlockKeys.url] ?? ''; + final info = await LinkInfoCache.get(url); + text += info?.title ?? url; + continue; } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/color_picker.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/color_picker.dart new file mode 100644 index 0000000000..162c7a1c34 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/color_picker.dart @@ -0,0 +1,330 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +// ignore: implementation_imports +import 'package:appflowy_editor/src/editor/toolbar/desktop/items/utils/overlay_util.dart'; +import 'package:flutter/material.dart'; + +class ColorPicker extends StatefulWidget { + const ColorPicker({ + super.key, + required this.title, + required this.selectedColorHex, + required this.onSubmittedColorHex, + required this.colorOptions, + this.resetText, + this.customColorHex, + this.resetIconName, + this.showClearButton = false, + }); + + final String title; + final String? selectedColorHex; + final String? customColorHex; + final void Function(String? color, bool isCustomColor) onSubmittedColorHex; + final String? resetText; + final String? resetIconName; + final bool showClearButton; + + final List colorOptions; + + @override + State createState() => _ColorPickerState(); +} + +class _ColorPickerState extends State { + final TextEditingController _colorHexController = TextEditingController(); + final TextEditingController _colorOpacityController = TextEditingController(); + + @override + void initState() { + super.initState(); + final selectedColorHex = widget.selectedColorHex, + customColorHex = widget.customColorHex; + _colorHexController.text = + _extractColorHex(customColorHex ?? selectedColorHex) ?? 'FFFFFF'; + _colorOpacityController.text = + _convertHexToOpacity(customColorHex ?? selectedColorHex) ?? '100'; + } + + @override + Widget build(BuildContext context) { + return basicOverlay( + context, + width: 300, + height: 250, + children: [ + EditorOverlayTitle(text: widget.title), + const SizedBox(height: 6), + widget.showClearButton && + widget.resetText != null && + widget.resetIconName != null + ? ResetColorButton( + resetText: widget.resetText!, + resetIconName: widget.resetIconName!, + onPressed: (color) => + widget.onSubmittedColorHex.call(color, false), + ) + : const SizedBox.shrink(), + CustomColorItem( + colorController: _colorHexController, + opacityController: _colorOpacityController, + onSubmittedColorHex: (color) => + widget.onSubmittedColorHex.call(color, true), + ), + _buildColorItems( + widget.colorOptions, + widget.selectedColorHex, + ), + ], + ); + } + + Widget _buildColorItems( + List options, + String? selectedColor, + ) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: options + .map((e) => _buildColorItem(e, e.colorHex == selectedColor)) + .toList(), + ); + } + + Widget _buildColorItem(ColorOption option, bool isChecked) { + return SizedBox( + height: 36, + child: TextButton.icon( + onPressed: () { + widget.onSubmittedColorHex(option.colorHex, false); + }, + icon: SizedBox.square( + dimension: 12, + child: Container( + decoration: BoxDecoration( + color: option.colorHex.tryToColor(), + shape: BoxShape.circle, + ), + ), + ), + style: buildOverlayButtonStyle(context), + label: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + option.name, + softWrap: false, + maxLines: 1, + overflow: TextOverflow.fade, + style: TextStyle( + color: Theme.of(context).textTheme.labelLarge?.color, + ), + ), + ), + // checkbox + if (isChecked) const FlowySvg(FlowySvgs.toolbar_check_m), + ], + ), + ), + ); + } + + String? _convertHexToOpacity(String? colorHex) { + if (colorHex == null) return null; + final opacityHex = colorHex.substring(2, 4); + final opacity = int.parse(opacityHex, radix: 16) / 2.55; + return opacity.toStringAsFixed(0); + } + + String? _extractColorHex(String? colorHex) { + if (colorHex == null) return null; + return colorHex.substring(4); + } +} + +class ResetColorButton extends StatelessWidget { + const ResetColorButton({ + super.key, + required this.resetText, + required this.resetIconName, + required this.onPressed, + }); + + final Function(String? color) onPressed; + final String resetText; + final String resetIconName; + + @override + Widget build(BuildContext context) { + return SizedBox( + width: double.infinity, + height: 32, + child: TextButton.icon( + onPressed: () => onPressed(null), + icon: EditorSvg( + name: resetIconName, + width: 13, + height: 13, + color: Theme.of(context).iconTheme.color, + ), + label: Text( + resetText, + style: TextStyle( + color: Theme.of(context).hintColor, + ), + textAlign: TextAlign.left, + ), + style: ButtonStyle( + backgroundColor: WidgetStateProperty.resolveWith( + (Set states) { + if (states.contains(WidgetState.hovered)) { + return Theme.of(context).hoverColor; + } + return Colors.transparent; + }, + ), + alignment: Alignment.centerLeft, + ), + ), + ); + } +} + +class CustomColorItem extends StatefulWidget { + const CustomColorItem({ + super.key, + required this.colorController, + required this.opacityController, + required this.onSubmittedColorHex, + }); + + final TextEditingController colorController; + final TextEditingController opacityController; + final void Function(String color) onSubmittedColorHex; + + @override + State createState() => _CustomColorItemState(); +} + +class _CustomColorItemState extends State { + @override + Widget build(BuildContext context) { + return ExpansionTile( + tilePadding: const EdgeInsets.only(left: 8), + shape: Border.all( + color: Colors.transparent, + ), // remove the default border when it is expanded + title: Row( + children: [ + // color sample box + SizedBox.square( + dimension: 12, + child: Container( + decoration: BoxDecoration( + color: Color( + int.tryParse( + _combineColorHexAndOpacity( + widget.colorController.text, + widget.opacityController.text, + ), + ) ?? + 0xFFFFFFFF, + ), + shape: BoxShape.circle, + ), + ), + ), + const SizedBox(width: 8), + Expanded( + child: Text( + AppFlowyEditorL10n.current.customColor, + style: Theme.of(context).textTheme.labelLarge, + // same style as TextButton.icon + ), + ), + ], + ), + children: [ + const SizedBox(height: 6), + _customColorDetailsTextField( + labelText: AppFlowyEditorL10n.current.hexValue, + controller: widget.colorController, + // update the color sample box when the text changes + onChanged: (_) => setState(() {}), + onSubmitted: _submitCustomColorHex, + ), + const SizedBox(height: 10), + _customColorDetailsTextField( + labelText: AppFlowyEditorL10n.current.opacity, + controller: widget.opacityController, + // update the color sample box when the text changes + onChanged: (_) => setState(() {}), + onSubmitted: _submitCustomColorHex, + ), + const SizedBox(height: 6), + ], + ); + } + + Widget _customColorDetailsTextField({ + required String labelText, + required TextEditingController controller, + Function(String)? onChanged, + Function(String)? onSubmitted, + }) { + return Padding( + padding: const EdgeInsets.only(right: 3), + child: TextField( + controller: controller, + decoration: InputDecoration( + labelText: labelText, + border: OutlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).colorScheme.outline, + ), + ), + ), + style: Theme.of(context).textTheme.bodyMedium, + onChanged: onChanged, + onSubmitted: onSubmitted, + ), + ); + } + + String _combineColorHexAndOpacity(String colorHex, String opacity) { + colorHex = _fixColorHex(colorHex); + opacity = _fixOpacity(opacity); + final opacityHex = (int.parse(opacity) * 2.55).round().toRadixString(16); + return '0x$opacityHex$colorHex'; + } + + String _fixColorHex(String colorHex) { + if (colorHex.length > 6) { + colorHex = colorHex.substring(0, 6); + } + if (int.tryParse(colorHex, radix: 16) == null) { + colorHex = 'FFFFFF'; + } + return colorHex; + } + + String _fixOpacity(String opacity) { + // if opacity is 0 - 99, return it + // otherwise return 100 + final RegExp regex = RegExp('^(0|[1-9][0-9]?)'); + if (regex.hasMatch(opacity)) { + return opacity; + } else { + return '100'; + } + } + + void _submitCustomColorHex(String value) { + final String color = _combineColorHexAndOpacity( + widget.colorController.text, + widget.opacityController.text, + ); + widget.onSubmittedColorHex(color); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/desktop_floating_toolbar.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/desktop_floating_toolbar.dart new file mode 100644 index 0000000000..03fc12a37c --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/desktop_floating_toolbar.dart @@ -0,0 +1,132 @@ +import 'package:appflowy/plugins/document/presentation/editor_plugins/base/toolbar_extension.dart'; +import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter/material.dart'; + +import 'toolbar_animation.dart'; + +class DesktopFloatingToolbar extends StatefulWidget { + const DesktopFloatingToolbar({ + super.key, + required this.editorState, + required this.child, + required this.onDismiss, + this.enableAnimation = true, + }); + + final EditorState editorState; + final Widget child; + final VoidCallback onDismiss; + final bool enableAnimation; + + @override + State createState() => _DesktopFloatingToolbarState(); +} + +class _DesktopFloatingToolbarState extends State { + EditorState get editorState => widget.editorState; + + _Position? position; + final toolbarController = getIt(); + + @override + void initState() { + super.initState(); + final selection = editorState.selection; + if (selection == null || selection.isCollapsed) { + return; + } + final selectionRect = editorState.selectionRects(); + if (selectionRect.isEmpty) return; + position = calculateSelectionMenuOffset(selectionRect.first); + toolbarController._addCallback(dismiss); + } + + @override + void dispose() { + toolbarController._removeCallback(dismiss); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (position == null) return Container(); + return Positioned( + left: position!.left, + top: position!.top, + right: position!.right, + child: widget.enableAnimation + ? ToolbarAnimationWidget(child: widget.child) + : widget.child, + ); + } + + void dismiss() { + widget.onDismiss.call(); + } + + _Position calculateSelectionMenuOffset( + Rect rect, + ) { + const toolbarHeight = 40, topLimit = toolbarHeight + 8; + final bool isLongMenu = onlyShowInSingleSelectionAndTextType(editorState); + final editorOffset = + editorState.renderBox?.localToGlobal(Offset.zero) ?? Offset.zero; + final editorSize = editorState.renderBox?.size ?? Size.zero; + final menuWidth = + isLongMenu ? (isNarrowWindow(editorState) ? 490.0 : 660.0) : 420.0; + final editorRect = editorOffset & editorSize; + final left = rect.left, leftStart = 50; + final top = + rect.top < topLimit ? rect.bottom + topLimit : rect.top - topLimit; + if (left + menuWidth > editorRect.right) { + return _Position( + editorRect.right - menuWidth, + top, + null, + ); + } else if (rect.left - leftStart > 0) { + return _Position(rect.left - leftStart, top, null); + } else { + return _Position(rect.left, top, null); + } + } +} + +class _Position { + _Position(this.left, this.top, this.right); + + final double? left; + final double? top; + final double? right; +} + +class FloatingToolbarController { + final Set _dismissCallbacks = {}; + final Set _displayListeners = {}; + + void _addCallback(VoidCallback callback) { + _dismissCallbacks.add(callback); + for (final listener in Set.of(_displayListeners)) { + listener.call(); + } + } + + void _removeCallback(VoidCallback callback) => + _dismissCallbacks.remove(callback); + + bool get isToolbarShowing => _dismissCallbacks.isNotEmpty; + + void addDisplayListener(VoidCallback listener) => + _displayListeners.add(listener); + + void removeDisplayListener(VoidCallback listener) => + _displayListeners.remove(listener); + + void hideToolbar() { + if (_dismissCallbacks.isEmpty) return; + for (final callback in _dismissCallbacks) { + callback.call(); + } + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_create_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_create_menu.dart new file mode 100644 index 0000000000..002d569c7b --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_create_menu.dart @@ -0,0 +1,320 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_styles.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/toolbar_item/custom_link_toolbar_item.dart'; +import 'package:appflowy/plugins/shared/share/constants.dart'; +import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'link_search_text_field.dart'; + +class LinkCreateMenu extends StatefulWidget { + const LinkCreateMenu({ + super.key, + required this.editorState, + required this.onSubmitted, + required this.onDismiss, + required this.alignment, + required this.currentViewId, + required this.initialText, + }); + + final EditorState editorState; + final void Function(String link, bool isPage) onSubmitted; + final VoidCallback onDismiss; + final String currentViewId; + final String initialText; + final LinkMenuAlignment alignment; + + @override + State createState() => _LinkCreateMenuState(); +} + +class _LinkCreateMenuState extends State { + late LinkSearchTextField searchTextField = LinkSearchTextField( + currentViewId: widget.currentViewId, + initialSearchText: widget.initialText, + onEnter: () { + searchTextField.onSearchResult( + onLink: () => onSubmittedLink(), + onRecentViews: () => + onSubmittedPageLink(searchTextField.currentRecentView), + onSearchViews: () => + onSubmittedPageLink(searchTextField.currentSearchedView), + onEmpty: () {}, + ); + }, + onEscape: widget.onDismiss, + onDataRefresh: () { + if (mounted) setState(() {}); + }, + ); + + bool get isTextfieldEnable => searchTextField.isTextfieldEnable; + + String get searchText => searchTextField.searchText; + + bool get showAtTop => widget.alignment.isTop; + + bool showErrorText = false; + + @override + void initState() { + super.initState(); + searchTextField.requestFocus(); + searchTextField.searchRecentViews(); + final focusNode = searchTextField.focusNode; + bool hasFocus = focusNode.hasFocus; + focusNode.addListener(() { + if (hasFocus != focusNode.hasFocus && mounted) { + setState(() { + hasFocus = focusNode.hasFocus; + }); + } + }); + } + + @override + void dispose() { + searchTextField.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 320, + child: Column( + children: showAtTop + ? [ + searchTextField.buildResultContainer( + margin: EdgeInsets.only(bottom: 2), + context: context, + onLinkSelected: onSubmittedLink, + onPageLinkSelected: onSubmittedPageLink, + ), + buildSearchContainer(), + ] + : [ + buildSearchContainer(), + searchTextField.buildResultContainer( + margin: EdgeInsets.only(top: 2), + context: context, + onLinkSelected: onSubmittedLink, + onPageLinkSelected: onSubmittedPageLink, + ), + ], + ), + ); + } + + Widget buildSearchContainer() { + return Container( + width: 320, + decoration: buildToolbarLinkDecoration(context), + padding: EdgeInsets.all(8), + child: ValueListenableBuilder( + valueListenable: searchTextField.textEditingController, + builder: (context, _, __) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: searchTextField.buildTextField(context: context), + ), + HSpace(8), + FlowyTextButton( + LocaleKeys.document_toolbar_insert.tr(), + mainAxisAlignment: MainAxisAlignment.center, + padding: EdgeInsets.zero, + constraints: BoxConstraints(maxWidth: 72, minHeight: 32), + fontSize: 14, + fontColor: Colors.white, + fillColor: LinkStyle.fillThemeThick, + hoverColor: LinkStyle.fillThemeThick.withAlpha(200), + lineHeight: 20 / 14, + fontWeight: FontWeight.w600, + onPressed: onSubmittedLink, + ), + ], + ), + if (showErrorText) + Padding( + padding: const EdgeInsets.only(top: 4), + child: FlowyText.regular( + LocaleKeys.document_plugins_file_networkUrlInvalid.tr(), + color: LinkStyle.textStatusError, + fontSize: 12, + figmaLineHeight: 16, + ), + ), + ], + ); + }, + ), + ); + } + + void onSubmittedLink() { + if (!isTextfieldEnable) { + setState(() { + showErrorText = true; + }); + return; + } + widget.onSubmitted(searchText, false); + } + + void onSubmittedPageLink(ViewPB view) async { + final workspaceId = context + .read() + ?.state + .currentWorkspace + ?.workspaceId ?? + ''; + final link = ShareConstants.buildShareUrl( + workspaceId: workspaceId, + viewId: view.id, + ); + widget.onSubmitted(link, true); + } +} + +void showLinkCreateMenu( + BuildContext context, + EditorState editorState, + Selection selection, + String currentViewId, +) { + if (!context.mounted) return; + final (left, top, right, bottom, alignment) = _getPosition(editorState); + + final node = editorState.getNodeAtPath(selection.end.path); + if (node == null) { + return; + } + final selectedText = editorState.getTextInSelection(selection).join(); + + OverlayEntry? overlay; + + void dismissOverlay() { + keepEditorFocusNotifier.decrease(); + overlay?.remove(); + overlay = null; + } + + keepEditorFocusNotifier.increase(); + overlay = FullScreenOverlayEntry( + top: top, + bottom: bottom, + left: left, + right: right, + dismissCallback: () => keepEditorFocusNotifier.decrease(), + builder: (context) { + return LinkCreateMenu( + alignment: alignment, + initialText: selectedText, + currentViewId: currentViewId, + editorState: editorState, + onSubmitted: (link, isPage) async { + await editorState.formatDelta(selection, { + BuiltInAttributeKey.href: link, + kIsPageLink: isPage, + }); + await editorState.updateSelectionWithReason( + null, + reason: SelectionUpdateReason.uiEvent, + ); + dismissOverlay(); + }, + onDismiss: dismissOverlay, + ); + }, + ).build(); + + Overlay.of(context, rootOverlay: true).insert(overlay!); +} + +// get a proper position for link menu +( + double? left, + double? top, + double? right, + double? bottom, + LinkMenuAlignment alignment, +) _getPosition( + EditorState editorState, +) { + final rect = editorState.selectionRects().first; + const menuHeight = 222.0, menuWidth = 320.0; + + double? left, right, top, bottom; + LinkMenuAlignment alignment = LinkMenuAlignment.topLeft; + final editorOffset = editorState.renderBox!.localToGlobal(Offset.zero), + editorSize = editorState.renderBox!.size; + final editorBottom = editorSize.height + editorOffset.dy, + editorRight = editorSize.width + editorOffset.dx; + final overflowBottom = rect.bottom + menuHeight > editorBottom, + overflowTop = rect.top - menuHeight < 0, + overflowLeft = rect.left - menuWidth < 0, + overflowRight = rect.right + menuWidth > editorRight; + + if (overflowTop && !overflowBottom) { + /// show at bottom + top = rect.bottom; + } else if (overflowBottom && !overflowTop) { + /// show at top + bottom = editorBottom - rect.top; + } else if (!overflowTop && !overflowBottom) { + /// show at bottom + top = rect.bottom; + } else { + top = 0; + } + + if (overflowLeft && !overflowRight) { + /// show at right + left = rect.left; + } else if (overflowRight && !overflowLeft) { + /// show at left + right = editorRight - rect.right; + } else if (!overflowLeft && !overflowRight) { + /// show at right + left = rect.left; + } else { + left = 0; + } + + if (left != null && top != null) { + alignment = LinkMenuAlignment.bottomRight; + } else if (left != null && bottom != null) { + alignment = LinkMenuAlignment.topRight; + } else if (right != null && top != null) { + alignment = LinkMenuAlignment.bottomLeft; + } else if (right != null && bottom != null) { + alignment = LinkMenuAlignment.topLeft; + } + + return (left, top, right, bottom, alignment); +} + +ShapeDecoration buildToolbarLinkDecoration( + BuildContext context, { + double radius = 12.0, +}) { + final theme = AppFlowyTheme.of(context); + return ShapeDecoration( + color: theme.surfaceColorScheme.primary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(radius), + ), + shadows: theme.shadow.small, + ); +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_edit_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_edit_menu.dart new file mode 100644 index 0000000000..e90ee22a80 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_edit_menu.dart @@ -0,0 +1,516 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/toolbar_item/custom_link_toolbar_item.dart'; +import 'package:appflowy/plugins/shared/share/constants.dart'; +import 'package:appflowy/user/application/user_service.dart'; +import 'package:appflowy/util/theme_extension.dart'; +import 'package:appflowy/workspace/application/view/view_service.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_result/appflowy_result.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +// ignore: implementation_imports +import 'package:appflowy_editor/src/editor/util/link_util.dart'; +import 'package:flutter/services.dart'; +import 'link_create_menu.dart'; +import 'link_search_text_field.dart'; +import 'link_styles.dart'; + +class LinkEditMenu extends StatefulWidget { + const LinkEditMenu({ + super.key, + required this.linkInfo, + required this.onDismiss, + required this.onApply, + required this.onRemoveLink, + required this.currentViewId, + }); + + final LinkInfo linkInfo; + final ValueChanged onApply; + final ValueChanged onRemoveLink; + final VoidCallback onDismiss; + final String currentViewId; + + @override + State createState() => _LinkEditMenuState(); +} + +class _LinkEditMenuState extends State { + ValueChanged get onRemoveLink => widget.onRemoveLink; + + VoidCallback get onDismiss => widget.onDismiss; + + late TextEditingController linkNameController = + TextEditingController(text: linkInfo.name); + late FocusNode textFocusNode = FocusNode(onKeyEvent: onFocusKeyEvent); + late FocusNode menuFocusNode = FocusNode(onKeyEvent: onFocusKeyEvent); + late LinkInfo linkInfo = widget.linkInfo; + late LinkSearchTextField searchTextField; + bool isShowingSearchResult = false; + ViewPB? currentView; + bool showErrorText = false; + + @override + void initState() { + super.initState(); + final isPageLink = linkInfo.isPage; + if (isPageLink) getPageView(); + searchTextField = LinkSearchTextField( + initialSearchText: isPageLink ? '' : linkInfo.link, + initialViewId: linkInfo.viewId, + currentViewId: widget.currentViewId, + onEnter: onConfirm, + onEscape: () { + if (isShowingSearchResult) { + hideSearchResult(); + } else { + onDismiss(); + } + }, + onDataRefresh: () { + if (mounted) setState(() {}); + }, + )..searchRecentViews(); + makeSureHasFocus(); + } + + @override + void dispose() { + linkNameController.dispose(); + textFocusNode.dispose(); + menuFocusNode.dispose(); + searchTextField.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final showingRecent = + searchTextField.showingRecent && isShowingSearchResult; + final errorHeight = showErrorText ? 20.0 : 0.0; + return GestureDetector( + onTap: onDismiss, + child: Focus( + focusNode: menuFocusNode, + child: Container( + width: 400, + height: 250 + (showingRecent ? 32 : 0), + color: Colors.white.withAlpha(1), + child: Stack( + children: [ + GestureDetector( + onTap: hideSearchResult, + child: Container( + width: 400, + height: 192 + errorHeight, + decoration: buildToolbarLinkDecoration(context), + ), + ), + Positioned( + top: 16, + left: 20, + child: FlowyText.semibold( + LocaleKeys.document_toolbar_pageOrURL.tr(), + color: LinkStyle.textTertiary, + fontSize: 12, + figmaLineHeight: 16, + ), + ), + Positioned( + top: 80 + errorHeight, + left: 20, + child: FlowyText.semibold( + LocaleKeys.document_toolbar_linkName.tr(), + color: LinkStyle.textTertiary, + fontSize: 12, + figmaLineHeight: 16, + ), + ), + Positioned( + top: 144 + errorHeight, + left: 20, + child: buildButtons(), + ), + Positioned( + top: 100 + errorHeight, + left: 20, + child: buildNameTextField(), + ), + Positioned( + top: 36, + left: 20, + child: buildLinkField(), + ), + ], + ), + ), + ), + ); + } + + Widget buildLinkField() { + final showPageView = linkInfo.isPage && !isShowingSearchResult; + Widget child; + if (showPageView) { + child = buildPageView(); + } else if (!isShowingSearchResult) { + child = buildLinkView(); + } else { + return SizedBox( + width: 360, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 360, + height: 32, + child: searchTextField.buildTextField( + autofocus: true, + context: context, + ), + ), + VSpace(6), + searchTextField.buildResultContainer( + context: context, + width: 360, + onPageLinkSelected: onPageSelected, + onLinkSelected: onLinkSelected, + ), + ], + ), + ); + } + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + child, + if (showErrorText) + Padding( + padding: const EdgeInsets.only(top: 4), + child: FlowyText.regular( + LocaleKeys.document_plugins_file_networkUrlInvalid.tr(), + color: LinkStyle.textStatusError, + fontSize: 12, + figmaLineHeight: 16, + ), + ), + ], + ); + } + + Widget buildButtons() { + return GestureDetector( + onTap: hideSearchResult, + child: SizedBox( + width: 360, + height: 32, + child: Row( + children: [ + FlowyIconButton( + icon: FlowySvg(FlowySvgs.toolbar_link_unlink_m), + width: 32, + height: 32, + tooltipText: LocaleKeys.editor_removeLink.tr(), + preferBelow: false, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(8)), + border: Border.all(color: LinkStyle.borderColor(context)), + ), + onPressed: () => onRemoveLink.call(linkInfo), + ), + Spacer(), + DecoratedBox( + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(8)), + border: Border.all(color: LinkStyle.borderColor(context)), + ), + child: FlowyTextButton( + LocaleKeys.button_cancel.tr(), + padding: EdgeInsets.zero, + mainAxisAlignment: MainAxisAlignment.center, + constraints: BoxConstraints(maxWidth: 78, minHeight: 32), + fontSize: 14, + lineHeight: 20 / 14, + fontColor: Theme.of(context).isLightMode + ? LinkStyle.textPrimary + : Theme.of(context).iconTheme.color, + fillColor: Colors.transparent, + fontWeight: FontWeight.w400, + onPressed: onDismiss, + ), + ), + HSpace(12), + ValueListenableBuilder( + valueListenable: linkNameController, + builder: (context, _, __) { + return FlowyTextButton( + LocaleKeys.settings_appearance_documentSettings_apply.tr(), + padding: EdgeInsets.zero, + mainAxisAlignment: MainAxisAlignment.center, + constraints: BoxConstraints(maxWidth: 78, minHeight: 32), + fontSize: 14, + lineHeight: 20 / 14, + hoverColor: LinkStyle.fillThemeThick.withAlpha(200), + fontColor: Colors.white, + fillColor: LinkStyle.fillThemeThick, + fontWeight: FontWeight.w400, + onPressed: onApply, + ); + }, + ), + ], + ), + ), + ); + } + + Widget buildNameTextField() { + return SizedBox( + width: 360, + height: 32, + child: TextFormField( + autovalidateMode: AutovalidateMode.onUserInteraction, + focusNode: textFocusNode, + autofocus: true, + textAlign: TextAlign.left, + controller: linkNameController, + style: TextStyle( + fontSize: 14, + height: 20 / 14, + fontWeight: FontWeight.w400, + ), + onChanged: (text) { + linkInfo = LinkInfo( + name: text, + link: linkInfo.link, + isPage: linkInfo.isPage, + ); + }, + decoration: LinkStyle.buildLinkTextFieldInputDecoration( + LocaleKeys.document_toolbar_linkNameHint.tr(), + context, + ), + ), + ); + } + + Widget buildPageView() { + late Widget child; + final view = currentView; + if (view == null) { + child = Center( + child: SizedBox.fromSize( + size: Size(10, 10), + child: CircularProgressIndicator(), + ), + ); + } else { + final viewName = view.name; + final displayName = viewName.isEmpty + ? LocaleKeys.document_title_placeholder.tr() + : viewName; + child = GestureDetector( + onTap: showSearchResult, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: FlowyTooltip( + preferBelow: false, + message: displayName, + child: Container( + height: 32, + padding: EdgeInsets.fromLTRB(8, 0, 8, 0), + child: Row( + children: [ + searchTextField.buildIcon(view), + HSpace(4), + Flexible( + child: FlowyText.regular( + displayName, + overflow: TextOverflow.ellipsis, + figmaLineHeight: 20, + fontSize: 14, + ), + ), + ], + ), + ), + ), + ), + ); + } + return Container( + width: 360, + height: 32, + decoration: buildDecoration(), + child: child, + ); + } + + Widget buildLinkView() { + return Container( + width: 360, + height: 32, + decoration: buildDecoration(), + child: FlowyTooltip( + preferBelow: false, + message: linkInfo.link, + child: GestureDetector( + onTap: showSearchResult, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: Padding( + padding: EdgeInsets.fromLTRB(8, 6, 8, 6), + child: Row( + children: [ + FlowySvg(FlowySvgs.toolbar_link_earth_m), + HSpace(8), + Flexible( + child: FlowyText.regular( + linkInfo.link, + overflow: TextOverflow.ellipsis, + figmaLineHeight: 20, + ), + ), + ], + ), + ), + ), + ), + ), + ); + } + + KeyEventResult onFocusKeyEvent(FocusNode node, KeyEvent key) { + if (key is! KeyDownEvent) return KeyEventResult.ignored; + if (key.logicalKey == LogicalKeyboardKey.enter) { + onApply(); + return KeyEventResult.handled; + } else if (key.logicalKey == LogicalKeyboardKey.escape) { + onDismiss(); + return KeyEventResult.handled; + } + return KeyEventResult.ignored; + } + + Future makeSureHasFocus() async { + final focusNode = textFocusNode; + if (!mounted || focusNode.hasFocus) return; + focusNode.requestFocus(); + WidgetsBinding.instance.addPostFrameCallback((_) { + makeSureHasFocus(); + }); + } + + void onApply() { + if (isShowingSearchResult) { + onConfirm(); + return; + } + if (linkInfo.link.isEmpty) { + widget.onRemoveLink(linkInfo); + return; + } + if (linkInfo.link.isEmpty || !isUri(linkInfo.link)) { + setState(() { + showErrorText = true; + }); + return; + } + widget.onApply.call(linkInfo); + } + + void onConfirm() { + searchTextField.onSearchResult( + onLink: onLinkSelected, + onRecentViews: () => onPageSelected(searchTextField.currentRecentView), + onSearchViews: () => onPageSelected(searchTextField.currentSearchedView), + onEmpty: () { + searchTextField.unfocus(); + }, + ); + menuFocusNode.requestFocus(); + } + + Future getPageView() async { + if (!linkInfo.isPage) return; + final (view, isInTrash, isDeleted) = + await ViewBackendService.getMentionPageStatus(linkInfo.viewId); + if (mounted) { + setState(() { + currentView = view; + }); + } + } + + void showSearchResult() { + setState(() { + if (linkInfo.isPage) searchTextField.updateText(''); + isShowingSearchResult = true; + searchTextField.requestFocus(); + }); + } + + void hideSearchResult() { + setState(() { + isShowingSearchResult = false; + searchTextField.unfocus(); + textFocusNode.unfocus(); + }); + } + + void onLinkSelected() { + if (mounted) { + linkInfo = LinkInfo( + name: linkInfo.name, + link: searchTextField.searchText, + ); + hideSearchResult(); + } + } + + Future onPageSelected(ViewPB view) async { + currentView = view; + final link = ShareConstants.buildShareUrl( + workspaceId: await UserBackendService.getCurrentWorkspace().fold( + (s) => s.id, + (f) => '', + ), + viewId: view.id, + ); + linkInfo = LinkInfo( + name: linkInfo.name, + link: link, + isPage: true, + ); + searchTextField.updateText(linkInfo.link); + if (mounted) { + setState(() { + isShowingSearchResult = false; + searchTextField.unfocus(); + }); + } + } + + BoxDecoration buildDecoration() => BoxDecoration( + borderRadius: BorderRadius.circular(8), + border: Border.all(color: LinkStyle.borderColor(context)), + ); +} + +class LinkInfo { + LinkInfo({this.isPage = false, required this.name, required this.link}); + + final bool isPage; + final String name; + final String link; + + Attributes toAttribute() => + {AppFlowyRichTextKeys.href: link, kIsPageLink: isPage}; + + String get viewId => isPage ? link.split('/').lastOrNull ?? '' : ''; +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_hover_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_hover_menu.dart new file mode 100644 index 0000000000..c992e40c61 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_hover_menu.dart @@ -0,0 +1,635 @@ +import 'dart:math'; + +import 'package:appflowy/core/helpers/url_launcher.dart'; +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/application/document_bloc.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/desktop_floating_toolbar.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/link_embed/link_embed_block_component.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/shared.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/toolbar_item/custom_link_toolbar_item.dart'; +import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/util/theme_extension.dart'; +import 'package:appflowy/workspace/application/view/view_service.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'link_create_menu.dart'; +import 'link_edit_menu.dart'; + +class LinkHoverTrigger extends StatefulWidget { + const LinkHoverTrigger({ + super.key, + required this.editorState, + required this.selection, + required this.node, + required this.attribute, + required this.size, + this.delayToShow = const Duration(milliseconds: 50), + this.delayToHide = const Duration(milliseconds: 300), + }); + + final EditorState editorState; + final Selection selection; + final Node node; + final Attributes attribute; + final Size size; + final Duration delayToShow; + final Duration delayToHide; + + @override + State createState() => _LinkHoverTriggerState(); +} + +class _LinkHoverTriggerState extends State { + final hoverMenuController = PopoverController(); + final editMenuController = PopoverController(); + final toolbarController = getIt(); + bool isHoverMenuShowing = false; + bool isHoverMenuHovering = false; + bool isHoverTriggerHovering = false; + + Size get size => widget.size; + + EditorState get editorState => widget.editorState; + + Selection get selection => widget.selection; + + Attributes get attribute => widget.attribute; + + late HoverTriggerKey triggerKey = HoverTriggerKey(widget.node.id, selection); + + @override + void initState() { + super.initState(); + getIt()._add(triggerKey, showLinkHoverMenu); + toolbarController.addDisplayListener(onToolbarShow); + } + + @override + void dispose() { + hoverMenuController.close(); + editMenuController.close(); + getIt()._remove(triggerKey, showLinkHoverMenu); + toolbarController.removeDisplayListener(onToolbarShow); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: (v) { + isHoverTriggerHovering = true; + Future.delayed(widget.delayToShow, () { + if (isHoverTriggerHovering && !isHoverMenuShowing) { + showLinkHoverMenu(); + } + }); + }, + onExit: (v) { + isHoverTriggerHovering = false; + tryToDismissLinkHoverMenu(); + }, + child: buildHoverPopover( + buildEditPopover( + Container( + color: Colors.black.withAlpha(1), + width: size.width, + height: size.height, + ), + ), + ), + ); + } + + Widget buildHoverPopover(Widget child) { + return AppFlowyPopover( + controller: hoverMenuController, + direction: PopoverDirection.topWithLeftAligned, + offset: Offset(0, size.height), + onOpen: () { + keepEditorFocusNotifier.increase(); + isHoverMenuShowing = true; + }, + onClose: () { + keepEditorFocusNotifier.decrease(); + isHoverMenuShowing = false; + }, + margin: EdgeInsets.zero, + constraints: BoxConstraints( + maxWidth: max(320, size.width), + maxHeight: 48 + size.height, + ), + decorationColor: Colors.transparent, + popoverDecoration: BoxDecoration(), + popupBuilder: (context) => LinkHoverMenu( + attribute: widget.attribute, + triggerSize: size, + onEnter: (_) { + isHoverMenuHovering = true; + }, + onExit: (_) { + isHoverMenuHovering = false; + tryToDismissLinkHoverMenu(); + }, + onConvertTo: (type) => convertLinkTo(editorState, selection, type), + onOpenLink: openLink, + onCopyLink: () => copyLink(context), + onEditLink: showLinkEditMenu, + onRemoveLink: () => removeLink(editorState, selection), + ), + child: child, + ); + } + + Widget buildEditPopover(Widget child) { + final href = attribute.href ?? '', + isPage = attribute.isPage, + title = editorState.getTextInSelection(selection).join(); + final currentViewId = context.read()?.documentId ?? ''; + return AppFlowyPopover( + controller: editMenuController, + direction: PopoverDirection.bottomWithLeftAligned, + offset: Offset(0, 0), + onOpen: () => keepEditorFocusNotifier.increase(), + onClose: () => keepEditorFocusNotifier.decrease(), + margin: EdgeInsets.zero, + asBarrier: true, + decorationColor: Colors.transparent, + popoverDecoration: BoxDecoration(), + constraints: BoxConstraints( + maxWidth: 400, + minHeight: 282, + ), + popupBuilder: (context) => LinkEditMenu( + currentViewId: currentViewId, + linkInfo: LinkInfo(name: title, link: href, isPage: isPage), + onDismiss: () => editMenuController.close(), + onApply: (info) async { + final transaction = editorState.transaction; + transaction.replaceText( + widget.node, + selection.startIndex, + selection.length, + info.name, + attributes: info.toAttribute(), + ); + editMenuController.close(); + await editorState.apply(transaction); + }, + onRemoveLink: (linkinfo) => + onRemoveAndReplaceLink(editorState, selection, linkinfo.name), + ), + child: child, + ); + } + + void onToolbarShow() => hoverMenuController.close(); + + void showLinkHoverMenu() { + if (isHoverMenuShowing || toolbarController.isToolbarShowing || !mounted) { + return; + } + keepEditorFocusNotifier.increase(); + hoverMenuController.show(); + } + + void showLinkEditMenu() { + keepEditorFocusNotifier.increase(); + hoverMenuController.close(); + editMenuController.show(); + } + + void tryToDismissLinkHoverMenu() { + Future.delayed(widget.delayToHide, () { + if (isHoverMenuHovering || isHoverTriggerHovering) { + return; + } + hoverMenuController.close(); + }); + } + + Future openLink() async { + final href = widget.attribute.href ?? '', isPage = widget.attribute.isPage; + + if (isPage) { + final viewId = href.split('/').lastOrNull ?? ''; + if (viewId.isEmpty) { + await afLaunchUrlString(href, addingHttpSchemeWhenFailed: true); + } else { + final (view, isInTrash, isDeleted) = + await ViewBackendService.getMentionPageStatus(viewId); + if (view != null) { + await handleMentionBlockTap(context, widget.editorState, view); + } + } + } else { + await afLaunchUrlString(href, addingHttpSchemeWhenFailed: true); + } + } + + Future copyLink(BuildContext context) async { + final href = widget.attribute.href ?? ''; + await context.copyLink(href); + hoverMenuController.close(); + } + + void removeLink( + EditorState editorState, + Selection selection, + ) { + final node = editorState.getNodeAtPath(selection.end.path); + if (node == null) { + return; + } + final index = selection.normalized.startIndex; + final length = selection.length; + final transaction = editorState.transaction + ..formatText( + node, + index, + length, + { + BuiltInAttributeKey.href: null, + kIsPageLink: null, + }, + ); + editorState.apply(transaction); + } + + Future convertLinkTo( + EditorState editorState, + Selection selection, + LinkConvertMenuCommand type, + ) async { + final url = widget.attribute.href ?? ''; + if (type == LinkConvertMenuCommand.toBookmark) { + await convertUrlToLinkPreview(editorState, selection, url); + } else if (type == LinkConvertMenuCommand.toMention) { + await convertUrlToMention(editorState, selection); + } else if (type == LinkConvertMenuCommand.toEmbed) { + await convertUrlToLinkPreview( + editorState, + selection, + url, + previewType: LinkEmbedKeys.embed, + ); + } + } + + void onRemoveAndReplaceLink( + EditorState editorState, + Selection selection, + String text, + ) { + final node = editorState.getNodeAtPath(selection.end.path); + if (node == null) { + return; + } + final index = selection.normalized.startIndex; + final length = selection.length; + final transaction = editorState.transaction + ..replaceText( + node, + index, + length, + text, + attributes: { + BuiltInAttributeKey.href: null, + kIsPageLink: null, + }, + ); + editorState.apply(transaction); + } +} + +class LinkHoverMenu extends StatefulWidget { + const LinkHoverMenu({ + super.key, + required this.attribute, + required this.onEnter, + required this.onExit, + required this.triggerSize, + required this.onCopyLink, + required this.onOpenLink, + required this.onEditLink, + required this.onRemoveLink, + required this.onConvertTo, + }); + + final Attributes attribute; + final PointerEnterEventListener onEnter; + final PointerExitEventListener onExit; + final Size triggerSize; + final VoidCallback onCopyLink; + final VoidCallback onOpenLink; + final VoidCallback onEditLink; + final VoidCallback onRemoveLink; + final ValueChanged onConvertTo; + + @override + State createState() => _LinkHoverMenuState(); +} + +class _LinkHoverMenuState extends State { + ViewPB? currentView; + late bool isPage = widget.attribute.isPage; + late String href = widget.attribute.href ?? ''; + final popoverController = PopoverController(); + bool isConvertButtonSelected = false; + + @override + void initState() { + super.initState(); + if (isPage) getPageView(); + } + + @override + void dispose() { + super.dispose(); + popoverController.close(); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + MouseRegion( + onEnter: widget.onEnter, + onExit: widget.onExit, + child: SizedBox( + width: max(320, widget.triggerSize.width), + height: 48, + child: Align( + alignment: Alignment.centerLeft, + child: Container( + width: 320, + height: 48, + decoration: buildToolbarLinkDecoration(context), + padding: EdgeInsets.fromLTRB(12, 8, 8, 8), + child: Row( + children: [ + Expanded(child: buildLinkWidget()), + Container( + height: 20, + width: 1, + color: Color(0xffE8ECF3) + .withAlpha(Theme.of(context).isLightMode ? 255 : 40), + margin: EdgeInsets.symmetric(horizontal: 6), + ), + FlowyIconButton( + icon: FlowySvg(FlowySvgs.toolbar_link_m), + tooltipText: LocaleKeys.editor_copyLink.tr(), + preferBelow: false, + width: 36, + height: 32, + onPressed: widget.onCopyLink, + ), + FlowyIconButton( + icon: FlowySvg(FlowySvgs.toolbar_link_edit_m), + tooltipText: LocaleKeys.editor_editLink.tr(), + preferBelow: false, + width: 36, + height: 32, + onPressed: widget.onEditLink, + ), + buildConvertButton(), + FlowyIconButton( + icon: FlowySvg(FlowySvgs.toolbar_link_unlink_m), + tooltipText: LocaleKeys.editor_removeLink.tr(), + preferBelow: false, + width: 36, + height: 32, + onPressed: widget.onRemoveLink, + ), + ], + ), + ), + ), + ), + ), + MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: widget.onEnter, + onExit: widget.onExit, + child: GestureDetector( + onTap: widget.onOpenLink, + child: Container( + width: widget.triggerSize.width, + height: widget.triggerSize.height, + color: Colors.black.withAlpha(1), + ), + ), + ), + ], + ); + } + + Future getPageView() async { + final viewId = href.split('/').lastOrNull ?? ''; + final (view, isInTrash, isDeleted) = + await ViewBackendService.getMentionPageStatus(viewId); + if (mounted) { + setState(() { + currentView = view; + }); + } + } + + Widget buildLinkWidget() { + final view = currentView; + if (isPage && view == null) { + return SizedBox.square( + dimension: 20, + child: CircularProgressIndicator(), + ); + } + String text = ''; + if (isPage && view != null) { + text = view.name; + if (text.isEmpty) { + text = LocaleKeys.document_title_placeholder.tr(); + } + } else { + text = href; + } + return FlowyTooltip( + message: text, + preferBelow: false, + child: FlowyText.regular( + text, + overflow: TextOverflow.ellipsis, + figmaLineHeight: 20, + fontSize: 14, + ), + ); + } + + Widget buildConvertButton() { + return AppFlowyPopover( + offset: Offset(44, 10.0), + direction: PopoverDirection.bottomWithRightAligned, + margin: EdgeInsets.zero, + controller: popoverController, + onOpen: () => keepEditorFocusNotifier.increase(), + onClose: () => keepEditorFocusNotifier.decrease(), + popupBuilder: (context) => buildConvertMenu(), + child: FlowyIconButton( + icon: FlowySvg(FlowySvgs.turninto_m), + isSelected: isConvertButtonSelected, + tooltipText: LocaleKeys.editor_convertTo.tr(), + preferBelow: false, + width: 36, + height: 32, + onPressed: () { + setState(() { + isConvertButtonSelected = true; + }); + showConvertMenu(); + }, + ), + ); + } + + Widget buildConvertMenu() { + return MouseRegion( + onEnter: widget.onEnter, + onExit: widget.onExit, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: SeparatedColumn( + mainAxisSize: MainAxisSize.min, + separatorBuilder: () => const VSpace(0.0), + children: + List.generate(LinkConvertMenuCommand.values.length, (index) { + final command = LinkConvertMenuCommand.values[index]; + return SizedBox( + height: 36, + child: FlowyButton( + text: FlowyText( + command.title, + fontWeight: FontWeight.w400, + figmaLineHeight: 20, + ), + onTap: () { + widget.onConvertTo(command); + closeConvertMenu(); + }, + ), + ); + }), + ), + ), + ); + } + + void showConvertMenu() { + keepEditorFocusNotifier.increase(); + popoverController.show(); + } + + void closeConvertMenu() { + popoverController.close(); + } +} + +class HoverTriggerKey { + HoverTriggerKey(this.nodeId, this.selection); + + final String nodeId; + final Selection selection; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is HoverTriggerKey && + runtimeType == other.runtimeType && + nodeId == other.nodeId && + isSelectionSame(other.selection); + + bool isSelectionSame(Selection other) => + (selection.start == other.start && selection.end == other.end) || + (selection.start == other.end && selection.end == other.start); + + @override + int get hashCode => nodeId.hashCode ^ selection.hashCode; +} + +class LinkHoverTriggers { + final Map> _map = {}; + + void _add(HoverTriggerKey key, VoidCallback callback) { + final callbacks = _map[key] ?? {}; + callbacks.add(callback); + _map[key] = callbacks; + } + + void _remove(HoverTriggerKey key, VoidCallback callback) { + final callbacks = _map[key] ?? {}; + callbacks.remove(callback); + _map[key] = callbacks; + } + + void call(HoverTriggerKey key) { + final callbacks = _map[key] ?? {}; + if (callbacks.isEmpty) return; + callbacks.first.call(); + } +} + +enum LinkConvertMenuCommand { + toMention, + toBookmark, + toEmbed; + + String get title { + switch (this) { + case toMention: + return LocaleKeys.document_plugins_linkPreview_linkPreviewMenu_toMetion + .tr(); + case toBookmark: + return LocaleKeys + .document_plugins_linkPreview_linkPreviewMenu_toBookmark + .tr(); + case toEmbed: + return LocaleKeys.document_plugins_linkPreview_linkPreviewMenu_toEmbed + .tr(); + } + } + + String get type { + switch (this) { + case toMention: + return MentionBlockKeys.type; + case toBookmark: + return LinkPreviewBlockKeys.type; + case toEmbed: + return LinkPreviewBlockKeys.type; + } + } +} + +extension LinkExtension on BuildContext { + Future copyLink(String link) async { + if (link.isEmpty) return; + await getIt() + .setData(ClipboardServiceData(plainText: link)); + if (mounted) { + showToastNotification( + message: LocaleKeys.shareAction_copyLinkSuccess.tr(), + ); + } + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_replace_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_replace_menu.dart new file mode 100644 index 0000000000..d08442d779 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_replace_menu.dart @@ -0,0 +1,184 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/menu/menu_extension.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +// ignore: implementation_imports +import 'package:appflowy_editor/src/editor/util/link_util.dart'; +import 'package:flutter/services.dart'; + +import 'link_create_menu.dart'; +import 'link_styles.dart'; + +void showReplaceMenu({ + required BuildContext context, + required EditorState editorState, + required Node node, + String? url, + required LTRB ltrb, + required ValueChanged onReplace, +}) { + OverlayEntry? overlay; + + void dismissOverlay() { + keepEditorFocusNotifier.decrease(); + overlay?.remove(); + overlay = null; + } + + keepEditorFocusNotifier.increase(); + overlay = FullScreenOverlayEntry( + top: ltrb.top, + bottom: ltrb.bottom, + left: ltrb.left, + right: ltrb.right, + dismissCallback: () => keepEditorFocusNotifier.decrease(), + builder: (context) { + return LinkReplaceMenu( + link: url ?? '', + onSubmitted: (link) async { + onReplace.call(link); + dismissOverlay(); + }, + onDismiss: dismissOverlay, + ); + }, + ).build(); + + Overlay.of(context, rootOverlay: true).insert(overlay!); +} + +class LinkReplaceMenu extends StatefulWidget { + const LinkReplaceMenu({ + super.key, + required this.onSubmitted, + required this.link, + required this.onDismiss, + }); + + final ValueChanged onSubmitted; + final VoidCallback onDismiss; + final String link; + + @override + State createState() => _LinkReplaceMenuState(); +} + +class _LinkReplaceMenuState extends State { + bool showErrorText = false; + late FocusNode focusNode = FocusNode(onKeyEvent: onKeyEvent); + late TextEditingController textEditingController = + TextEditingController(text: widget.link); + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + focusNode.requestFocus(); + }); + } + + @override + void dispose() { + focusNode.dispose(); + textEditingController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Container( + width: 330, + padding: EdgeInsets.all(8), + decoration: buildToolbarLinkDecoration(context), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(child: buildLinkField()), + HSpace(8), + buildReplaceButton(), + ], + ), + ); + } + + Widget buildLinkField() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: 32, + child: TextFormField( + autovalidateMode: AutovalidateMode.onUserInteraction, + autofocus: true, + focusNode: focusNode, + textAlign: TextAlign.left, + controller: textEditingController, + style: TextStyle( + fontSize: 14, + height: 20 / 14, + fontWeight: FontWeight.w400, + ), + decoration: LinkStyle.buildLinkTextFieldInputDecoration( + LocaleKeys.document_plugins_linkPreview_linkPreviewMenu_pasteHint + .tr(), + context, + showErrorBorder: showErrorText, + ), + ), + ), + if (showErrorText) + Padding( + padding: const EdgeInsets.only(top: 4), + child: FlowyText.regular( + LocaleKeys.document_plugins_file_networkUrlInvalid.tr(), + color: LinkStyle.textStatusError, + fontSize: 12, + figmaLineHeight: 16, + ), + ), + ], + ); + } + + Widget buildReplaceButton() { + return FlowyTextButton( + LocaleKeys.button_replace.tr(), + padding: EdgeInsets.zero, + mainAxisAlignment: MainAxisAlignment.center, + constraints: BoxConstraints(maxWidth: 78, minHeight: 32), + fontSize: 14, + lineHeight: 20 / 14, + hoverColor: LinkStyle.fillThemeThick.withAlpha(200), + fontColor: Colors.white, + fillColor: LinkStyle.fillThemeThick, + fontWeight: FontWeight.w400, + onPressed: onSubmit, + ); + } + + void onSubmit() { + final link = textEditingController.text.trim(); + if (link.isEmpty || !isUri(link)) { + setState(() { + showErrorText = true; + }); + return; + } + widget.onSubmitted.call(link); + } + + KeyEventResult onKeyEvent(FocusNode node, KeyEvent key) { + if (key is! KeyDownEvent) return KeyEventResult.ignored; + if (key.logicalKey == LogicalKeyboardKey.escape) { + widget.onDismiss.call(); + return KeyEventResult.handled; + } else if (key.logicalKey == LogicalKeyboardKey.enter) { + onSubmit(); + return KeyEventResult.handled; + } + return KeyEventResult.ignored; + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_search_text_field.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_search_text_field.dart new file mode 100644 index 0000000000..97fd6abdad --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_search_text_field.dart @@ -0,0 +1,352 @@ +import 'dart:math'; + +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart'; +import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; +import 'package:appflowy/shared/list_extension.dart'; +import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/workspace/application/recent/cached_recent_service.dart'; +import 'package:appflowy/workspace/application/view/view_ext.dart'; +import 'package:appflowy/workspace/application/view/view_service.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +// ignore: implementation_imports +import 'package:appflowy_editor/src/flutter/scrollable_positioned_list/scrollable_positioned_list.dart'; +// ignore: implementation_imports +import 'package:appflowy_editor/src/editor/util/link_util.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import 'link_create_menu.dart'; +import 'link_styles.dart'; + +class LinkSearchTextField { + LinkSearchTextField({ + this.onEscape, + this.onEnter, + this.onDataRefresh, + this.initialViewId = '', + required this.currentViewId, + String? initialSearchText, + }) : textEditingController = TextEditingController( + text: isUri(initialSearchText ?? '') ? initialSearchText : '', + ); + + final TextEditingController textEditingController; + final String initialViewId; + final String currentViewId; + final ItemScrollController searchController = ItemScrollController(); + late FocusNode focusNode = FocusNode(onKeyEvent: onKeyEvent); + final List searchedViews = []; + final List recentViews = []; + int selectedIndex = 0; + + final VoidCallback? onEscape; + final VoidCallback? onEnter; + final VoidCallback? onDataRefresh; + + String get searchText => textEditingController.text; + + bool get isTextfieldEnable => searchText.isNotEmpty && isUri(searchText); + + bool get showingRecent => searchText.isEmpty && recentViews.isNotEmpty; + + ViewPB get currentSearchedView => searchedViews[selectedIndex]; + + ViewPB get currentRecentView => recentViews[selectedIndex]; + + void dispose() { + textEditingController.dispose(); + focusNode.dispose(); + searchedViews.clear(); + recentViews.clear(); + } + + Widget buildTextField({ + bool autofocus = false, + bool showError = false, + required BuildContext context, + }) { + return TextFormField( + autovalidateMode: AutovalidateMode.onUserInteraction, + autofocus: autofocus, + focusNode: focusNode, + textAlign: TextAlign.left, + controller: textEditingController, + style: TextStyle( + fontSize: 14, + height: 20 / 14, + fontWeight: FontWeight.w400, + ), + onChanged: (text) { + if (text.isEmpty) { + searchedViews.clear(); + selectedIndex = 0; + onDataRefresh?.call(); + } else { + searchViews(text); + } + }, + decoration: LinkStyle.buildLinkTextFieldInputDecoration( + LocaleKeys.document_toolbar_linkInputHint.tr(), + context, + showErrorBorder: showError, + ), + ); + } + + Widget buildResultContainer({ + EdgeInsetsGeometry? margin, + required BuildContext context, + VoidCallback? onLinkSelected, + ValueChanged? onPageLinkSelected, + double width = 320.0, + }) { + return onSearchResult( + onEmpty: () => SizedBox.shrink(), + onLink: () => Container( + height: 48, + width: width, + padding: EdgeInsets.all(8), + margin: margin, + decoration: buildToolbarLinkDecoration(context), + child: FlowyButton( + leftIcon: FlowySvg(FlowySvgs.toolbar_link_earth_m), + isSelected: true, + text: FlowyText.regular( + searchText, + overflow: TextOverflow.ellipsis, + fontSize: 14, + figmaLineHeight: 20, + ), + onTap: onLinkSelected, + ), + ), + onRecentViews: () => Container( + width: width, + height: recentViews.length.clamp(1, 5) * 32.0 + 48, + margin: margin, + padding: EdgeInsets.all(8), + decoration: buildToolbarLinkDecoration(context), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 32, + padding: EdgeInsets.all(8), + child: FlowyText.semibold( + LocaleKeys.inlineActions_recentPages.tr(), + color: LinkStyle.textTertiary, + fontSize: 12, + figmaLineHeight: 16, + ), + ), + Flexible( + child: ListView.builder( + itemBuilder: (context, index) { + final currentView = recentViews[index]; + return buildPageItem( + currentView, + index == selectedIndex, + onPageLinkSelected, + ); + }, + itemCount: recentViews.length, + ), + ), + ], + ), + ), + onSearchViews: () => Container( + width: width, + height: searchedViews.length.clamp(1, 5) * 32.0 + 16, + margin: margin, + decoration: buildToolbarLinkDecoration(context), + child: ScrollablePositionedList.builder( + padding: EdgeInsets.all(8), + physics: const ClampingScrollPhysics(), + shrinkWrap: true, + itemCount: searchedViews.length, + itemScrollController: searchController, + initialScrollIndex: max(0, selectedIndex), + itemBuilder: (context, index) { + final currentView = searchedViews[index]; + return buildPageItem( + currentView, + index == selectedIndex, + onPageLinkSelected, + ); + }, + ), + ), + ); + } + + Widget buildPageItem( + ViewPB view, + bool isSelected, + ValueChanged? onSubmittedPageLink, + ) { + final viewName = view.name; + final displayName = viewName.isEmpty + ? LocaleKeys.document_title_placeholder.tr() + : viewName; + final isCurrent = initialViewId == view.id; + return SizedBox( + height: 32, + child: FlowyButton( + isSelected: isSelected, + leftIcon: buildIcon(view, padding: EdgeInsets.zero), + text: FlowyText.regular( + displayName, + overflow: TextOverflow.ellipsis, + fontSize: 14, + figmaLineHeight: 20, + ), + rightIcon: isCurrent ? FlowySvg(FlowySvgs.toolbar_check_m) : null, + onTap: () => onSubmittedPageLink?.call(view), + ), + ); + } + + Widget buildIcon( + ViewPB view, { + EdgeInsetsGeometry padding = const EdgeInsets.only(top: 4), + }) { + if (view.icon.value.isEmpty) return view.defaultIcon(size: Size(20, 20)); + final iconData = view.icon.toEmojiIconData(); + return Padding( + padding: padding, + child: RawEmojiIconWidget( + emoji: iconData, + emojiSize: iconData.type == FlowyIconType.emoji ? 16 : 20, + lineHeight: 1, + ), + ); + } + + void requestFocus() => focusNode.requestFocus(); + + void unfocus() => focusNode.unfocus(); + + void updateText(String text) => textEditingController.text = text; + + T onSearchResult({ + required ValueGetter onLink, + required ValueGetter onRecentViews, + required ValueGetter onSearchViews, + required ValueGetter onEmpty, + }) { + if (searchedViews.isEmpty && recentViews.isEmpty && searchText.isEmpty) { + return onEmpty.call(); + } + if (searchedViews.isEmpty && searchText.isNotEmpty) { + return onLink.call(); + } + if (searchedViews.isEmpty) return onRecentViews.call(); + return onSearchViews.call(); + } + + KeyEventResult onKeyEvent(FocusNode node, KeyEvent key) { + if (key is! KeyDownEvent) return KeyEventResult.ignored; + int index = selectedIndex; + if (key.logicalKey == LogicalKeyboardKey.escape) { + onEscape?.call(); + return KeyEventResult.handled; + } else if (key.logicalKey == LogicalKeyboardKey.arrowUp) { + index = onSearchResult( + onLink: () => 0, + onRecentViews: () { + int result = index - 1; + if (result < 0) result = recentViews.length - 1; + return result; + }, + onSearchViews: () { + int result = index - 1; + if (result < 0) result = searchedViews.length - 1; + searchController.scrollTo( + index: result, + alignment: 0.5, + duration: const Duration(milliseconds: 300), + ); + return result; + }, + onEmpty: () => 0, + ); + refreshIndex(index); + return KeyEventResult.handled; + } else if (key.logicalKey == LogicalKeyboardKey.arrowDown) { + index = onSearchResult( + onLink: () => 0, + onRecentViews: () { + int result = index + 1; + if (result >= recentViews.length) result = 0; + return result; + }, + onSearchViews: () { + int result = index + 1; + if (result >= searchedViews.length) result = 0; + searchController.scrollTo( + index: result, + alignment: 0.5, + duration: const Duration(milliseconds: 300), + ); + return result; + }, + onEmpty: () => 0, + ); + refreshIndex(index); + return KeyEventResult.handled; + } else if (key.logicalKey == LogicalKeyboardKey.enter) { + onEnter?.call(); + return KeyEventResult.handled; + } + return KeyEventResult.ignored; + } + + Future searchRecentViews() async { + final recentService = getIt(); + final sectionViews = await recentService.recentViews(); + final views = sectionViews + .unique((e) => e.item.id) + .map((e) => e.item) + .where((e) => e.id != currentViewId) + .take(5) + .toList(); + recentViews.clear(); + recentViews.addAll(views); + selectedIndex = 0; + onDataRefresh?.call(); + } + + Future searchViews(String search) async { + final viewResult = await ViewBackendService.getAllViews(); + final allViews = viewResult + .toNullable() + ?.items + .where( + (view) => + (view.id != currentViewId) && + (view.name.toLowerCase().contains(search.toLowerCase()) || + (view.name.isEmpty && search.isEmpty) || + (view.name.isEmpty && + LocaleKeys.menuAppHeader_defaultNewPageName + .tr() + .toLowerCase() + .contains(search.toLowerCase()))), + ) + .take(10) + .toList(); + searchedViews.clear(); + searchedViews.addAll(allViews ?? []); + selectedIndex = 0; + onDataRefresh?.call(); + } + + void refreshIndex(int index) { + selectedIndex = index; + onDataRefresh?.call(); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_styles.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_styles.dart new file mode 100644 index 0000000000..cabc00a312 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_styles.dart @@ -0,0 +1,46 @@ +import 'package:appflowy/util/theme_extension.dart'; +import 'package:flutter/material.dart'; + +class LinkStyle { + static const textTertiary = Color(0xFF99A1A8); + static const textStatusError = Color(0xffE71D32); + static const fillThemeThick = Color(0xFF00B5FF); + static const shadowMedium = Color(0x1F22251F); + static const textPrimary = Color(0xFF1F2329); + + static Color borderColor(BuildContext context) => + Theme.of(context).isLightMode ? Color(0xFFE8ECF3) : Color(0x64BDBDBD); + + static InputDecoration buildLinkTextFieldInputDecoration( + String hintText, + BuildContext context, { + bool showErrorBorder = false, + }) { + final border = OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(8.0)), + borderSide: BorderSide(color: borderColor(context)), + ); + final enableBorder = border.copyWith( + borderSide: BorderSide( + color: showErrorBorder + ? LinkStyle.textStatusError + : LinkStyle.fillThemeThick, + ), + ); + const hintStyle = TextStyle( + fontSize: 14, + height: 20 / 14, + fontWeight: FontWeight.w400, + color: LinkStyle.textTertiary, + ); + return InputDecoration( + hintText: hintText, + hintStyle: hintStyle, + contentPadding: const EdgeInsets.fromLTRB(8, 6, 8, 6), + isDense: true, + border: border, + enabledBorder: border, + focusedBorder: enableBorder, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/toolbar_animation.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/toolbar_animation.dart new file mode 100644 index 0000000000..7598a2b657 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/desktop_toolbar/toolbar_animation.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; + +class ToolbarAnimationWidget extends StatefulWidget { + const ToolbarAnimationWidget({ + super.key, + required this.child, + this.duration = const Duration(milliseconds: 150), + this.beginOpacity = 0.0, + this.endOpacity = 1.0, + this.beginScaleFactor = 0.95, + this.endScaleFactor = 1.0, + }); + + final Widget child; + final Duration duration; + final double beginScaleFactor; + final double endScaleFactor; + final double beginOpacity; + final double endOpacity; + + @override + State createState() => _ToolbarAnimationWidgetState(); +} + +class _ToolbarAnimationWidgetState extends State + with SingleTickerProviderStateMixin { + late AnimationController controller; + late Animation fadeAnimation; + late Animation scaleAnimation; + + @override + void initState() { + super.initState(); + controller = AnimationController( + vsync: this, + duration: widget.duration, + ); + fadeAnimation = _buildFadeAnimation(); + scaleAnimation = _buildScaleAnimation(); + controller.forward(); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: controller, + builder: (_, child) => Opacity( + opacity: fadeAnimation.value, + child: Transform.scale( + scale: scaleAnimation.value, + child: child, + ), + ), + child: widget.child, + ); + } + + Animation _buildFadeAnimation() { + return Tween( + begin: widget.beginOpacity, + end: widget.endOpacity, + ).animate( + CurvedAnimation( + parent: controller, + curve: Curves.easeInOut, + ), + ); + } + + Animation _buildScaleAnimation() { + return Tween( + begin: widget.beginScaleFactor, + end: widget.endScaleFactor, + ).animate( + CurvedAnimation( + parent: controller, + curve: Curves.easeInOut, + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/error/error_block_component_builder.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/error/error_block_component_builder.dart index a37ed29150..6c09ca6a28 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/error/error_block_component_builder.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/error/error_block_component_builder.dart @@ -30,6 +30,10 @@ class ErrorBlockComponentBuilder extends BlockComponentBuilder { blockComponentContext, state, ), + actionTrailingBuilder: (context, state) => actionTrailingBuilder( + blockComponentContext, + state, + ), ); } @@ -43,6 +47,7 @@ class ErrorBlockComponentWidget extends BlockComponentStatefulWidget { required super.node, super.showActions, super.actionBuilder, + super.actionTrailingBuilder, super.configuration = const BlockComponentConfiguration(), }); @@ -81,6 +86,7 @@ class _ErrorBlockComponentWidgetState extends State child = BlockComponentActionWrapper( node: node, actionBuilder: widget.actionBuilder!, + actionTrailingBuilder: widget.actionTrailingBuilder, child: child, ); } @@ -148,7 +154,6 @@ class _ErrorBlockComponentWidgetState extends State void _copyBlockContent() { showToastNotification( - context, message: LocaleKeys.document_errorBlock_blockContentHasBeenCopied.tr(), ); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_block_component.dart index 52fc2e717f..fe50224caa 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_block_component.dart @@ -10,6 +10,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_p import 'package:appflowy/plugins/document/presentation/editor_plugins/file/file_util.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/workspace/presentation/home/toast.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/file_entities.pbenum.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:cross_file/cross_file.dart'; import 'package:desktop_drop/desktop_drop.dart'; @@ -95,6 +96,17 @@ enum FileUrlType { return 2; } } + + FileUploadTypePB toFileUploadTypePB() { + switch (this) { + case FileUrlType.local: + return FileUploadTypePB.LocalFile; + case FileUrlType.network: + return FileUploadTypePB.NetworkFile; + case FileUrlType.cloud: + return FileUploadTypePB.CloudFile; + } + } } Node fileNode({ @@ -141,6 +153,7 @@ class FileBlockComponent extends BlockComponentStatefulWidget { required super.node, super.showActions, super.actionBuilder, + super.actionTrailingBuilder, super.configuration = const BlockComponentConfiguration(), }); @@ -303,6 +316,7 @@ class FileBlockComponentState extends State child = BlockComponentActionWrapper( node: node, actionBuilder: widget.actionBuilder!, + actionTrailingBuilder: widget.actionTrailingBuilder, child: child, ); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_block_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_block_menu.dart index d79d5a1994..99529b3b8e 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_block_menu.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_block_menu.dart @@ -1,15 +1,17 @@ -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/application/prelude.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/file/file_block.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/file/file_util.dart'; import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; import 'package:appflowy/workspace/application/settings/date_time/date_format_ext.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; +import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class FileBlockMenu extends StatefulWidget { @@ -59,11 +61,37 @@ class _FileBlockMenuState extends State { final dateFormat = context.read().state.dateFormat; final urlType = FileUrlType.fromIntValue(widget.node.attributes[FileBlockKeys.urlType]); - + final fileUploadType = urlType.toFileUploadTypePB(); return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ + HoverButton( + itemHeight: 20, + leftIcon: const FlowySvg(FlowySvgs.download_s), + name: LocaleKeys.button_download.tr(), + onTap: () { + final userProfile = widget.editorState.document.root.context + ?.read() + .state + .userProfilePB; + final url = widget.node.attributes[FileBlockKeys.url]; + final name = widget.node.attributes[FileBlockKeys.name]; + if (url != null && name != null) { + final filePB = MediaFilePB( + url: url, + name: name, + uploadType: fileUploadType, + ); + downloadMediaFile( + context, + filePB, + userProfile: userProfile, + ); + } + }, + ), + const VSpace(4), HoverButton( itemHeight: 20, leftIcon: const FlowySvg(FlowySvgs.edit_s), diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_selection_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_selection_menu.dart index 8a5d7047a9..132a7b8e7e 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_selection_menu.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_selection_menu.dart @@ -9,26 +9,19 @@ extension InsertFile on EditorState { if (selection == null || !selection.isCollapsed) { return; } - final node = getNodeAtPath(selection.end.path); - if (node == null) { + final path = selection.end.path; + final node = getNodeAtPath(path); + final delta = node?.delta; + if (node == null || delta == null) { return; } final file = fileNode(url: '')..extraInfos = {'global_key': key}; - final transaction = this.transaction; - // if the current node is empty paragraph, replace it with the file node - if (node.type == ParagraphBlockKeys.type && - (node.delta?.isEmpty ?? false)) { - transaction - ..insertNode(node.path, file) - ..deleteNode(node); - } else { - transaction.insertNode(node.path.next, file); - } - - transaction.afterSelection = - Selection.collapsed(Position(path: node.path.next)); - transaction.selectionExtraInfo = {}; + final insertedPath = delta.isEmpty ? path : path.next; + final transaction = this.transaction + ..insertNode(insertedPath, file) + ..insertNode(insertedPath, paragraphNode()) + ..afterSelection = Selection.collapsed(Position(path: insertedPath.next)); return apply(transaction); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_upload_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_upload_menu.dart index 75f02f5079..4ef680d1b9 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_upload_menu.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_upload_menu.dart @@ -141,7 +141,8 @@ class _FileUploadLocalState extends State<_FileUploadLocal> { height: 32, child: FlowyButton( backgroundColor: Theme.of(context).colorScheme.primary, - hoverColor: Theme.of(context).colorScheme.primary.withOpacity(0.9), + hoverColor: + Theme.of(context).colorScheme.primary.withValues(alpha: 0.9), showDefaultBoxDecorationOnMobile: true, margin: const EdgeInsets.all(5), text: FlowyText( @@ -295,7 +296,7 @@ class _FileUploadNetworkState extends State<_FileUploadNetwork> { child: FlowyButton( backgroundColor: Theme.of(context).colorScheme.primary, hoverColor: - Theme.of(context).colorScheme.primary.withOpacity(0.9), + Theme.of(context).colorScheme.primary.withValues(alpha: 0.9), showDefaultBoxDecorationOnMobile: true, margin: const EdgeInsets.all(5), text: FlowyText( diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_util.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_util.dart index 3ab93b4c95..8e6651ff73 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_util.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_util.dart @@ -14,8 +14,8 @@ import 'package:appflowy_backend/dispatch/error.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/file_entities.pbenum.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/media_entities.pb.dart'; -import 'package:appflowy_backend/protobuf/flowy-user/auth.pbenum.dart'; import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-user/workspace.pb.dart'; import 'package:cross_file/cross_file.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/file_picker/file_picker_impl.dart'; @@ -104,10 +104,10 @@ Future downloadMediaFile( await afLaunchUrlString(file.url); } else { if (userProfile == null) { - return showToastNotification( - context, + showToastNotification( message: LocaleKeys.grid_media_downloadFailedToken.tr(), ); + return; } final uri = Uri.parse(file.url); @@ -128,14 +128,12 @@ Future downloadMediaFile( if (result != null && context.mounted) { showToastNotification( - context, type: ToastificationType.error, message: LocaleKeys.grid_media_downloadSuccess.tr(), ); } } else if (context.mounted) { showToastNotification( - context, type: ToastificationType.error, message: LocaleKeys.document_plugins_image_imageDownloadFailed.tr(), ); @@ -159,13 +157,11 @@ Future downloadMediaFile( if (context.mounted) { showToastNotification( - context, message: LocaleKeys.grid_media_downloadSuccess.tr(), ); } } else if (context.mounted) { showToastNotification( - context, type: ToastificationType.error, message: LocaleKeys.document_plugins_image_imageDownloadFailed.tr(), ); @@ -188,8 +184,8 @@ Future insertLocalFile( final fileType = file.fileType.toMediaFileTypePB(); // Check upload type - final isLocalMode = (userProfile?.authenticator ?? AuthenticatorPB.Local) == - AuthenticatorPB.Local; + final isLocalMode = + (userProfile?.workspaceAuthType ?? AuthTypePB.Local) == AuthTypePB.Local; String? path; String? errorMsg; @@ -233,8 +229,8 @@ Future insertLocalFiles( if (files.every((f) => f.path.isEmpty)) return; // Check upload type - final isLocalMode = (userProfile?.authenticator ?? AuthenticatorPB.Local) == - AuthenticatorPB.Local; + final isLocalMode = + (userProfile?.workspaceAuthType ?? AuthTypePB.Local) == AuthTypePB.Local; for (final file in files) { final fileType = file.fileType.toMediaFileTypePB(); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/mobile_file_upload_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/mobile_file_upload_menu.dart index f716c107df..f4c7a76c0e 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/mobile_file_upload_menu.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/mobile_file_upload_menu.dart @@ -138,7 +138,7 @@ class _FileUploadLocalState extends State<_FileUploadLocal> { radius: Corners.s8Border, backgroundColor: Theme.of(context).colorScheme.primary, hoverColor: - Theme.of(context).colorScheme.primary.withOpacity(0.9), + Theme.of(context).colorScheme.primary.withValues(alpha: 0.9), margin: const EdgeInsets.all(5), text: FlowyText( LocaleKeys.document_plugins_file_uploadMobileGallery.tr(), @@ -155,7 +155,7 @@ class _FileUploadLocalState extends State<_FileUploadLocal> { radius: Corners.s8Border, backgroundColor: Theme.of(context).colorScheme.primary, hoverColor: - Theme.of(context).colorScheme.primary.withOpacity(0.9), + Theme.of(context).colorScheme.primary.withValues(alpha: 0.9), margin: const EdgeInsets.all(5), text: FlowyText( LocaleKeys.document_plugins_file_uploadMobile.tr(), @@ -241,7 +241,7 @@ class _FileUploadNetworkState extends State<_FileUploadNetwork> { child: FlowyButton( backgroundColor: Theme.of(context).colorScheme.primary, hoverColor: - Theme.of(context).colorScheme.primary.withOpacity(0.9), + Theme.of(context).colorScheme.primary.withValues(alpha: 0.9), radius: Corners.s8Border, margin: const EdgeInsets.all(5), text: FlowyText( diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/font/customize_font_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/font/customize_font_toolbar_item.dart index ef943a7ef7..e0f63e57c7 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/font/customize_font_toolbar_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/font/customize_font_toolbar_item.dart @@ -9,8 +9,6 @@ import 'package:appflowy/workspace/application/settings/appearance/appearance_cu import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart'; import 'package:appflowy/workspace/presentation/settings/shared/setting_list_tile.dart'; import 'package:appflowy/workspace/presentation/settings/shared/setting_value_dropdown.dart'; -import 'package:appflowy_backend/log.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -21,68 +19,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:google_fonts/google_fonts.dart'; -const kFontToolbarItemId = 'editor.font'; - -@visibleForTesting -const kFontFamilyToolbarItemKey = ValueKey('FontFamilyToolbarItem'); - -final customizeFontToolbarItem = ToolbarItem( - id: kFontToolbarItemId, - group: 4, - isActive: onlyShowInTextType, - builder: (context, editorState, highlightColor, _, tooltipBuilder) { - final selection = editorState.selection!; - final popoverController = PopoverController(); - final String? currentFontFamily = editorState - .getDeltaAttributeValueInSelection(AppFlowyRichTextKeys.fontFamily); - - Widget child = FontFamilyDropDown( - currentFontFamily: currentFontFamily ?? '', - offset: const Offset(0, 12), - popoverController: popoverController, - onOpen: () => keepEditorFocusNotifier.increase(), - onClose: () => keepEditorFocusNotifier.decrease(), - onFontFamilyChanged: (fontFamily) async { - popoverController.close(); - try { - await editorState.formatDelta(selection, { - AppFlowyRichTextKeys.fontFamily: fontFamily, - }); - } catch (e) { - Log.error('Failed to set font family: $e'); - } - }, - onResetFont: () async { - popoverController.close(); - await editorState - .formatDelta(selection, {AppFlowyRichTextKeys.fontFamily: null}); - }, - child: FlowyButton( - key: kFontFamilyToolbarItemKey, - useIntrinsicWidth: true, - hoverColor: Colors.grey.withOpacity(0.3), - onTap: () => popoverController.show(), - text: const FlowySvg( - FlowySvgs.font_family_s, - size: Size.square(16.0), - color: Colors.white, - ), - ), - ); - - if (tooltipBuilder != null) { - child = tooltipBuilder( - context, - kFontToolbarItemId, - LocaleKeys.document_plugins_fonts.tr(), - child, - ); - } - - return child; - }, -); - class ThemeFontFamilySetting extends StatefulWidget { const ThemeFontFamilySetting({ super.key, @@ -163,6 +99,11 @@ class _FontFamilyDropDownState extends State { popoverKey: ThemeFontFamilySetting.popoverKey, popoverController: widget.popoverController, currentValue: currentValue, + margin: EdgeInsets.zero, + boxConstraints: const BoxConstraints( + maxWidth: 240, + maxHeight: 420, + ), onClose: () { query.value = ''; widget.onClose?.call(); @@ -171,27 +112,25 @@ class _FontFamilyDropDownState extends State { child: widget.child, popupBuilder: (_) { widget.onOpen?.call(); - return CustomScrollView( - shrinkWrap: true, - slivers: [ - SliverPadding( - padding: const EdgeInsets.only(right: 8), - sliver: SliverToBoxAdapter( - child: FlowyTextField( - key: ThemeFontFamilySetting.textFieldKey, - hintText: - LocaleKeys.settings_appearance_fontFamily_search.tr(), - autoFocus: false, - debounceDuration: const Duration(milliseconds: 300), - onChanged: (value) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: FlowyTextField( + key: ThemeFontFamilySetting.textFieldKey, + hintText: LocaleKeys.settings_appearance_fontFamily_search.tr(), + autoFocus: false, + debounceDuration: const Duration(milliseconds: 300), + onChanged: (value) { + setState(() { query.value = value; - }, - ), + }); + }, ), ), - const SliverToBoxAdapter( - child: SizedBox(height: 4), - ), + Container(height: 1, color: Theme.of(context).dividerColor), ValueListenableBuilder( valueListenable: query, builder: (context, value, child) { @@ -206,14 +145,32 @@ class _FontFamilyDropDownState extends State { .sorted((a, b) => levenshtein(a, b)) .toList(); } - return SliverFixedExtentList.builder( - itemBuilder: (context, index) => _fontFamilyItemButton( - context, - getGoogleFontSafely(displayed[index]), - ), - itemCount: displayed.length, - itemExtent: 32, - ); + return displayed.length >= 10 + ? Flexible( + child: ListView.builder( + padding: const EdgeInsets.all(8.0), + itemBuilder: (context, index) => + _fontFamilyItemButton( + context, + getGoogleFontSafely(displayed[index]), + ), + itemCount: displayed.length, + ), + ) + : Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: List.generate( + displayed.length, + (index) => _fontFamilyItemButton( + context, + getGoogleFontSafely(displayed[index]), + ), + ), + ), + ); }, ), ], @@ -233,16 +190,18 @@ class _FontFamilyDropDownState extends State { waitDuration: const Duration(milliseconds: 150), child: SizedBox( key: ValueKey(buttonFontFamily), - height: 32, + height: 36, child: FlowyButton( onHover: (_) => FocusScope.of(context).unfocus(), - text: FlowyText.medium( + text: FlowyText( buttonFontFamily.fontFamilyDisplayName, fontFamily: buttonFontFamily, + figmaLineHeight: 20, + fontWeight: FontWeight.w400, ), rightIcon: buttonFontFamily == widget.currentFontFamily.parseFontFamilyName() - ? const FlowySvg(FlowySvgs.check_s) + ? const FlowySvg(FlowySvgs.toolbar_check_m) : null, onTap: () { if (widget.onFontFamilyChanged != null) { diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/cover_editor_bloc.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/cover_editor_bloc.dart index 112cfda026..b891aecb6e 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/cover_editor_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/cover_editor_bloc.dart @@ -30,8 +30,7 @@ class ChangeCoverPopoverBloc void _dispatch() { on((event, emit) async { await event.map( - fetchPickedImagePaths: - (FetchPickedImagePaths fetchPickedImagePaths) async { + fetchPickedImagePaths: (fetchPickedImagePaths) async { final imageNames = await _getPreviouslyPickedImagePaths(); emit( @@ -41,11 +40,11 @@ class ChangeCoverPopoverBloc ), ); }, - deleteImage: (DeleteImage deleteImage) async { + deleteImage: (deleteImage) async { final currentState = state; final currentlySelectedImage = node.attributes[DocumentHeaderBlockKeys.coverDetails]; - if (currentState is Loaded) { + if (currentState is _Loaded) { await _deleteImageInStorage(deleteImage.path); if (currentlySelectedImage == deleteImage.path) { _removeCoverImageFromNode(); @@ -54,15 +53,15 @@ class ChangeCoverPopoverBloc .where((path) => path != deleteImage.path) .toList(); _updateImagePathsInStorage(updateImageList); - emit(Loaded(updateImageList)); + emit(ChangeCoverPopoverState.loaded(updateImageList)); } }, - clearAllImages: (ClearAllImages clearAllImages) async { + clearAllImages: (clearAllImages) async { final currentState = state; final currentlySelectedImage = node.attributes[DocumentHeaderBlockKeys.coverDetails]; - if (currentState is Loaded) { + if (currentState is _Loaded) { for (final image in currentState.imageNames) { await _deleteImageInStorage(image); if (currentlySelectedImage == image) { @@ -70,7 +69,7 @@ class ChangeCoverPopoverBloc } } _updateImagePathsInStorage([]); - emit(const Loaded([])); + emit(const ChangeCoverPopoverState.loaded([])); } }, ); @@ -113,18 +112,18 @@ class ChangeCoverPopoverBloc class ChangeCoverPopoverEvent with _$ChangeCoverPopoverEvent { const factory ChangeCoverPopoverEvent.fetchPickedImagePaths({ @Default(false) bool selectLatestImage, - }) = FetchPickedImagePaths; + }) = _FetchPickedImagePaths; - const factory ChangeCoverPopoverEvent.deleteImage(String path) = DeleteImage; - const factory ChangeCoverPopoverEvent.clearAllImages() = ClearAllImages; + const factory ChangeCoverPopoverEvent.deleteImage(String path) = _DeleteImage; + const factory ChangeCoverPopoverEvent.clearAllImages() = _ClearAllImages; } @freezed class ChangeCoverPopoverState with _$ChangeCoverPopoverState { - const factory ChangeCoverPopoverState.initial() = Initial; - const factory ChangeCoverPopoverState.loading() = Loading; + const factory ChangeCoverPopoverState.initial() = _Initial; + const factory ChangeCoverPopoverState.loading() = _Loading; const factory ChangeCoverPopoverState.loaded( List imageNames, { @Default(false) selectLatestImage, - }) = Loaded; + }) = _Loaded; } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/cover_title.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/cover_title.dart index 8111797b80..2c5062d408 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/cover_title.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/cover_title.dart @@ -1,10 +1,10 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/shared_context/shared_context.dart'; -import 'package:appflowy/plugins/document/presentation/editor_style.dart'; import 'package:appflowy/shared/text_field/text_filed_with_metric_lines.dart'; import 'package:appflowy/workspace/application/appearance_defaults.dart'; import 'package:appflowy/workspace/application/view/view_bloc.dart'; +import 'package:appflowy/workspace/application/view_info/view_info_bloc.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; @@ -92,7 +92,6 @@ class _InnerCoverTitleState extends State<_InnerCoverTitle> { builder: (context, state) { final appearance = context.read().state; return Container( - padding: EditorStyleCustomizer.documentPaddingWithOptionMenu, constraints: BoxConstraints(maxWidth: width), child: Theme( data: Theme.of(context).copyWith( @@ -220,6 +219,9 @@ class _InnerCoverTitleState extends State<_InnerCoverTitle> { .read() .add(ViewEvent.rename(titleTextController.text)); } + context + .read() + ?.add(ViewInfoEvent.titleChanged(titleTextController.text)); }, ); } @@ -239,6 +241,8 @@ class _InnerCoverTitleState extends State<_InnerCoverTitle> { return _moveCursorToNextLine(event.logicalKey); } else if (event.logicalKey == LogicalKeyboardKey.escape) { return _exitEditing(); + } else if (event.logicalKey == LogicalKeyboardKey.tab) { + return KeyEventResult.handled; } return KeyEventResult.ignored; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/custom_cover_picker.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/custom_cover_picker.dart index 3f84d04283..f5df4c0904 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/custom_cover_picker.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/custom_cover_picker.dart @@ -36,43 +36,49 @@ class _CoverImagePickerState extends State { ..add(const CoverImagePickerEvent.initialEvent()), child: BlocListener( listener: (context, state) { - if (state is NetworkImagePicked) { - state.successOrFail.fold( - (s) {}, - (e) => showSnapBar( - context, - LocaleKeys.document_plugins_cover_invalidImageUrl.tr(), - ), - ); - } - if (state is Done) { - state.successOrFail.fold( - (l) => widget.onFileSubmit(l), - (r) => showSnapBar( - context, - LocaleKeys.document_plugins_cover_failedToAddImageToGallery - .tr(), - ), - ); - } + state.maybeWhen( + networkImage: (successOrFail) { + successOrFail.fold( + (s) {}, + (e) => showSnapBar( + context, + LocaleKeys.document_plugins_cover_invalidImageUrl.tr(), + ), + ); + }, + done: (successOrFail) { + successOrFail.fold( + (l) => widget.onFileSubmit(l), + (r) => showSnapBar( + context, + LocaleKeys.document_plugins_cover_failedToAddImageToGallery + .tr(), + ), + ); + }, + orElse: () {}, + ); }, child: BlocBuilder( builder: (context, state) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - state is Loading - ? const SizedBox( - height: 180, - child: Center( - child: CircularProgressIndicator(), - ), - ) - : CoverImagePreviewWidget(state: state), + state.maybeMap( + loading: (_) => const SizedBox( + height: 180, + child: Center( + child: CircularProgressIndicator(), + ), + ), + orElse: () => CoverImagePreviewWidget(state: state), + ), const VSpace(10), NetworkImageUrlInput( onAdd: (url) { - context.read().add(UrlSubmit(url)); + context + .read() + .add(CoverImagePickerEvent.urlSubmit(url)); }, ), const VSpace(10), @@ -81,9 +87,9 @@ class _CoverImagePickerState extends State { widget.onBackPressed(); }, onSave: () { - context.read().add( - SaveToGallery(state), - ); + context + .read() + .add(CoverImagePickerEvent.saveToGallery(state)); }, ), ], @@ -196,7 +202,7 @@ class ImagePickerActionButtons extends StatelessWidget { class CoverImagePreviewWidget extends StatefulWidget { const CoverImagePreviewWidget({super.key, required this.state}); - final dynamic state; + final CoverImagePickerState state; @override State createState() => @@ -242,7 +248,9 @@ class _CoverImagePreviewWidgetState extends State { FlowyButton( hoverColor: Theme.of(context).hoverColor, onTap: () { - ctx.read().add(const PickFileImage()); + ctx + .read() + .add(const CoverImagePickerEvent.pickFileImage()); }, useIntrinsicWidth: true, leftIcon: const FlowySvg( @@ -265,7 +273,9 @@ class _CoverImagePreviewWidgetState extends State { top: 10, child: InkWell( onTap: () { - ctx.read().add(const DeleteImage()); + ctx + .read() + .add(const CoverImagePickerEvent.deleteImage()); }, child: DecoratedBox( decoration: BoxDecoration( @@ -291,42 +301,42 @@ class _CoverImagePreviewWidgetState extends State { decoration: BoxDecoration( color: Theme.of(context).colorScheme.secondary, borderRadius: Corners.s6Border, - image: widget.state is Initial - ? null - : widget.state is NetworkImagePicked - ? widget.state.successOrFail.fold( - (path) => DecorationImage( - image: NetworkImage(path), - fit: BoxFit.cover, - ), - (r) => null, - ) - : widget.state is FileImagePicked - ? DecorationImage( - image: FileImage(File(widget.state.path)), - fit: BoxFit.cover, - ) - : null, + image: widget.state.whenOrNull( + networkImage: (successOrFail) { + return successOrFail.fold( + (path) => DecorationImage( + image: NetworkImage(path), + fit: BoxFit.cover, + ), + (r) => null, + ); + }, + fileImage: (path) { + return DecorationImage( + image: FileImage(File(path)), + fit: BoxFit.cover, + ); + }, + ), + ), + child: widget.state.whenOrNull( + initial: () => _buildFilePickerWidget(context), + networkImage: (successOrFail) => successOrFail.fold( + (l) => null, + (r) => _buildFilePickerWidget( + context, + ), + ), ), - child: (widget.state is Initial) - ? _buildFilePickerWidget(context) - : (widget.state is NetworkImagePicked) - ? widget.state.successOrFail.fold( - (l) => null, - (r) => _buildFilePickerWidget( - context, - ), - ) - : null, ), - (widget.state is FileImagePicked) - ? _buildImageDeleteButton(context) - : (widget.state is NetworkImagePicked) - ? widget.state.successOrFail.fold( - (l) => _buildImageDeleteButton(context), - (r) => const SizedBox.shrink(), - ) - : const SizedBox.shrink(), + widget.state.maybeWhen( + fileImage: (_) => _buildImageDeleteButton(context), + networkImage: (successOrFail) => successOrFail.fold( + (l) => _buildImageDeleteButton(context), + (r) => const SizedBox.shrink(), + ), + orElse: () => const SizedBox.shrink(), + ), ], ); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/custom_cover_picker_bloc.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/custom_cover_picker_bloc.dart index 9ead1ff3f4..64e21eb773 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/custom_cover_picker_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/custom_cover_picker_bloc.dart @@ -28,10 +28,10 @@ class CoverImagePickerBloc on( (event, emit) async { await event.map( - initialEvent: (InitialEvent initialEvent) { + initialEvent: (initialEvent) { emit(const CoverImagePickerState.initial()); }, - urlSubmit: (UrlSubmit urlSubmit) async { + urlSubmit: (urlSubmit) async { emit(const CoverImagePickerState.loading()); final validateImage = await _validateURL(urlSubmit.path); if (validateImage) { @@ -53,7 +53,7 @@ class CoverImagePickerBloc ); } }, - pickFileImage: (PickFileImage pickFileImage) async { + pickFileImage: (pickFileImage) async { final imagePickerResults = await _pickImages(); if (imagePickerResults != null) { emit(CoverImagePickerState.fileImage(imagePickerResults)); @@ -61,10 +61,10 @@ class CoverImagePickerBloc emit(const CoverImagePickerState.initial()); } }, - deleteImage: (DeleteImage deleteImage) { + deleteImage: (deleteImage) { emit(const CoverImagePickerState.initial()); }, - saveToGallery: (SaveToGallery saveToGallery) async { + saveToGallery: (saveToGallery) async { emit(const CoverImagePickerState.loading()); final saveImage = await _saveToGallery(saveToGallery.previousState); if (saveImage != null) { @@ -93,7 +93,7 @@ class CoverImagePickerBloc final List imagePaths = prefs.getStringList(kLocalImagesKey) ?? []; final directory = await _coverPath(); - if (state is FileImagePicked) { + if (state is _FileImagePicked) { try { final path = state.path; final newPath = p.join(directory, p.split(path).last); @@ -102,7 +102,7 @@ class CoverImagePickerBloc } catch (e) { return null; } - } else if (state is NetworkImagePicked) { + } else if (state is _NetworkImagePicked) { try { final url = state.successOrFail.fold((path) => path, (r) => null); if (url != null) { @@ -197,25 +197,25 @@ class CoverImagePickerBloc @freezed class CoverImagePickerEvent with _$CoverImagePickerEvent { - const factory CoverImagePickerEvent.urlSubmit(String path) = UrlSubmit; - const factory CoverImagePickerEvent.pickFileImage() = PickFileImage; - const factory CoverImagePickerEvent.deleteImage() = DeleteImage; + const factory CoverImagePickerEvent.urlSubmit(String path) = _UrlSubmit; + const factory CoverImagePickerEvent.pickFileImage() = _PickFileImage; + const factory CoverImagePickerEvent.deleteImage() = _DeleteImage; const factory CoverImagePickerEvent.saveToGallery( CoverImagePickerState previousState, - ) = SaveToGallery; - const factory CoverImagePickerEvent.initialEvent() = InitialEvent; + ) = _SaveToGallery; + const factory CoverImagePickerEvent.initialEvent() = _InitialEvent; } @freezed class CoverImagePickerState with _$CoverImagePickerState { - const factory CoverImagePickerState.initial() = Initial; - const factory CoverImagePickerState.loading() = Loading; + const factory CoverImagePickerState.initial() = _Initial; + const factory CoverImagePickerState.loading() = _Loading; const factory CoverImagePickerState.networkImage( FlowyResult successOrFail, - ) = NetworkImagePicked; - const factory CoverImagePickerState.fileImage(String path) = FileImagePicked; + ) = _NetworkImagePicked; + const factory CoverImagePickerState.fileImage(String path) = _FileImagePicked; const factory CoverImagePickerState.done( FlowyResult, FlowyError> successOrFail, - ) = Done; + ) = _Done; } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_cover_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_cover_widget.dart index 54feee3d90..16605367ca 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_cover_widget.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_cover_widget.dart @@ -17,6 +17,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/migration/ import 'package:appflowy/plugins/document/presentation/editor_style.dart'; import 'package:appflowy/shared/appflowy_network_image.dart'; import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy/workspace/application/view/view_listener.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; @@ -73,12 +74,14 @@ class DocumentCoverWidget extends StatefulWidget { required this.editorState, required this.onIconChanged, required this.view, + required this.tabs, }); final Node node; final EditorState editorState; final ValueChanged onIconChanged; final ViewPB view; + final List tabs; @override State createState() => _DocumentCoverWidgetState(); @@ -157,6 +160,7 @@ class _DocumentCoverWidgetState extends State { final offset = _calculateIconLeft(context, constraints); return Column( mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ Stack( children: [ @@ -170,6 +174,8 @@ class _DocumentCoverWidgetState extends State { hasIcon: hasIcon, offset: offset, isCoverTitleHovered: isCoverTitleHovered, + documentId: view.id, + tabs: widget.tabs, ), ), if (hasCover) @@ -182,48 +188,65 @@ class _DocumentCoverWidgetState extends State { onChangeCover: (type, details) => _saveIconOrCover(cover: (type, details)), ), - _buildCoverIcon( - context, - constraints, - offset, - ), + _buildAlignedCoverIcon(context), ], ), - Padding( - padding: const EdgeInsets.only(bottom: 12.0), - child: MouseRegion( - onEnter: (event) => isCoverTitleHovered.value = true, - onExit: (event) => isCoverTitleHovered.value = false, - child: CoverTitle( - view: widget.view, - ), - ), - ), + _buildAlignedTitle(context), ], ); }, ); } - Widget _buildCoverIcon( - BuildContext context, - BoxConstraints constraints, - double offset, - ) { - if (!hasIcon || offset == 0) { + Widget _buildAlignedTitle(BuildContext context) { + return Center( + child: Container( + constraints: BoxConstraints( + maxWidth: widget.editorState.editorStyle.maxWidth ?? double.infinity, + ), + padding: widget.editorState.editorStyle.padding + + const EdgeInsets.symmetric(horizontal: 44), + child: MouseRegion( + onEnter: (event) => isCoverTitleHovered.value = true, + onExit: (event) => isCoverTitleHovered.value = false, + child: CoverTitle( + view: widget.view, + ), + ), + ), + ); + } + + Widget _buildAlignedCoverIcon(BuildContext context) { + if (!hasIcon) { return const SizedBox.shrink(); } return Positioned( - // if hasCover, there shouldn't be icons present so the icon can - // be closer to the bottom. - left: offset, bottom: hasCover ? kToolbarHeight - kIconHeight / 2 : kToolbarHeight, - child: DocumentIcon( - editorState: widget.editorState, - node: widget.node, - icon: viewIcon, - onChangeIcon: (icon) => _saveIconOrCover(icon: icon), + left: 0, + right: 0, + child: Center( + child: Container( + constraints: BoxConstraints( + maxWidth: + widget.editorState.editorStyle.maxWidth ?? double.infinity, + ), + padding: widget.editorState.editorStyle.padding + + const EdgeInsets.symmetric(horizontal: 44), + child: Row( + children: [ + DocumentIcon( + editorState: widget.editorState, + node: widget.node, + icon: viewIcon, + documentId: view.id, + onChangeIcon: (icon) => _saveIconOrCover(icon: icon), + ), + Spacer(), + ], + ), + ), ), ); } @@ -339,7 +362,9 @@ class DocumentHeaderToolbar extends StatefulWidget { required this.hasIcon, required this.onIconOrCoverChanged, required this.offset, + this.documentId, required this.isCoverTitleHovered, + required this.tabs, }); final Node node; @@ -349,7 +374,9 @@ class DocumentHeaderToolbar extends StatefulWidget { final void Function({(CoverType, String?)? cover, EmojiIconData? icon}) onIconOrCoverChanged; final double offset; + final String? documentId; final ValueNotifier isCoverTitleHovered; + final List tabs; @override State createState() => _DocumentHeaderToolbarState(); @@ -468,9 +495,11 @@ class _DocumentHeaderToolbarState extends State { popupBuilder: (BuildContext popoverContext) { isPopoverOpen = true; return FlowyIconEmojiPicker( - onSelectedEmoji: (result) { - widget.onIconOrCoverChanged(icon: result); - _popoverController.close(); + tabs: widget.tabs, + documentId: widget.documentId, + onSelectedEmoji: (r) { + widget.onIconOrCoverChanged(icon: r.data); + if (!r.keepOpen) _popoverController.close(); }, ); }, @@ -628,7 +657,7 @@ class DocumentCoverState extends State { fillColor: Theme.of(context) .colorScheme .onSurfaceVariant - .withOpacity(0.5), + .withValues(alpha: 0.5), height: 32, title: LocaleKeys.document_plugins_cover_changeCover.tr(), ), @@ -714,8 +743,10 @@ class DocumentCoverState extends State { onPressed: () => popoverController.show(), hoverColor: Theme.of(context).colorScheme.surface, textColor: Theme.of(context).colorScheme.tertiary, - fillColor: - Theme.of(context).colorScheme.surface.withOpacity(0.5), + fillColor: Theme.of(context) + .colorScheme + .surface + .withValues(alpha: 0.5), title: LocaleKeys.document_plugins_cover_changeCover.tr(), ), ), @@ -763,15 +794,29 @@ class DocumentCoverState extends State { } Future onCoverChanged(CoverType type, String? details) async { - if (type == CoverType.file && details != null && !isURL(details)) { + final previousType = CoverType.fromString( + widget.node.attributes[DocumentHeaderBlockKeys.coverType], + ); + final previousDetails = + widget.node.attributes[DocumentHeaderBlockKeys.coverDetails]; + + bool isFileType(CoverType type, String? details) => + type == CoverType.file && details != null && !isURL(details); + + if (isFileType(type, details)) { if (_isLocalMode()) { - details = await saveImageToLocalStorage(details); + details = await saveImageToLocalStorage(details!); } else { // else we should save the image to cloud storage - (details, _) = await saveImageToCloudStorage(details, widget.view.id); + (details, _) = await saveImageToCloudStorage(details!, widget.view.id); } } widget.onChangeCover(type, details); + + // After cover change,delete from localstorage if previous cover was image type + if (isFileType(previousType, previousDetails) && _isLocalMode()) { + await deleteImageFromLocalStorage(previousDetails); + } } void setOverlayButtonsHidden(bool value) { @@ -795,8 +840,8 @@ class DeleteCoverButton extends StatelessWidget { @override Widget build(BuildContext context) { final fillColor = UniversalPlatform.isDesktopOrWeb - ? Theme.of(context).colorScheme.surface.withOpacity(0.5) - : Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.5); + ? Theme.of(context).colorScheme.surface.withValues(alpha: 0.5) + : Theme.of(context).colorScheme.onSurfaceVariant.withValues(alpha: 0.5); final svgColor = UniversalPlatform.isDesktopOrWeb ? Theme.of(context).colorScheme.tertiary : Theme.of(context).colorScheme.onPrimary; @@ -822,11 +867,13 @@ class DocumentIcon extends StatefulWidget { required this.editorState, required this.icon, required this.onChangeIcon, + this.documentId, }); final Node node; final EditorState editorState; final EmojiIconData icon; + final String? documentId; final ValueChanged onChangeIcon; @override @@ -838,9 +885,7 @@ class _DocumentIconState extends State { @override Widget build(BuildContext context) { - Widget child = EmojiIconWidget( - emoji: widget.icon, - ); + Widget child = EmojiIconWidget(emoji: widget.icon); if (UniversalPlatform.isDesktopOrWeb) { child = AppFlowyPopover( @@ -852,9 +897,16 @@ class _DocumentIconState extends State { child: child, popupBuilder: (BuildContext popoverContext) { return FlowyIconEmojiPicker( - onSelectedEmoji: (result) { - widget.onChangeIcon(result); - _popoverController.close(); + initialType: widget.icon.type.toPickerTabType(), + tabs: const [ + PickerTabType.emoji, + PickerTabType.icon, + PickerTabType.custom, + ], + documentId: widget.documentId, + onSelectedEmoji: (r) { + widget.onChangeIcon(r.data); + if (!r.keepOpen) _popoverController.close(); }, ); }, @@ -864,7 +916,12 @@ class _DocumentIconState extends State { child: child, onTap: () async { final result = await context.push( - MobileEmojiPickerScreen.routeName, + Uri( + path: MobileEmojiPickerScreen.routeName, + queryParameters: { + MobileEmojiPickerScreen.iconSelectedType: widget.icon.type.name, + }, + ).toString(), ); if (result != null) { widget.onChangeIcon(result); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart index 77b0c158f5..cda76233d6 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart @@ -1,9 +1,17 @@ import 'dart:convert'; +import 'dart:io'; import 'package:appflowy/plugins/base/emoji/emoji_text.dart'; +import 'package:appflowy/shared/appflowy_network_image.dart'; +import 'package:appflowy/shared/appflowy_network_svg.dart'; import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart'; +import 'package:appflowy/user/application/user_service.dart'; import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flowy_svg/flowy_svg.dart'; import 'package:flutter/material.dart'; +import 'package:string_validator/string_validator.dart'; import '../../../../../shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; import '../../../../base/icon/icon_widget.dart'; @@ -34,7 +42,10 @@ class _EmojiIconWidgetState extends State { child: Container( decoration: BoxDecoration( color: !hover - ? Theme.of(context).colorScheme.inverseSurface.withOpacity(0.5) + ? Theme.of(context) + .colorScheme + .inverseSurface + .withValues(alpha: 0.5) : Colors.transparent, borderRadius: BorderRadius.circular(8), ), @@ -55,43 +66,154 @@ class _EmojiIconWidgetState extends State { } } -class RawEmojiIconWidget extends StatelessWidget { +class RawEmojiIconWidget extends StatefulWidget { const RawEmojiIconWidget({ super.key, required this.emoji, required this.emojiSize, + this.enableColor = true, + this.lineHeight, }); final EmojiIconData emoji; final double emojiSize; + final bool enableColor; + final double? lineHeight; + + @override + State createState() => _RawEmojiIconWidgetState(); +} + +class _RawEmojiIconWidgetState extends State { + UserProfilePB? userProfile; + + EmojiIconData get emoji => widget.emoji; + + @override + void initState() { + super.initState(); + loadUserProfile(); + } + + @override + void didUpdateWidget(RawEmojiIconWidget oldWidget) { + super.didUpdateWidget(oldWidget); + loadUserProfile(); + } @override Widget build(BuildContext context) { - final defaultEmoji = EmojiText( - emoji: '❓', - fontSize: emojiSize, - textAlign: TextAlign.center, + final defaultEmoji = SizedBox( + width: widget.emojiSize, + child: EmojiText( + emoji: '❓', + fontSize: widget.emojiSize, + textAlign: TextAlign.center, + ), ); try { - switch (emoji.type) { + switch (widget.emoji.type) { case FlowyIconType.emoji: - return EmojiText( - emoji: emoji.emoji, - fontSize: emojiSize, - textAlign: TextAlign.center, + return FlowyText.emoji( + widget.emoji.emoji, + fontSize: widget.emojiSize, + textAlign: TextAlign.justify, + lineHeight: widget.lineHeight, ); case FlowyIconType.icon: - final iconData = IconsData.fromJson(jsonDecode(emoji.emoji)); - return IconWidget( - data: iconData, - size: emojiSize, + IconsData iconData = IconsData.fromJson( + jsonDecode(widget.emoji.emoji), + ); + if (!widget.enableColor) { + iconData = iconData.noColor(); + } + + final iconSize = widget.emojiSize; + return IconWidget( + iconsData: iconData, + size: iconSize, + ); + case FlowyIconType.custom: + final url = widget.emoji.emoji; + final isSvg = url.endsWith('.svg'); + final hasUserProfile = userProfile != null; + if (isURL(url)) { + Widget child = const SizedBox.shrink(); + if (isSvg) { + child = FlowyNetworkSvg( + url, + headers: + hasUserProfile ? _buildRequestHeader(userProfile!) : {}, + width: widget.emojiSize, + height: widget.emojiSize, + ); + } else if (hasUserProfile) { + child = FlowyNetworkImage( + url: url, + width: widget.emojiSize, + height: widget.emojiSize, + userProfilePB: userProfile, + errorWidgetBuilder: (context, url, error) { + return const SizedBox.shrink(); + }, + ); + } + return SizedBox.square( + dimension: widget.emojiSize, + child: child, + ); + } + final imageFile = File(url); + if (!imageFile.existsSync()) { + throw PathNotFoundException(url, const OSError()); + } + return SizedBox.square( + dimension: widget.emojiSize, + child: isSvg + ? SvgPicture.file( + File(url), + width: widget.emojiSize, + height: widget.emojiSize, + ) + : Image.file( + imageFile, + fit: BoxFit.cover, + width: widget.emojiSize, + height: widget.emojiSize, + ), ); - default: - return defaultEmoji; } } catch (e) { Log.error("Display widget error: $e"); return defaultEmoji; } } + + Map _buildRequestHeader(UserProfilePB userProfilePB) { + final header = {}; + final token = userProfilePB.token; + try { + final decodedToken = jsonDecode(token); + header['Authorization'] = 'Bearer ${decodedToken['access_token']}'; + } catch (e) { + Log.error('Unable to decode token: $e'); + } + return header; + } + + Future loadUserProfile() async { + if (userProfile != null) return; + if (emoji.type == FlowyIconType.custom) { + final userProfile = + (await UserBackendService.getCurrentUserProfile()).fold( + (userProfile) => userProfile, + (l) => null, + ); + if (mounted) { + setState(() { + this.userProfile = userProfile; + }); + } + } + } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/heading/heading_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/heading/heading_toolbar_item.dart index c5e1758435..3d0c199ea2 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/heading/heading_toolbar_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/heading/heading_toolbar_item.dart @@ -140,7 +140,7 @@ class HeadingPopup extends StatelessWidget { }, child: FlowyButton( useIntrinsicWidth: true, - hoverColor: Colors.grey.withOpacity(0.3), + hoverColor: Colors.grey.withValues(alpha: 0.3), text: child, ), ); @@ -209,7 +209,7 @@ class HeadingButton extends StatelessWidget { Widget build(BuildContext context) { return FlowyButton( useIntrinsicWidth: true, - hoverColor: Colors.grey.withOpacity(0.3), + hoverColor: Colors.grey.withValues(alpha: 0.3), onTap: onTap, text: FlowyTooltip( message: tooltip, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/custom_image_block_component/custom_image_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/custom_image_block_component/custom_image_block_component.dart index 7a68ad99b9..7f0105134d 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/custom_image_block_component/custom_image_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/custom_image_block_component/custom_image_block_component.dart @@ -9,8 +9,9 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_p import 'package:appflowy/plugins/document/presentation/editor_plugins/image/custom_image_block_component/unsupport_image_widget.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_placeholder.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/image/resizeable_image.dart'; +import 'package:appflowy/shared/custom_image_cache_manager.dart'; +import 'package:appflowy/shared/permission/permission_checker.dart'; import 'package:appflowy/startup/startup.dart'; -import 'package:appflowy/workspace/presentation/home/toast.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy/workspace/presentation/widgets/image_viewer/image_provider.dart'; import 'package:appflowy/workspace/presentation/widgets/image_viewer/interactive_image_viewer.dart'; @@ -19,6 +20,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; +import 'package:saver_gallery/saver_gallery.dart'; import 'package:string_validator/string_validator.dart'; import 'package:universal_platform/universal_platform.dart'; @@ -81,6 +83,7 @@ Node customImageNode({ typedef CustomImageBlockComponentMenuBuilder = Widget Function( Node node, CustomImageBlockComponentState state, + ValueNotifier imageStateNotifier, ); class CustomImageBlockComponentBuilder extends BlockComponentBuilder { @@ -120,6 +123,7 @@ class CustomImageBlockComponent extends BlockComponentStatefulWidget { required super.node, super.showActions, super.actionBuilder, + super.actionTrailingBuilder, super.configuration = const BlockComponentConfiguration(), this.showMenu = false, this.menuBuilder, @@ -149,6 +153,8 @@ class CustomImageBlockComponentState extends State late final editorState = Provider.of(context, listen: false); final showActionsNotifier = ValueNotifier(false); + final imageStateNotifier = + ValueNotifier(ResizableImageState.loading); bool alwaysShowMenu = false; @@ -185,6 +191,7 @@ class CustomImageBlockComponentState extends State editable: editorState.editable, alignment: alignment, type: imageType, + onStateChange: (state) => imageStateNotifier.value = state, onDoubleTap: () => showDialog( context: context, builder: (_) => InteractiveImageViewer( @@ -220,6 +227,7 @@ class CustomImageBlockComponentState extends State delegate: this, listenable: editorState.selectionNotifier, blockColor: editorState.editorStyle.selectionColor, + selectionAboveBlock: true, supportTypes: const [BlockSelectionType.block], child: child, ); @@ -229,6 +237,7 @@ class CustomImageBlockComponentState extends State child = BlockComponentActionWrapper( node: node, actionBuilder: widget.actionBuilder!, + actionTrailingBuilder: widget.actionTrailingBuilder, child: child, ); } @@ -248,19 +257,21 @@ class CustomImageBlockComponentState extends State child: ValueListenableBuilder( valueListenable: showActionsNotifier, builder: (_, value, child) { - final url = node.attributes[CustomImageBlockKeys.url]; return Stack( children: [ - BlockSelectionContainer( - node: node, - delegate: this, - listenable: editorState.selectionNotifier, - cursorColor: editorState.editorStyle.cursorColor, - selectionColor: editorState.editorStyle.selectionColor, - child: child!, - ), - if (value && url.isNotEmpty == true) - widget.menuBuilder!(widget.node, this), + editorState.editable + ? BlockSelectionContainer( + node: node, + delegate: this, + listenable: editorState.selectionNotifier, + cursorColor: editorState.editorStyle.cursorColor, + selectionColor: + editorState.editorStyle.selectionColor, + child: child!, + ) + : child!, + if (value) + widget.menuBuilder!(widget.node, this, imageStateNotifier), ], ); }, @@ -303,7 +314,7 @@ class CustomImageBlockComponentState extends State }) { final imageBox = imageKey.currentContext?.findRenderObject(); if (imageBox is RenderBox) { - return Offset.zero & imageBox.size; + return padding.topLeft & imageBox.size; } return Rect.zero; } @@ -367,7 +378,6 @@ class CustomImageBlockComponentState extends State onTap: () async { context.pop(); showToastNotification( - context, message: LocaleKeys.document_plugins_image_copiedToPasteBoard.tr(), ); await getIt().setPlainText(url); @@ -382,11 +392,8 @@ class CustomImageBlockComponentState extends State ), onTap: () async { context.pop(); - showSnackBarMessage( - context, - LocaleKeys.document_plugins_image_copiedToPasteBoard.tr(), - ); - await getIt().setPlainText(url); + // save the image to the photo library + await _saveImageToGallery(url); }, ), ]; @@ -407,4 +414,27 @@ class CustomImageBlockComponentState extends State return true; } + + Future _saveImageToGallery(String url) async { + final permission = await PermissionChecker.checkPhotoPermission(context); + if (!permission) { + return; + } + + final imageFile = await CustomImageCacheManager().getSingleFile(url); + if (imageFile.existsSync()) { + final result = await SaverGallery.saveImage( + imageFile.readAsBytesSync(), + fileName: imageFile.basename, + skipIfExists: false, + ); + if (mounted) { + showToastNotification( + message: result.isSuccess + ? LocaleKeys.document_imageBlock_successToAddImageToGallery.tr() + : LocaleKeys.document_imageBlock_failedToAddImageToGallery.tr(), + ); + } + } + } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/custom_image_block_component/image_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/custom_image_block_component/image_menu.dart index bffb651d29..d11d943066 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/custom_image_block_component/image_menu.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/custom_image_block_component/image_menu.dart @@ -6,8 +6,10 @@ import 'package:appflowy/plugins/document/application/document_bloc.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/block_menu/block_menu_button.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/image/common.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/image/resizeable_image.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy/workspace/presentation/widgets/image_viewer/image_provider.dart'; import 'package:appflowy/workspace/presentation/widgets/image_viewer/interactive_image_viewer.dart'; @@ -25,10 +27,12 @@ class ImageMenu extends StatefulWidget { super.key, required this.node, required this.state, + required this.imageStateNotifier, }); final Node node; final CustomImageBlockComponentState state; + final ValueNotifier imageStateNotifier; @override State createState() => _ImageMenuState(); @@ -39,45 +43,61 @@ class _ImageMenuState extends State { @override Widget build(BuildContext context) { + final isPlaceholder = url == null || url!.isEmpty; final theme = Theme.of(context); - return Container( - height: 32, - decoration: BoxDecoration( - color: theme.cardColor, - boxShadow: [ - BoxShadow( - blurRadius: 5, - spreadRadius: 1, - color: Colors.black.withOpacity(0.1), + return ValueListenableBuilder( + valueListenable: widget.imageStateNotifier, + builder: (_, state, child) { + if (state == ResizableImageState.loading && !isPlaceholder) { + return const SizedBox.shrink(); + } + + return Container( + height: 32, + decoration: BoxDecoration( + color: theme.cardColor, + boxShadow: [ + BoxShadow( + blurRadius: 5, + spreadRadius: 1, + color: Colors.black.withValues(alpha: 0.1), + ), + ], + borderRadius: BorderRadius.circular(4.0), ), - ], - borderRadius: BorderRadius.circular(4.0), - ), - child: Row( - children: [ - const HSpace(4), - MenuBlockButton( - tooltip: LocaleKeys.document_imageBlock_openFullScreen.tr(), - iconData: FlowySvgs.full_view_s, - onTap: openFullScreen, + child: Row( + children: [ + const HSpace(4), + if (!isPlaceholder) ...[ + MenuBlockButton( + tooltip: LocaleKeys.document_imageBlock_openFullScreen.tr(), + iconData: FlowySvgs.full_view_s, + onTap: openFullScreen, + ), + const HSpace(4), + MenuBlockButton( + tooltip: LocaleKeys.editor_copy.tr(), + iconData: FlowySvgs.copy_s, + onTap: copyImageLink, + ), + const HSpace(4), + ], + if (widget.state.editorState.editable) ...[ + if (!isPlaceholder) ...[ + _ImageAlignButton(node: widget.node, state: widget.state), + const _Divider(), + ], + MenuBlockButton( + tooltip: LocaleKeys.button_delete.tr(), + iconData: FlowySvgs.trash_s, + onTap: deleteImage, + ), + const HSpace(4), + ], + ], ), - const HSpace(4), - MenuBlockButton( - tooltip: LocaleKeys.editor_copy.tr(), - iconData: FlowySvgs.copy_s, - onTap: copyImageLink, - ), - const HSpace(4), - _ImageAlignButton(node: widget.node, state: widget.state), - const _Divider(), - MenuBlockButton( - tooltip: LocaleKeys.button_delete.tr(), - iconData: FlowySvgs.trash_s, - onTap: deleteImage, - ), - const HSpace(4), - ], - ), + ); + }, ); } @@ -97,14 +117,12 @@ class _ImageMenuState extends State { if (mounted) { showToastNotification( - context, message: LocaleKeys.message_copy_success.tr(), ); } } catch (e) { if (mounted) { showToastNotification( - context, message: LocaleKeys.message_copy_fail.tr(), type: ToastificationType.error, ); @@ -126,7 +144,8 @@ class _ImageMenuState extends State { showDialog( context: context, builder: (_) => InteractiveImageViewer( - userProfile: context.read().state.userProfilePB, + userProfile: context.read()?.userProfile ?? + context.read().state.userProfilePB, imageProvider: AFBlockImageProvider( images: [ ImageBlockData( @@ -136,11 +155,13 @@ class _ImageMenuState extends State { ), ), ], - onDeleteImage: (_) async { - final transaction = widget.state.editorState.transaction; - transaction.deleteNode(widget.node); - await widget.state.editorState.apply(transaction); - }, + onDeleteImage: widget.state.editorState.editable + ? (_) async { + final transaction = widget.state.editorState.transaction; + transaction.deleteNode(widget.node); + await widget.state.editorState.apply(transaction); + } + : null, ), ), ); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_selection_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_selection_menu.dart index f41127b78d..5a30a4dda5 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_selection_menu.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_selection_menu.dart @@ -58,26 +58,20 @@ extension InsertImage on EditorState { if (selection == null || !selection.isCollapsed) { return; } - final node = getNodeAtPath(selection.end.path); - if (node == null) { + final path = selection.end.path; + final node = getNodeAtPath(path); + final delta = node?.delta; + if (node == null || delta == null) { return; } final emptyImage = imageNode(url: '') ..extraInfos = {kImagePlaceholderKey: key}; - final transaction = this.transaction; - // if the current node is empty paragraph, replace it with image node - if (node.type == ParagraphBlockKeys.type && - (node.delta?.isEmpty ?? false)) { - transaction - ..insertNode(node.path, emptyImage) - ..deleteNode(node); - } else { - transaction.insertNode(node.path.next, emptyImage); - } - transaction.afterSelection = - Selection.collapsed(Position(path: node.path.next)); - transaction.selectionExtraInfo = {}; + final insertedPath = delta.isEmpty ? path : path.next; + final transaction = this.transaction + ..insertNode(insertedPath, emptyImage) + ..insertNode(insertedPath, paragraphNode()) + ..afterSelection = Selection.collapsed(Position(path: insertedPath.next)); return apply(transaction); } @@ -87,26 +81,20 @@ extension InsertImage on EditorState { if (selection == null || !selection.isCollapsed) { return; } - final node = getNodeAtPath(selection.end.path); - if (node == null) { + final path = selection.end.path; + final node = getNodeAtPath(path); + final delta = node?.delta; + if (node == null || delta == null) { return; } final emptyBlock = multiImageNode() ..extraInfos = {kMultiImagePlaceholderKey: key}; - final transaction = this.transaction; - // if the current node is empty paragraph, replace it with image node - if (node.type == ParagraphBlockKeys.type && - (node.delta?.isEmpty ?? false)) { - transaction - ..insertNode(node.path, emptyBlock) - ..deleteNode(node); - } else { - transaction.insertNode(node.path.next, emptyBlock); - } - transaction.afterSelection = - Selection.collapsed(Position(path: node.path.next)); - transaction.selectionExtraInfo = {}; + final insertedPath = delta.isEmpty ? path : path.next; + final transaction = this.transaction + ..insertNode(insertedPath, emptyBlock) + ..insertNode(insertedPath, paragraphNode()) + ..afterSelection = Selection.collapsed(Position(path: insertedPath.next)); return apply(transaction); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_util.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_util.dart index efa5721382..74d1955312 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_util.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_util.dart @@ -117,3 +117,16 @@ Future> extractAndUploadImages( return images; } + +@visibleForTesting +int deleteImageTestCounter = 0; + +Future deleteImageFromLocalStorage(String localImagePath) async { + try { + await File(localImagePath) + .delete() + .whenComplete(() => deleteImageTestCounter++); + } catch (e) { + Log.error('cannot delete image file', e); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/multi_image_block_component/layouts/image_browser_layout.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/multi_image_block_component/layouts/image_browser_layout.dart index 9f6b10cf3c..eb8ddba0b8 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/multi_image_block_component/layouts/image_browser_layout.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/multi_image_block_component/layouts/image_browser_layout.dart @@ -1,7 +1,5 @@ import 'dart:io'; -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/application/document_bloc.dart'; @@ -11,6 +9,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/image/mult import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_block_component.dart'; import 'package:appflowy/shared/appflowy_network_image.dart'; import 'package:appflowy/shared/patterns/file_type_patterns.dart'; +import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; import 'package:appflowy/workspace/presentation/widgets/image_viewer/image_provider.dart'; import 'package:appflowy/workspace/presentation/widgets/image_viewer/interactive_image_viewer.dart'; import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart'; @@ -23,11 +22,12 @@ import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; +import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../image_render.dart'; -const _thumbnailItemSize = 100.0; +const _thumbnailItemSize = 100.0, _imageHeight = 400.0; class ImageBrowserLayout extends ImageBlockMultiLayout { const ImageBrowserLayout({ @@ -53,18 +53,19 @@ class _ImageBrowserLayoutState extends State { @override void initState() { super.initState(); - _userProfile = context.read().state.userProfilePB; + _userProfile = context.read()?.userProfile ?? + context.read().state.userProfilePB; } @override Widget build(BuildContext context) { - return Stack( + final gallery = Stack( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( - height: 400, + height: _imageHeight, width: MediaQuery.of(context).size.width, child: GestureDetector( onDoubleTap: () => _openInteractiveViewer(context), @@ -135,7 +136,8 @@ class _ImageBrowserLayoutState extends State { ), DecoratedBox( decoration: BoxDecoration( - color: Colors.white.withOpacity(0.5), + color: + Colors.white.withValues(alpha: 0.5), ), child: Center( child: FlowyText( @@ -224,8 +226,9 @@ class _ImageBrowserLayoutState extends State { ? const SizedBox.shrink() : SizedBox.expand( child: DecoratedBox( - decoration: - BoxDecoration(color: Colors.white.withOpacity(0.5)), + decoration: BoxDecoration( + color: Colors.white.withValues(alpha: 0.5), + ), child: Center( child: Row( mainAxisAlignment: MainAxisAlignment.center, @@ -255,6 +258,10 @@ class _ImageBrowserLayoutState extends State { ), ], ); + return SizedBox( + height: _imageHeight + _thumbnailItemSize + 20, + child: gallery, + ); } void _openInteractiveViewer(BuildContext context, [int? index]) => showDialog( @@ -385,8 +392,8 @@ class _ThumbnailItemState extends State { child: FlowyHover( resetHoverOnRebuild: false, style: HoverStyle( - backgroundColor: Colors.black.withOpacity(0.6), - hoverColor: Colors.black.withOpacity(0.9), + backgroundColor: Colors.black.withValues(alpha: 0.6), + hoverColor: Colors.black.withValues(alpha: 0.9), ), child: const Padding( padding: EdgeInsets.all(4), diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/multi_image_block_component/layouts/multi_image_layouts.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/multi_image_block_component/layouts/multi_image_layouts.dart index 00919a20cc..43d1c7ae36 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/multi_image_block_component/layouts/multi_image_layouts.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/multi_image_block_component/layouts/multi_image_layouts.dart @@ -1,10 +1,9 @@ -import 'package:flutter/material.dart'; - import 'package:appflowy/plugins/document/presentation/editor_plugins/image/common.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/layouts/image_browser_layout.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/layouts/image_grid_layout.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_block_component.dart'; import 'package:appflowy_editor/appflowy_editor.dart' hide ResizableImage; +import 'package:flutter/material.dart'; abstract class ImageBlockMultiLayout extends StatefulWidget { const ImageBlockMultiLayout({ @@ -65,7 +64,6 @@ class ImageLayoutRender extends StatelessWidget { isLocalMode: isLocalMode, ); case MultiImageLayout.browser: - default: return ImageBrowserLayout( node: node, editorState: editorState, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_block_component.dart index 272b492835..51da975938 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_block_component.dart @@ -13,10 +13,11 @@ import 'package:universal_platform/universal_platform.dart'; const kMultiImagePlaceholderKey = 'multiImagePlaceholderKey'; -Node multiImageNode() => Node( +Node multiImageNode({List? images}) => Node( type: MultiImageBlockKeys.type, attributes: { - MultiImageBlockKeys.images: MultiImageData(images: []).toJson(), + MultiImageBlockKeys.images: + MultiImageData(images: images ?? []).toJson(), MultiImageBlockKeys.layout: MultiImageLayout.browser.toIntValue(), }, ); @@ -81,6 +82,7 @@ class MultiImageBlockComponent extends BlockComponentStatefulWidget { this.menuBuilder, super.configuration = const BlockComponentConfiguration(), super.actionBuilder, + super.actionTrailingBuilder, }); final bool showMenu; @@ -189,6 +191,7 @@ class MultiImageBlockComponentState extends State child = BlockComponentActionWrapper( node: node, actionBuilder: widget.actionBuilder!, + actionTrailingBuilder: widget.actionTrailingBuilder, child: child, ); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_menu.dart index 8abeaf9e99..dc95054e81 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_menu.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_menu.dart @@ -1,8 +1,5 @@ import 'dart:io'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/application/document_bloc.dart'; @@ -15,6 +12,7 @@ import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/util/string_extension.dart'; import 'package:appflowy/workspace/application/settings/application_data_storage.dart'; import 'package:appflowy/workspace/presentation/home/toast.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy/workspace/presentation/widgets/image_viewer/image_provider.dart'; import 'package:appflowy/workspace/presentation/widgets/image_viewer/interactive_image_viewer.dart'; import 'package:appflowy_backend/log.dart'; @@ -25,6 +23,8 @@ import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/uuid.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:http/http.dart'; import 'package:path/path.dart' as p; import 'package:provider/provider.dart'; @@ -103,7 +103,7 @@ class _MultiImageMenuState extends State { BoxShadow( blurRadius: 5, spreadRadius: 1, - color: Colors.black.withOpacity(0.1), + color: Colors.black.withValues(alpha: 0.1), ), ], borderRadius: BorderRadius.circular(4.0), @@ -217,9 +217,8 @@ class _MultiImageMenuState extends State { Clipboard.setData( ClipboardData(text: images[widget.indexNotifier.value].url), ); - showSnackBarMessage( - context, - LocaleKeys.document_plugins_image_copiedToPasteBoard.tr(), + showToastNotification( + message: LocaleKeys.document_plugins_image_copiedToPasteBoard.tr(), ); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/resizeable_image.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/resizeable_image.dart index 607e1d4d06..da37945bf5 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/resizeable_image.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/resizeable_image.dart @@ -2,17 +2,26 @@ import 'dart:io'; import 'dart:math'; import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/application/prelude.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/image/common.dart'; import 'package:appflowy/shared/appflowy_network_image.dart'; +import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:string_validator/string_validator.dart'; +enum ResizableImageState { + loading, + loaded, + failed, +} + class ResizableImage extends StatefulWidget { const ResizableImage({ super.key, @@ -24,6 +33,7 @@ class ResizableImage extends StatefulWidget { required this.src, this.height, this.onDoubleTap, + this.onStateChange, }); final String src; @@ -33,6 +43,7 @@ class ResizableImage extends StatefulWidget { final Alignment alignment; final bool editable; final VoidCallback? onDoubleTap; + final ValueChanged? onStateChange; final void Function(double width) onResize; @@ -59,8 +70,11 @@ class _ResizableImageState extends State { @override void initState() { super.initState(); + imageWidth = widget.width; - _userProfilePB = context.read()?.state.userProfilePB; + + _userProfilePB = context.read()?.userProfile ?? + context.read().state.userProfilePB; } @override @@ -86,20 +100,39 @@ class _ResizableImageState extends State { Widget child; final src = widget.src; if (isURL(src)) { - // load network image - if (widget.type == CustomImageType.internal && _userProfilePB == null) { - return _buildLoading(context); - } - _cacheImage = FlowyNetworkImage( url: widget.src, width: imageWidth - moveDistance, userProfilePB: _userProfilePB, - progressIndicatorBuilder: (context, _, __) => _buildLoading(context), - errorWidgetBuilder: (_, __, error) => _ImageLoadFailedWidget( - width: imageWidth, - error: error, - ), + onImageLoaded: (isImageInCache) { + if (isImageInCache) { + widget.onStateChange?.call(ResizableImageState.loaded); + } + }, + progressIndicatorBuilder: (context, _, progress) { + if (progress.totalSize != null) { + if (progress.progress == 1) { + widget.onStateChange?.call(ResizableImageState.loaded); + } else { + widget.onStateChange?.call(ResizableImageState.loading); + } + } + + return _buildLoading(context); + }, + errorWidgetBuilder: (_, __, error) { + widget.onStateChange?.call(ResizableImageState.failed); + return _ImageLoadFailedWidget( + width: imageWidth, + error: error, + onRetry: () { + setState(() { + final retryCounter = FlowyNetworkRetryCounter(); + retryCounter.clear(tag: src, url: src); + }); + }, + ); + }, ); child = _cacheImage!; @@ -193,7 +226,7 @@ class _ResizableImageState extends State { child: Container( height: 40, decoration: BoxDecoration( - color: Colors.black.withOpacity(0.5), + color: Colors.black.withValues(alpha: 0.5), borderRadius: const BorderRadius.all( Radius.circular(5.0), ), @@ -209,40 +242,53 @@ class _ResizableImageState extends State { } class _ImageLoadFailedWidget extends StatelessWidget { - const _ImageLoadFailedWidget({required this.width, required this.error}); + const _ImageLoadFailedWidget({ + required this.width, + required this.error, + required this.onRetry, + }); final double width; final Object error; + final VoidCallback onRetry; @override Widget build(BuildContext context) { final error = _getErrorMessage(); return Container( - height: 140, + height: 160, width: width, alignment: Alignment.center, - padding: const EdgeInsets.only(top: 8.0, bottom: 8.0), + padding: const EdgeInsets.symmetric(vertical: 8.0), decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(4.0)), - border: Border.all(color: Colors.grey.withOpacity(0.6)), + border: Border.all(color: Colors.grey.withValues(alpha: 0.6)), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ const FlowySvg( FlowySvgs.broken_image_xl, - size: Size.square(48), + size: Size.square(36), ), - FlowyText(AppFlowyEditorL10n.current.imageLoadFailed), - const VSpace(6), + FlowyText( + AppFlowyEditorL10n.current.imageLoadFailed, + fontSize: 14, + ), + const VSpace(4), if (error != null) FlowyText( error, textAlign: TextAlign.center, - color: Theme.of(context).hintColor.withOpacity(0.6), + color: Theme.of(context).hintColor.withValues(alpha: 0.6), fontSize: 10, maxLines: 2, ), + const VSpace(12), + OutlinedRoundedButton( + text: LocaleKeys.chat_retry.tr(), + onTap: onRetry, + ), ], ), ); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/embed_image_url_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/embed_image_url_widget.dart index caddbf464a..28dccad72d 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/embed_image_url_widget.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/upload_image_menu/widgets/embed_image_url_widget.dart @@ -57,7 +57,8 @@ class _EmbedImageUrlWidgetState extends State { width: 300, child: FlowyButton( backgroundColor: Theme.of(context).colorScheme.primary, - hoverColor: Theme.of(context).colorScheme.primary.withOpacity(0.9), + hoverColor: + Theme.of(context).colorScheme.primary.withValues(alpha: 0.9), showDefaultBoxDecorationOnMobile: true, radius: UniversalPlatform.isMobile ? BorderRadius.circular(8) : null, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/inline_math_equation/inline_math_equation_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/inline_math_equation/inline_math_equation_toolbar_item.dart index 061a6fe320..cd3779cb6c 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/inline_math_equation/inline_math_equation_toolbar_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/inline_math_equation/inline_math_equation_toolbar_item.dart @@ -9,7 +9,7 @@ const _kInlineMathEquationToolbarItemId = 'editor.inline_math_equation'; final ToolbarItem inlineMathEquationItem = ToolbarItem( id: _kInlineMathEquationToolbarItemId, - group: 2, + group: 4, isActive: onlyShowInSingleSelectionAndTextType, builder: (context, editorState, highlightColor, _, tooltipBuilder) { final selection = editorState.selection!; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/keyboard_interceptor/keyboard_interceptor.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/keyboard_interceptor/keyboard_interceptor.dart index 1d0bb5e49f..040989243f 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/keyboard_interceptor/keyboard_interceptor.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/keyboard_interceptor/keyboard_interceptor.dart @@ -1,9 +1,43 @@ +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_shortcuts/simple_table_command_extension.dart'; +import 'package:appflowy/shared/patterns/common_patterns.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; +import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:universal_platform/universal_platform.dart'; class EditorKeyboardInterceptor extends AppFlowyKeyboardServiceInterceptor { + @override + Future interceptInsert( + TextEditingDeltaInsertion insertion, + EditorState editorState, + List characterShortcutEvents, + ) async { + // Only check on the mobile platform: check if the inserted text is a link, if so, try to paste it as a link preview + final text = insertion.textInserted; + if (UniversalPlatform.isMobile && hrefRegex.hasMatch(text)) { + final result = customPasteCommand.execute(editorState); + return result == KeyEventResult.handled; + } + return false; + } + + @override + Future interceptReplace( + TextEditingDeltaReplacement replacement, + EditorState editorState, + List characterShortcutEvents, + ) async { + // Only check on the mobile platform: check if the replaced text is a link, if so, try to paste it as a link preview + final text = replacement.replacementText; + if (UniversalPlatform.isMobile && hrefRegex.hasMatch(text)) { + final result = customPasteCommand.execute(editorState); + return result == KeyEventResult.handled; + } + return false; + } + @override Future interceptNonTextUpdate( TextEditingDeltaNonTextUpdate nonTextUpdate, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_embed/link_embed_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_embed/link_embed_block_component.dart new file mode 100644 index 0000000000..baf9702a36 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_embed/link_embed_block_component.dart @@ -0,0 +1,310 @@ +import 'package:appflowy/core/helpers/url_launcher.dart'; +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/custom_link_parser.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/default_selectable_mixin.dart'; +import 'package:appflowy/shared/appflowy_network_image.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'link_embed_menu.dart'; + +class LinkEmbedKeys { + const LinkEmbedKeys._(); + static const String previewType = 'preview_type'; + static const String embed = 'embed'; + static const String align = 'align'; +} + +Node linkEmbedNode({required String url}) => Node( + type: LinkPreviewBlockKeys.type, + attributes: { + LinkPreviewBlockKeys.url: url, + LinkEmbedKeys.previewType: LinkEmbedKeys.embed, + }, + ); + +class LinkEmbedBlockComponent extends BlockComponentStatefulWidget { + const LinkEmbedBlockComponent({ + super.key, + super.showActions, + super.actionBuilder, + super.configuration = const BlockComponentConfiguration(), + required super.node, + }); + + @override + DefaultSelectableMixinState createState() => + LinkEmbedBlockComponentState(); +} + +class LinkEmbedBlockComponentState + extends DefaultSelectableMixinState + with BlockComponentConfigurable { + @override + BlockComponentConfiguration get configuration => widget.configuration; + + @override + Node get node => widget.node; + + String get url => widget.node.attributes[LinkPreviewBlockKeys.url] ?? ''; + + LinkLoadingStatus status = LinkLoadingStatus.loading; + final parser = LinkParser(); + late LinkInfo linkInfo = LinkInfo(url: url); + + final showActionsNotifier = ValueNotifier(false); + bool isMenuShowing = false, isHovering = false; + + @override + void initState() { + super.initState(); + parser.addLinkInfoListener((v) { + final hasNewInfo = !v.isEmpty(), hasOldInfo = !linkInfo.isEmpty(); + if (mounted) { + setState(() { + if (hasNewInfo) { + linkInfo = v; + status = LinkLoadingStatus.idle; + } else if (!hasOldInfo) { + status = LinkLoadingStatus.error; + } + }); + } + }); + parser.start(url); + } + + @override + void dispose() { + parser.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + Widget result = MouseRegion( + onEnter: (_) { + isHovering = true; + showActionsNotifier.value = true; + }, + onExit: (_) { + isHovering = false; + Future.delayed(const Duration(milliseconds: 200), () { + if (isMenuShowing || isHovering) return; + if (mounted) showActionsNotifier.value = false; + }); + }, + child: buildChild(context), + ); + final parent = node.parent; + EdgeInsets newPadding = padding; + if (parent?.type == CalloutBlockKeys.type) { + newPadding = padding.copyWith(right: padding.right + 10); + } + + result = Padding(padding: newPadding, child: result); + + if (widget.showActions && widget.actionBuilder != null) { + result = BlockComponentActionWrapper( + node: node, + actionBuilder: widget.actionBuilder!, + child: result, + ); + } + return result; + } + + Widget buildChild(BuildContext context) { + final theme = AppFlowyTheme.of(context), + fillSceme = theme.fillColorScheme, + borderScheme = theme.borderColorScheme; + Widget child; + final isIdle = status == LinkLoadingStatus.idle; + if (isIdle) { + child = buildContent(context); + } else { + child = buildErrorLoadingWidget(context); + } + return Container( + height: 450, + key: widgetKey, + decoration: BoxDecoration( + color: isIdle ? Theme.of(context).cardColor : fillSceme.tertiaryHover, + borderRadius: BorderRadius.all(Radius.circular(16)), + border: Border.all(color: borderScheme.greyTertiary), + ), + child: Stack( + children: [ + child, + buildMenu(context), + ], + ), + ); + } + + Widget buildMenu(BuildContext context) { + return Positioned( + top: 12, + right: 12, + child: ValueListenableBuilder( + valueListenable: showActionsNotifier, + builder: (context, showActions, child) { + if (!showActions) return SizedBox.shrink(); + return LinkEmbedMenu( + editorState: context.read(), + node: node, + onReload: () { + setState(() { + status = LinkLoadingStatus.loading; + }); + Future.delayed(const Duration(milliseconds: 200), () { + if (mounted) parser.start(url); + }); + }, + onMenuShowed: () { + isMenuShowing = true; + }, + onMenuHided: () { + isMenuShowing = false; + if (!isHovering && mounted) { + showActionsNotifier.value = false; + } + }, + ); + }, + ), + ); + } + + Widget buildContent(BuildContext context) { + final theme = AppFlowyTheme.of(context), textScheme = theme.textColorScheme; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: ClipRRect( + borderRadius: const BorderRadius.vertical(top: Radius.circular(16)), + child: FlowyNetworkImage( + url: linkInfo.imageUrl ?? '', + width: MediaQuery.of(context).size.width, + ), + ), + ), + MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () => + afLaunchUrlString(url, addingHttpSchemeWhenFailed: true), + child: Container( + height: 64, + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 20), + child: Row( + children: [ + SizedBox.square( + dimension: 40, + child: Center( + child: linkInfo.buildIconWidget(size: Size.square(32)), + ), + ), + HSpace(12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + FlowyText( + linkInfo.siteName ?? '', + color: textScheme.primary, + fontSize: 14, + figmaLineHeight: 20, + fontWeight: FontWeight.w600, + overflow: TextOverflow.ellipsis, + ), + VSpace(4), + FlowyText.regular( + url, + color: textScheme.secondary, + fontSize: 12, + figmaLineHeight: 16, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ], + ), + ), + ), + ), + ], + ); + } + + Widget buildErrorLoadingWidget(BuildContext context) { + final theme = AppFlowyTheme.of(context), textSceme = theme.textColorScheme; + final isLoading = status == LinkLoadingStatus.loading; + return isLoading + ? Center( + child: SizedBox.square( + dimension: 64, + child: CircularProgressIndicator.adaptive(), + ), + ) + : Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SvgPicture.asset( + FlowySvgs.embed_error_xl.path, + ), + VSpace(4), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: RichText( + maxLines: 1, + overflow: TextOverflow.ellipsis, + text: TextSpan( + children: [ + TextSpan( + text: '$url ', + style: TextStyle( + color: textSceme.primary, + fontSize: 14, + height: 20 / 14, + fontWeight: FontWeight.w700, + ), + ), + TextSpan( + text: LocaleKeys + .document_plugins_linkPreview_linkPreviewMenu_unableToDisplay + .tr(), + style: TextStyle( + color: textSceme.primary, + fontSize: 14, + height: 20 / 14, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ), + ), + ], + ), + ); + } + + @override + Node get currentNode => node; + + @override + EdgeInsets get boxPadding => padding; +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_embed/link_embed_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_embed/link_embed_menu.dart new file mode 100644 index 0000000000..c3d2aebbcc --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_embed/link_embed_menu.dart @@ -0,0 +1,354 @@ +import 'package:appflowy/core/helpers/url_launcher.dart'; +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_hover_menu.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_replace_menu.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/shared.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/menu/menu_extension.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/widgets.dart'; + +import 'link_embed_block_component.dart'; + +class LinkEmbedMenu extends StatefulWidget { + const LinkEmbedMenu({ + super.key, + required this.node, + required this.editorState, + required this.onMenuShowed, + required this.onMenuHided, + required this.onReload, + }); + + final Node node; + final EditorState editorState; + final VoidCallback onMenuShowed; + final VoidCallback onMenuHided; + final VoidCallback onReload; + + @override + State createState() => _LinkEmbedMenuState(); +} + +class _LinkEmbedMenuState extends State { + final turnintoController = PopoverController(); + final moreOptionController = PopoverController(); + int turnintoMenuNum = 0, moreOptionNum = 0, alignMenuNum = 0; + final moreOptionButtonKey = GlobalKey(); + bool get isTurnIntoShowing => turnintoMenuNum > 0; + bool get isMoreOptionShowing => moreOptionNum > 0; + bool get isAlignMenuShowing => alignMenuNum > 0; + + Node get node => widget.node; + EditorState get editorState => widget.editorState; + + String get url => node.attributes[LinkPreviewBlockKeys.url] ?? ''; + + @override + void dispose() { + super.dispose(); + turnintoController.close(); + moreOptionController.close(); + widget.onMenuHided.call(); + } + + @override + Widget build(BuildContext context) { + return buildChild(); + } + + Widget buildChild() { + final theme = AppFlowyTheme.of(context), + iconScheme = theme.iconColorScheme, + fillScheme = theme.fillColorScheme; + + return Container( + padding: EdgeInsets.all(4), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: fillScheme.primaryAlpha80, + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + // FlowyIconButton( + // icon: FlowySvg( + // FlowySvgs.embed_fullscreen_m, + // color: iconScheme.tertiary, + // ), + // tooltipText: LocaleKeys.document_imageBlock_openFullScreen.tr(), + // preferBelow: false, + // onPressed: () {}, + // ), + FlowyIconButton( + icon: FlowySvg( + FlowySvgs.toolbar_link_m, + color: iconScheme.tertiary, + ), + radius: BorderRadius.all(Radius.circular(theme.borderRadius.m)), + tooltipText: LocaleKeys.editor_copyLink.tr(), + preferBelow: false, + onPressed: () => copyLink(context), + ), + buildconvertBotton(), + buildMoreOptionBotton(), + ], + ), + ); + } + + Widget buildconvertBotton() { + final theme = AppFlowyTheme.of(context), iconScheme = theme.iconColorScheme; + return AppFlowyPopover( + offset: Offset(0, 6), + direction: PopoverDirection.bottomWithRightAligned, + margin: EdgeInsets.zero, + controller: turnintoController, + onOpen: () { + keepEditorFocusNotifier.increase(); + turnintoMenuNum++; + }, + onClose: () { + keepEditorFocusNotifier.decrease(); + turnintoMenuNum--; + checkToHideMenu(); + }, + popupBuilder: (context) => buildConvertMenu(), + child: FlowyIconButton( + icon: FlowySvg( + FlowySvgs.turninto_m, + color: iconScheme.tertiary, + ), + radius: BorderRadius.all(Radius.circular(theme.borderRadius.m)), + tooltipText: LocaleKeys.editor_convertTo.tr(), + preferBelow: false, + onPressed: showTurnIntoMenu, + ), + ); + } + + Widget buildConvertMenu() { + final types = LinkEmbedConvertCommand.values; + return Padding( + padding: const EdgeInsets.all(8.0), + child: SeparatedColumn( + mainAxisSize: MainAxisSize.min, + separatorBuilder: () => const VSpace(0.0), + children: List.generate(types.length, (index) { + final command = types[index]; + return SizedBox( + height: 36, + child: FlowyButton( + text: FlowyText( + command.title, + fontWeight: FontWeight.w400, + figmaLineHeight: 20, + ), + onTap: () { + if (command == LinkEmbedConvertCommand.toBookmark) { + final transaction = editorState.transaction; + transaction.updateNode(node, { + LinkPreviewBlockKeys.url: url, + LinkEmbedKeys.previewType: '', + }); + editorState.apply(transaction); + } else if (command == LinkEmbedConvertCommand.toMention) { + convertUrlPreviewNodeToMention(editorState, node); + } else if (command == LinkEmbedConvertCommand.toURL) { + convertUrlPreviewNodeToLink(editorState, node); + } + }, + ), + ); + }), + ), + ); + } + + Widget buildMoreOptionBotton() { + final theme = AppFlowyTheme.of(context), iconScheme = theme.iconColorScheme; + return AppFlowyPopover( + offset: Offset(0, 6), + direction: PopoverDirection.bottomWithRightAligned, + margin: EdgeInsets.zero, + controller: moreOptionController, + onOpen: () { + keepEditorFocusNotifier.increase(); + moreOptionNum++; + }, + onClose: () { + keepEditorFocusNotifier.decrease(); + moreOptionNum--; + checkToHideMenu(); + }, + popupBuilder: (context) => buildMoreOptionMenu(), + child: FlowyIconButton( + key: moreOptionButtonKey, + icon: FlowySvg( + FlowySvgs.toolbar_more_m, + color: iconScheme.tertiary, + ), + radius: BorderRadius.all(Radius.circular(theme.borderRadius.m)), + tooltipText: LocaleKeys.document_toolbar_moreOptions.tr(), + preferBelow: false, + onPressed: showMoreOptionMenu, + ), + ); + } + + Widget buildMoreOptionMenu() { + final types = LinkEmbedMenuCommand.values; + return Padding( + padding: const EdgeInsets.all(8.0), + child: SeparatedColumn( + mainAxisSize: MainAxisSize.min, + separatorBuilder: () => const VSpace(0.0), + children: List.generate(types.length, (index) { + final command = types[index]; + return SizedBox( + height: 36, + child: FlowyButton( + text: FlowyText( + command.title, + fontWeight: FontWeight.w400, + figmaLineHeight: 20, + ), + onTap: () => onEmbedMenuCommand(command), + ), + ); + }), + ), + ); + } + + void showTurnIntoMenu() { + keepEditorFocusNotifier.increase(); + turnintoController.show(); + checkToShowMenu(); + turnintoMenuNum++; + if (isMoreOptionShowing) closeMoreOptionMenu(); + } + + void closeTurnIntoMenu() { + turnintoController.close(); + checkToHideMenu(); + } + + void showMoreOptionMenu() { + keepEditorFocusNotifier.increase(); + moreOptionController.show(); + checkToShowMenu(); + moreOptionNum++; + if (isTurnIntoShowing) closeTurnIntoMenu(); + } + + void closeMoreOptionMenu() { + moreOptionController.close(); + checkToHideMenu(); + } + + void checkToHideMenu() { + Future.delayed(Duration(milliseconds: 200), () { + if (!mounted) return; + if (!isAlignMenuShowing && !isMoreOptionShowing && !isTurnIntoShowing) { + widget.onMenuHided.call(); + } + }); + } + + void checkToShowMenu() { + if (!isAlignMenuShowing && !isMoreOptionShowing && !isTurnIntoShowing) { + widget.onMenuShowed.call(); + } + } + + Future copyLink(BuildContext context) async { + await context.copyLink(url); + widget.onMenuHided.call(); + } + + void onEmbedMenuCommand(LinkEmbedMenuCommand command) { + switch (command) { + case LinkEmbedMenuCommand.openLink: + afLaunchUrlString(url, addingHttpSchemeWhenFailed: true); + break; + case LinkEmbedMenuCommand.replace: + final box = moreOptionButtonKey.currentContext?.findRenderObject() + as RenderBox?; + if (box == null) return; + final p = box.localToGlobal(Offset.zero); + showReplaceMenu( + context: context, + editorState: editorState, + node: node, + url: url, + ltrb: LTRB(left: p.dx - 330, top: p.dy), + onReplace: (url) async { + await convertLinkBlockToOtherLinkBlock( + editorState, + node, + node.type, + url: url, + ); + }, + ); + break; + case LinkEmbedMenuCommand.reload: + widget.onReload.call(); + break; + case LinkEmbedMenuCommand.removeLink: + removeUrlPreviewLink(editorState, node); + break; + } + closeMoreOptionMenu(); + } +} + +enum LinkEmbedMenuCommand { + openLink, + replace, + reload, + removeLink; + + String get title { + switch (this) { + case openLink: + return LocaleKeys.editor_openLink.tr(); + case replace: + return LocaleKeys.document_plugins_linkPreview_linkPreviewMenu_replace + .tr(); + case reload: + return LocaleKeys.document_plugins_linkPreview_linkPreviewMenu_reload + .tr(); + case removeLink: + return LocaleKeys + .document_plugins_linkPreview_linkPreviewMenu_removeLink + .tr(); + } + } +} + +enum LinkEmbedConvertCommand { + toMention, + toURL, + toBookmark; + + String get title { + switch (this) { + case toMention: + return LocaleKeys.document_plugins_linkPreview_linkPreviewMenu_toMetion + .tr(); + case toURL: + return LocaleKeys.document_plugins_linkPreview_linkPreviewMenu_toUrl + .tr(); + case toBookmark: + return LocaleKeys + .document_plugins_linkPreview_linkPreviewMenu_toBookmark + .tr(); + } + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/custom_link_parser.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/custom_link_parser.dart new file mode 100644 index 0000000000..1907f68d29 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/custom_link_parser.dart @@ -0,0 +1,151 @@ +import 'dart:convert'; +import 'package:appflowy/core/config/kv.dart'; +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/shared/appflowy_network_image.dart'; +import 'package:appflowy/shared/appflowy_network_svg.dart'; +import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:flutter/material.dart'; +import 'link_parsers/default_parser.dart'; +import 'link_parsers/youtube_parser.dart'; + +class LinkParser { + final Set> _listeners = >{}; + static final Map _hostToParsers = { + 'www.youtube.com': YoutubeParser(), + 'youtube.com': YoutubeParser(), + 'youtu.be': YoutubeParser(), + }; + + Future start(String url, {LinkInfoParser? parser}) async { + final uri = Uri.tryParse(LinkInfoParser.formatUrl(url)) ?? Uri.parse(url); + final data = await LinkInfoCache.get(uri); + if (data != null) { + refreshLinkInfo(data); + } + + final host = uri.host; + final currentParser = parser ?? _hostToParsers[host] ?? DefaultParser(); + await _getLinkInfo(uri, currentParser); + } + + Future _getLinkInfo(Uri uri, LinkInfoParser parser) async { + try { + final linkInfo = await parser.parse(uri) ?? LinkInfo(url: '$uri'); + if (!linkInfo.isEmpty()) await LinkInfoCache.set(uri, linkInfo); + refreshLinkInfo(linkInfo); + return linkInfo; + } catch (e, s) { + Log.error('get link info error: ', e, s); + refreshLinkInfo(LinkInfo(url: '$uri')); + return null; + } + } + + void refreshLinkInfo(LinkInfo info) { + for (final listener in _listeners) { + listener(info); + } + } + + void addLinkInfoListener(ValueChanged listener) { + _listeners.add(listener); + } + + void dispose() { + _listeners.clear(); + } +} + +class LinkInfo { + factory LinkInfo.fromJson(Map json) => LinkInfo( + siteName: json['siteName'], + url: json['url'] ?? '', + title: json['title'], + description: json['description'], + imageUrl: json['imageUrl'], + faviconUrl: json['faviconUrl'], + ); + + LinkInfo({ + required this.url, + this.siteName, + this.title, + this.description, + this.imageUrl, + this.faviconUrl, + }); + + final String url; + final String? siteName; + final String? title; + final String? description; + final String? imageUrl; + final String? faviconUrl; + + Map toJson() => { + 'url': url, + 'siteName': siteName, + 'title': title, + 'description': description, + 'imageUrl': imageUrl, + 'faviconUrl': faviconUrl, + }; + + @override + String toString() { + return 'LinkInfo{url: $url, siteName: $siteName, title: $title, description: $description, imageUrl: $imageUrl, faviconUrl: $faviconUrl}'; + } + + bool isEmpty() { + return title == null; + } + + Widget buildIconWidget({Size size = const Size.square(20.0)}) { + final iconUrl = faviconUrl; + if (iconUrl == null) { + return FlowySvg(FlowySvgs.toolbar_link_earth_m, size: size); + } + if (iconUrl.endsWith('.svg')) { + return FlowyNetworkSvg( + iconUrl, + height: size.height, + width: size.width, + errorWidget: const FlowySvg(FlowySvgs.toolbar_link_earth_m), + ); + } + return FlowyNetworkImage( + url: iconUrl, + fit: BoxFit.contain, + height: size.height, + width: size.width, + errorWidgetBuilder: (context, error, stackTrace) => + const FlowySvg(FlowySvgs.toolbar_link_earth_m), + ); + } +} + +class LinkInfoCache { + static const _linkInfoPrefix = 'link_info'; + + static Future get(Uri uri) async { + final option = await getIt().getWithFormat( + '$_linkInfoPrefix$uri', + (value) => LinkInfo.fromJson(jsonDecode(value)), + ); + return option; + } + + static Future set(Uri uri, LinkInfo data) async { + await getIt().set( + '$_linkInfoPrefix$uri', + jsonEncode(data.toJson()), + ); + } +} + +enum LinkLoadingStatus { + loading, + idle, + error, +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/custom_link_preview.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/custom_link_preview.dart index 879a71f008..9be73fcc0b 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/custom_link_preview.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/custom_link_preview.dart @@ -3,8 +3,10 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/widgets/widgets.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/mobile_block_action_buttons.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/shared.dart'; import 'package:appflowy/shared/appflowy_network_image.dart'; +import 'package:appflowy/util/theme_extension.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; @@ -12,6 +14,9 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; import 'package:universal_platform/universal_platform.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; + +import 'custom_link_parser.dart'; class CustomLinkPreviewWidget extends StatelessWidget { const CustomLinkPreviewWidget({ @@ -21,6 +26,8 @@ class CustomLinkPreviewWidget extends StatelessWidget { this.title, this.description, this.imageUrl, + this.isHovering = false, + this.status = LinkLoadingStatus.loading, }); final Node node; @@ -28,9 +35,14 @@ class CustomLinkPreviewWidget extends StatelessWidget { final String? description; final String? imageUrl; final String url; + final bool isHovering; + final LinkLoadingStatus status; @override Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context), + borderScheme = theme.borderColorScheme, + textScheme = theme.textColorScheme; final documentFontSize = context .read() .editorStyle @@ -38,73 +50,67 @@ class CustomLinkPreviewWidget extends StatelessWidget { .text .fontSize ?? 16.0; + final isInDarkCallout = node.parent?.type == CalloutBlockKeys.type && + !Theme.of(context).isLightMode; final (fontSize, width) = UniversalPlatform.isDesktopOrWeb - ? (documentFontSize, 180.0) + ? (documentFontSize, 160.0) : (documentFontSize - 2, 120.0); final Widget child = Container( clipBehavior: Clip.hardEdge, decoration: BoxDecoration( border: Border.all( - color: Theme.of(context).colorScheme.onSurface, - ), - borderRadius: BorderRadius.circular( - 6.0, + color: isHovering || isInDarkCallout + ? borderScheme.greyTertiaryHover + : borderScheme.greyTertiary, ), + borderRadius: BorderRadius.circular(16.0), ), - child: IntrinsicHeight( + child: SizedBox( + height: 96, child: Row( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - if (imageUrl != null) - ClipRRect( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(6.0), - bottomLeft: Radius.circular(6.0), - ), - child: FlowyNetworkImage( - url: imageUrl!, - width: width, - ), - ), + buildImage(context), Expanded( child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if (title != null) - Padding( - padding: const EdgeInsets.only( - bottom: 4.0, - right: 10.0, - ), - child: FlowyText.medium( - title!, - maxLines: 2, - overflow: TextOverflow.ellipsis, - fontSize: fontSize, - ), + padding: const EdgeInsets.fromLTRB(20, 12, 58, 12), + child: status != LinkLoadingStatus.idle + ? buildLoadingOrErrorWidget() + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (title != null) + Padding( + padding: const EdgeInsets.only(bottom: 4.0), + child: FlowyText.medium( + title!, + overflow: TextOverflow.ellipsis, + fontSize: fontSize, + color: textScheme.primary, + figmaLineHeight: 20, + ), + ), + if (description != null) + Padding( + padding: const EdgeInsets.only(bottom: 16.0), + child: FlowyText( + description!, + overflow: TextOverflow.ellipsis, + fontSize: fontSize - 4, + figmaLineHeight: 16, + color: textScheme.primary, + ), + ), + FlowyText( + url.toString(), + overflow: TextOverflow.ellipsis, + color: textScheme.secondary, + fontSize: fontSize - 4, + figmaLineHeight: 16, + ), + ], ), - if (description != null) - Padding( - padding: const EdgeInsets.only(bottom: 4.0), - child: FlowyText( - description!, - maxLines: 2, - overflow: TextOverflow.ellipsis, - fontSize: fontSize - 4, - ), - ), - FlowyText( - url.toString(), - overflow: TextOverflow.ellipsis, - maxLines: 2, - color: Theme.of(context).hintColor, - fontSize: fontSize - 4, - ), - ], - ), ), ), ], @@ -113,9 +119,12 @@ class CustomLinkPreviewWidget extends StatelessWidget { ); if (UniversalPlatform.isDesktopOrWeb) { - return InkWell( - onTap: () => afLaunchUrlString(url), - child: child, + return MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () => afLaunchUrlString(url), + child: child, + ), ); } @@ -150,4 +159,59 @@ class CustomLinkPreviewWidget extends StatelessWidget { ), ]; } + + Widget buildImage(BuildContext context) { + if (imageUrl?.isEmpty ?? true) { + return SizedBox.shrink(); + } + final theme = AppFlowyTheme.of(context), + fillScheme = theme.fillColorScheme, + iconScheme = theme.iconColorScheme; + final width = UniversalPlatform.isDesktopOrWeb ? 160.0 : 120.0; + return ClipRRect( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(16.0), + bottomLeft: Radius.circular(16.0), + ), + child: Container( + width: width, + color: fillScheme.quaternary, + child: FlowyNetworkImage( + url: imageUrl!, + width: width, + errorWidgetBuilder: (_, __, ___) => Center( + child: FlowySvg( + FlowySvgs.toolbar_link_earth_m, + color: iconScheme.secondary, + size: Size.square(30), + ), + ), + ), + ), + ); + } + + Widget buildLoadingOrErrorWidget() { + if (status == LinkLoadingStatus.loading) { + return const Center( + child: SizedBox( + height: 16, + width: 16, + child: CircularProgressIndicator.adaptive(), + ), + ); + } else if (status == LinkLoadingStatus.error) { + return const Center( + child: SizedBox( + height: 16, + width: 16, + child: Icon( + Icons.error_outline, + color: Colors.red, + ), + ), + ); + } + return SizedBox.shrink(); + } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/custom_link_preview_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/custom_link_preview_block_component.dart new file mode 100644 index 0000000000..3f2128db52 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/custom_link_preview_block_component.dart @@ -0,0 +1,194 @@ +import 'package:appflowy/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/link_embed/link_embed_block_component.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/custom_link_parser.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; +import 'package:flutter/material.dart'; + +import 'custom_link_preview.dart'; +import 'default_selectable_mixin.dart'; +import 'link_preview_menu.dart'; + +class CustomLinkPreviewBlockComponentBuilder extends BlockComponentBuilder { + CustomLinkPreviewBlockComponentBuilder({ + super.configuration, + }); + + @override + BlockComponentWidget build(BlockComponentContext blockComponentContext) { + final node = blockComponentContext.node; + final isEmbed = + node.attributes[LinkEmbedKeys.previewType] == LinkEmbedKeys.embed; + if (isEmbed) { + return LinkEmbedBlockComponent( + key: node.key, + node: node, + configuration: configuration, + showActions: showActions(node), + actionBuilder: (_, state) => + actionBuilder(blockComponentContext, state), + ); + } + return CustomLinkPreviewBlockComponent( + key: node.key, + node: node, + configuration: configuration, + showActions: showActions(node), + actionBuilder: (_, state) => actionBuilder(blockComponentContext, state), + ); + } + + @override + BlockComponentValidate get validate => + (node) => node.attributes[LinkPreviewBlockKeys.url]!.isNotEmpty; +} + +class CustomLinkPreviewBlockComponent extends BlockComponentStatefulWidget { + const CustomLinkPreviewBlockComponent({ + super.key, + required super.node, + super.showActions, + super.actionBuilder, + super.configuration = const BlockComponentConfiguration(), + }); + + @override + DefaultSelectableMixinState createState() => + CustomLinkPreviewBlockComponentState(); +} + +class CustomLinkPreviewBlockComponentState + extends DefaultSelectableMixinState + with BlockComponentConfigurable { + @override + BlockComponentConfiguration get configuration => widget.configuration; + + @override + Node get node => widget.node; + + String get url => widget.node.attributes[LinkPreviewBlockKeys.url]!; + + final parser = LinkParser(); + LinkLoadingStatus status = LinkLoadingStatus.loading; + late LinkInfo linkInfo = LinkInfo(url: url); + + final showActionsNotifier = ValueNotifier(false); + bool isMenuShowing = false, isHovering = false; + + @override + void initState() { + super.initState(); + parser.addLinkInfoListener((v) { + final hasNewInfo = !v.isEmpty(), hasOldInfo = !linkInfo.isEmpty(); + if (mounted) { + setState(() { + if (hasNewInfo) { + linkInfo = v; + status = LinkLoadingStatus.idle; + } else if (!hasOldInfo) { + status = LinkLoadingStatus.error; + } + }); + } + }); + parser.start(url); + } + + @override + void dispose() { + parser.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return MouseRegion( + onEnter: (_) { + isHovering = true; + showActionsNotifier.value = true; + }, + onExit: (_) { + isHovering = false; + Future.delayed(const Duration(milliseconds: 200), () { + if (isMenuShowing || isHovering) return; + if (mounted) showActionsNotifier.value = false; + }); + }, + hitTestBehavior: HitTestBehavior.opaque, + opaque: false, + child: ValueListenableBuilder( + valueListenable: showActionsNotifier, + builder: (context, showActions, child) { + return buildPreview(showActions); + }, + ), + ); + } + + Widget buildPreview(bool showActions) { + Widget child = CustomLinkPreviewWidget( + key: widgetKey, + node: node, + url: url, + isHovering: showActions, + title: linkInfo.siteName, + description: linkInfo.description, + imageUrl: linkInfo.imageUrl, + status: status, + ); + + if (widget.showActions && widget.actionBuilder != null) { + child = BlockComponentActionWrapper( + node: node, + actionBuilder: widget.actionBuilder!, + child: child, + ); + } + + child = Stack( + children: [ + child, + if (showActions) + Positioned( + top: 12, + right: 12, + child: CustomLinkPreviewMenu( + onMenuShowed: () { + isMenuShowing = true; + }, + onMenuHided: () { + isMenuShowing = false; + if (!isHovering && mounted) { + showActionsNotifier.value = false; + } + }, + onReload: () { + setState(() { + status = LinkLoadingStatus.loading; + }); + Future.delayed(const Duration(milliseconds: 200), () { + if (mounted) parser.start(url); + }); + }, + node: node, + ), + ), + ], + ); + + final parent = node.parent; + EdgeInsets newPadding = padding; + if (parent?.type == CalloutBlockKeys.type) { + newPadding = padding.copyWith(right: padding.right + 10); + } + child = Padding(padding: newPadding, child: child); + + return child; + } + + @override + Node get currentNode => node; + + @override + EdgeInsets get boxPadding => padding; +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/default_selectable_mixin.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/default_selectable_mixin.dart new file mode 100644 index 0000000000..c894811522 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/default_selectable_mixin.dart @@ -0,0 +1,77 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter/widgets.dart'; + +abstract class DefaultSelectableMixinState + extends State with SelectableMixin { + final widgetKey = GlobalKey(); + RenderBox? get _renderBox => + widgetKey.currentContext?.findRenderObject() as RenderBox?; + + Node get currentNode; + + EdgeInsets get boxPadding => EdgeInsets.zero; + + @override + Position start() => Position(path: currentNode.path); + + @override + Position end() => Position(path: currentNode.path, offset: 1); + + @override + Position getPositionInOffset(Offset start) => end(); + + @override + bool get shouldCursorBlink => false; + + @override + CursorStyle get cursorStyle => CursorStyle.cover; + + @override + Rect getBlockRect({ + bool shiftWithBaseOffset = false, + }) { + final box = _renderBox; + if (box is RenderBox) { + return boxPadding.topLeft & box.size; + } + return Rect.zero; + } + + @override + Rect? getCursorRectInPosition( + Position position, { + bool shiftWithBaseOffset = false, + }) { + final rects = getRectsInSelection(Selection.collapsed(position)); + return rects.firstOrNull; + } + + @override + List getRectsInSelection( + Selection selection, { + bool shiftWithBaseOffset = false, + }) { + if (_renderBox == null) { + return []; + } + final parentBox = context.findRenderObject(); + final box = widgetKey.currentContext?.findRenderObject(); + if (parentBox is RenderBox && box is RenderBox) { + return [ + box.localToGlobal(Offset.zero, ancestor: parentBox) & box.size, + ]; + } + return [Offset.zero & _renderBox!.size]; + } + + @override + Selection getSelectionInRange(Offset start, Offset end) => Selection.single( + path: currentNode.path, + startOffset: 0, + endOffset: 1, + ); + + @override + Offset localToGlobal(Offset offset, {bool shiftWithBaseOffset = false}) => + _renderBox!.localToGlobal(offset); +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/link_parsers/default_parser.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/link_parsers/default_parser.dart new file mode 100644 index 0000000000..7b52994654 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/link_parsers/default_parser.dart @@ -0,0 +1,95 @@ +import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/custom_link_parser.dart'; +import 'package:appflowy_backend/log.dart'; +// ignore: depend_on_referenced_packages +import 'package:html/parser.dart' as html_parser; +import 'package:http/http.dart' as http; +import 'dart:convert'; + +abstract class LinkInfoParser { + Future parse( + Uri link, { + Duration timeout = const Duration(seconds: 8), + Map? headers, + }); + + static String formatUrl(String url) { + Uri? uri = Uri.tryParse(url); + if (uri == null) return url; + if (!uri.hasScheme) uri = Uri.tryParse('http://$url'); + if (uri == null) return url; + final isHome = (uri.hasEmptyPath || uri.path == '/') && !uri.hasQuery; + final homeUrl = '${uri.scheme}://${uri.host}/'; + if (isHome) return homeUrl; + return '$uri'; + } +} + +class DefaultParser implements LinkInfoParser { + @override + Future parse( + Uri link, { + Duration timeout = const Duration(seconds: 8), + Map? headers, + }) async { + try { + final isHome = (link.hasEmptyPath || link.path == '/') && !link.hasQuery; + final http.Response response = + await http.get(link, headers: headers).timeout(timeout); + final code = response.statusCode; + if (code != 200 && isHome) { + throw Exception('Http request error: $code'); + } + + final contentType = response.headers['content-type']; + final charset = contentType?.split('charset=').lastOrNull; + String body = ''; + if (charset == null || + charset.toLowerCase() == 'latin-1' || + charset.toLowerCase() == 'iso-8859-1') { + body = latin1.decode(response.bodyBytes); + } else { + body = utf8.decode(response.bodyBytes, allowMalformed: true); + } + + final document = html_parser.parse(body); + + final siteName = document + .querySelector('meta[property="og:site_name"]') + ?.attributes['content']; + + String? title = document + .querySelector('meta[property="og:title"]') + ?.attributes['content']; + title ??= document.querySelector('title')?.text; + + String? description = document + .querySelector('meta[property="og:description"]') + ?.attributes['content']; + description ??= document + .querySelector('meta[name="description"]') + ?.attributes['content']; + + String? imageUrl = document + .querySelector('meta[property="og:image"]') + ?.attributes['content']; + if (imageUrl != null && !imageUrl.startsWith('http')) { + imageUrl = link.resolve(imageUrl).toString(); + } + + final favicon = + 'https://www.faviconextractor.com/favicon/${link.host}?larger=true'; + + return LinkInfo( + url: '$link', + siteName: siteName, + title: title, + description: description, + imageUrl: imageUrl, + faviconUrl: favicon, + ); + } catch (e) { + Log.error('Parse link $link error: $e'); + return null; + } + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/link_parsers/youtube_parser.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/link_parsers/youtube_parser.dart new file mode 100644 index 0000000000..6f1ac6fb22 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/link_parsers/youtube_parser.dart @@ -0,0 +1,86 @@ +import 'dart:convert'; + +import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/custom_link_parser.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:http/http.dart' as http; +import 'default_parser.dart'; + +class YoutubeParser implements LinkInfoParser { + @override + Future parse( + Uri link, { + Duration timeout = const Duration(seconds: 8), + Map? headers, + }) async { + try { + final isHome = (link.hasEmptyPath || link.path == '/') && !link.hasQuery; + if (isHome) { + return DefaultParser().parse( + link, + timeout: timeout, + headers: headers, + ); + } + + final requestLink = + 'https://www.youtube.com/oembed?url=$link&format=json'; + final http.Response response = await http + .get(Uri.parse(requestLink), headers: headers) + .timeout(timeout); + final code = response.statusCode; + if (code != 200) { + throw Exception('Http request error: $code'); + } + + final youtubeInfo = YoutubeInfo.fromJson(jsonDecode(response.body)); + + final favicon = + 'https://www.google.com/s2/favicons?sz=64&domain=${link.host}'; + return LinkInfo( + url: '$link', + title: youtubeInfo.title, + siteName: youtubeInfo.authorName, + imageUrl: youtubeInfo.thumbnailUrl, + faviconUrl: favicon, + ); + } catch (e) { + Log.error('Parse link $link error: $e'); + return null; + } + } +} + +class YoutubeInfo { + YoutubeInfo({ + this.title, + this.authorName, + this.version, + this.providerName, + this.providerUrl, + this.thumbnailUrl, + }); + + YoutubeInfo.fromJson(Map json) { + title = json['title']; + authorName = json['author_name']; + version = json['version']; + providerName = json['provider_name']; + providerUrl = json['provider_url']; + thumbnailUrl = json['thumbnail_url']; + } + String? title; + String? authorName; + String? version; + String? providerName; + String? providerUrl; + String? thumbnailUrl; + + Map toJson() => { + 'title': title, + 'author_name': authorName, + 'version': version, + 'provider_name': providerName, + 'provider_url': providerUrl, + 'thumbnail_url': thumbnailUrl, + }; +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/link_preview_cache.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/link_preview_cache.dart deleted file mode 100644 index 6688cfe304..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/link_preview_cache.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'dart:convert'; - -import 'package:appflowy/core/config/kv.dart'; -import 'package:appflowy/startup/startup.dart'; -import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; - -class LinkPreviewDataCache implements LinkPreviewDataCacheInterface { - @override - Future get(String url) async { - final option = - await getIt().getWithFormat( - url, - (value) => LinkPreviewData.fromJson(jsonDecode(value)), - ); - return option; - } - - @override - Future set(String url, LinkPreviewData data) async { - await getIt().set( - url, - jsonEncode(data.toJson()), - ); - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/link_preview_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/link_preview_menu.dart index 61e5156060..2fb493dda3 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/link_preview_menu.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/link_preview_menu.dart @@ -1,110 +1,207 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/block_menu/block_menu_button.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_hover_menu.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_replace_menu.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/link_embed/link_embed_block_component.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/shared.dart'; -import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/menu/menu_extension.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; -import '../image/custom_image_block_component/custom_image_block_component.dart'; - -class LinkPreviewMenu extends StatefulWidget { - const LinkPreviewMenu({ +class CustomLinkPreviewMenu extends StatefulWidget { + const CustomLinkPreviewMenu({ super.key, + required this.onMenuShowed, + required this.onMenuHided, + required this.onReload, required this.node, - required this.state, }); - + final VoidCallback onMenuShowed; + final VoidCallback onMenuHided; + final VoidCallback onReload; final Node node; - final LinkPreviewBlockComponentState state; @override - State createState() => _LinkPreviewMenuState(); + State createState() => _CustomLinkPreviewMenuState(); } -class _LinkPreviewMenuState extends State { +class _CustomLinkPreviewMenuState extends State { + final popoverController = PopoverController(); + final buttonKey = GlobalKey(); + bool closed = false; + bool selected = false; + + @override + void dispose() { + super.dispose(); + popoverController.close(); + widget.onMenuHided.call(); + } + @override Widget build(BuildContext context) { - final theme = Theme.of(context); - return Container( - height: 32, - decoration: BoxDecoration( - color: theme.cardColor, - boxShadow: [ - BoxShadow( - blurRadius: 5, - spreadRadius: 1, - color: Colors.black.withOpacity(0.1), - ), - ], - borderRadius: BorderRadius.circular(4.0), - ), - child: Row( - children: [ - const HSpace(4), - MenuBlockButton( - tooltip: LocaleKeys.document_plugins_urlPreview_convertToLink.tr(), - iconData: FlowySvgs.m_toolbar_link_m, - onTap: () async => convertUrlPreviewNodeToLink( - context.read(), - widget.node, - ), - ), - const HSpace(4), - MenuBlockButton( - tooltip: LocaleKeys.editor_copyLink.tr(), - iconData: FlowySvgs.copy_s, - onTap: copyImageLink, - ), - const _Divider(), - MenuBlockButton( - tooltip: LocaleKeys.button_delete.tr(), - iconData: FlowySvgs.trash_s, - onTap: deleteLinkPreviewNode, - ), - const HSpace(4), - ], + return AppFlowyPopover( + offset: Offset(0, 0.0), + direction: PopoverDirection.bottomWithRightAligned, + margin: EdgeInsets.zero, + controller: popoverController, + onOpen: () => keepEditorFocusNotifier.increase(), + onClose: () { + keepEditorFocusNotifier.decrease(); + if (!closed) { + closed = true; + return; + } else { + closed = false; + widget.onMenuHided.call(); + } + setState(() { + selected = false; + }); + }, + popupBuilder: (context) => buildMenu(), + child: FlowyIconButton( + key: buttonKey, + isSelected: selected, + icon: FlowySvg(FlowySvgs.toolbar_more_m), + onPressed: showPopover, ), ); } - void copyImageLink() { - final url = widget.node.attributes[CustomImageBlockKeys.url]; - if (url != null) { - Clipboard.setData(ClipboardData(text: url)); - showToastNotification( - context, - message: LocaleKeys.document_plugins_urlPreview_copiedToPasteBoard.tr(), - ); + Widget buildMenu() { + return MouseRegion( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: SeparatedColumn( + mainAxisSize: MainAxisSize.min, + separatorBuilder: () => const VSpace(0.0), + children: + List.generate(LinkPreviewMenuCommand.values.length, (index) { + final command = LinkPreviewMenuCommand.values[index]; + return SizedBox( + height: 36, + child: FlowyButton( + text: FlowyText( + command.title, + fontWeight: FontWeight.w400, + figmaLineHeight: 20, + ), + onTap: () => onTap(command), + ), + ); + }), + ), + ), + ); + } + + Future onTap(LinkPreviewMenuCommand command) async { + final editorState = context.read(); + final node = widget.node; + final url = node.attributes[LinkPreviewBlockKeys.url]; + switch (command) { + case LinkPreviewMenuCommand.convertToMention: + await convertUrlPreviewNodeToMention(editorState, node); + break; + case LinkPreviewMenuCommand.convertToUrl: + await convertUrlPreviewNodeToLink(editorState, node); + break; + case LinkPreviewMenuCommand.convertToEmbed: + final transaction = editorState.transaction; + transaction.updateNode(node, { + LinkPreviewBlockKeys.url: url, + LinkEmbedKeys.previewType: LinkEmbedKeys.embed, + }); + await editorState.apply(transaction); + break; + case LinkPreviewMenuCommand.copyLink: + if (url != null) { + await context.copyLink(url); + } + break; + case LinkPreviewMenuCommand.replace: + final box = buttonKey.currentContext?.findRenderObject() as RenderBox?; + if (box == null) return; + final p = box.localToGlobal(Offset.zero); + showReplaceMenu( + context: context, + editorState: editorState, + node: node, + url: url, + ltrb: LTRB(left: p.dx - 330, top: p.dy), + onReplace: (url) async { + await convertLinkBlockToOtherLinkBlock( + editorState, + node, + node.type, + url: url, + ); + }, + ); + break; + case LinkPreviewMenuCommand.reload: + widget.onReload.call(); + break; + case LinkPreviewMenuCommand.removeLink: + await removeUrlPreviewLink(editorState, node); + break; + } + closePopover(); + } + + void showPopover() { + widget.onMenuShowed.call(); + keepEditorFocusNotifier.increase(); + popoverController.show(); + setState(() { + selected = true; + }); + } + + void closePopover() { + popoverController.close(); + widget.onMenuHided.call(); + } +} + +enum LinkPreviewMenuCommand { + convertToMention, + convertToUrl, + convertToEmbed, + copyLink, + replace, + reload, + removeLink; + + String get title { + switch (this) { + case convertToMention: + return LocaleKeys.document_plugins_linkPreview_linkPreviewMenu_toMetion + .tr(); + case LinkPreviewMenuCommand.convertToUrl: + return LocaleKeys.document_plugins_linkPreview_linkPreviewMenu_toUrl + .tr(); + case LinkPreviewMenuCommand.convertToEmbed: + return LocaleKeys.document_plugins_linkPreview_linkPreviewMenu_toEmbed + .tr(); + case LinkPreviewMenuCommand.copyLink: + return LocaleKeys.document_plugins_linkPreview_linkPreviewMenu_copyLink + .tr(); + case LinkPreviewMenuCommand.replace: + return LocaleKeys.document_plugins_linkPreview_linkPreviewMenu_replace + .tr(); + case LinkPreviewMenuCommand.reload: + return LocaleKeys.document_plugins_linkPreview_linkPreviewMenu_reload + .tr(); + case LinkPreviewMenuCommand.removeLink: + return LocaleKeys + .document_plugins_linkPreview_linkPreviewMenu_removeLink + .tr(); } } - - Future deleteLinkPreviewNode() async { - final node = widget.node; - final editorState = context.read(); - final transaction = editorState.transaction; - transaction.deleteNode(node); - transaction.afterSelection = null; - await editorState.apply(transaction); - } -} - -class _Divider extends StatelessWidget { - const _Divider(); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(8), - child: Container( - width: 1, - color: Colors.grey, - ), - ); - } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/paste_as/paste_as_menu.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/paste_as/paste_as_menu.dart new file mode 100644 index 0000000000..fb51cdcf47 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/paste_as/paste_as_menu.dart @@ -0,0 +1,259 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/link_embed/link_embed_block_component.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/shared.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/menu/menu_extension.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/style_widget/button.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +const _menuHeighgt = 188.0, _menuWidth = 288.0; + +class PasteAsMenuService { + PasteAsMenuService({ + required this.context, + required this.editorState, + }); + + final BuildContext context; + final EditorState editorState; + OverlayEntry? _menuEntry; + + void show(String href) { + WidgetsBinding.instance.addPostFrameCallback((_) => _show(href)); + } + + void dismiss() { + if (_menuEntry != null) { + keepEditorFocusNotifier.decrease(); + // editorState.service.scrollService?.enable(); + // editorState.service.keyboardService?.enable(); + } + _menuEntry?.remove(); + _menuEntry = null; + } + + void _show(String href) { + final Size editorSize = editorState.renderBox?.size ?? Size.zero; + if (editorSize == Size.zero) return; + final menuPosition = editorState.calculateMenuOffset( + menuWidth: _menuWidth, + menuHeight: _menuHeighgt, + ); + if (menuPosition == null) return; + final ltrb = menuPosition.ltrb; + + _menuEntry = OverlayEntry( + builder: (context) => SizedBox( + height: editorSize.height, + width: editorSize.width, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: dismiss, + child: Stack( + children: [ + ltrb.buildPositioned( + child: PasteAsMenu( + editorState: editorState, + onSelect: (t) { + final selection = editorState.selection; + if (selection == null) return; + final end = selection.end; + final urlSelection = Selection( + start: end.copyWith(offset: end.offset - href.length), + end: end, + ); + if (t == PasteMenuType.bookmark) { + convertUrlToLinkPreview(editorState, urlSelection, href); + } else if (t == PasteMenuType.mention) { + convertUrlToMention(editorState, urlSelection); + } else if (t == PasteMenuType.embed) { + convertUrlToLinkPreview( + editorState, + urlSelection, + href, + previewType: LinkEmbedKeys.embed, + ); + } + dismiss(); + }, + onDismiss: dismiss, + ), + ), + ], + ), + ), + ), + ); + + Overlay.of(context).insert(_menuEntry!); + + keepEditorFocusNotifier.increase(); + // editorState.service.keyboardService?.disable(showCursor: true); + // editorState.service.scrollService?.disable(); + } +} + +class PasteAsMenu extends StatefulWidget { + const PasteAsMenu({ + super.key, + required this.onSelect, + required this.onDismiss, + required this.editorState, + }); + final ValueChanged onSelect; + final VoidCallback onDismiss; + final EditorState editorState; + + @override + State createState() => _PasteAsMenuState(); +} + +class _PasteAsMenuState extends State { + final focusNode = FocusNode(debugLabel: 'paste_as_menu'); + final ValueNotifier selectedIndexNotifier = ValueNotifier(0); + + EditorState get editorState => widget.editorState; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback( + (_) => focusNode.requestFocus(), + ); + editorState.selectionNotifier.addListener(dismiss); + } + + @override + void dispose() { + focusNode.dispose(); + selectedIndexNotifier.dispose(); + editorState.selectionNotifier.removeListener(dismiss); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); + return Focus( + focusNode: focusNode, + onKeyEvent: onKeyEvent, + child: Container( + width: _menuWidth, + height: _menuHeighgt, + padding: EdgeInsets.all(6), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + color: theme.surfaceColorScheme.primary, + boxShadow: theme.shadow.medium, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 32, + padding: EdgeInsets.all(8), + child: FlowyText.semibold( + color: theme.textColorScheme.primary, + LocaleKeys.document_plugins_linkPreview_typeSelection_pasteAs + .tr(), + ), + ), + ...List.generate( + PasteMenuType.values.length, + (i) => buildItem(PasteMenuType.values[i], i), + ), + ], + ), + ), + ); + } + + Widget buildItem(PasteMenuType type, int i) { + return ValueListenableBuilder( + valueListenable: selectedIndexNotifier, + builder: (context, value, child) { + final isSelected = i == value; + return SizedBox( + height: 36, + child: FlowyButton( + isSelected: isSelected, + text: FlowyText( + type.title, + ), + onTap: () => onSelect(type), + ), + ); + }, + ); + } + + void changeIndex(int index) => selectedIndexNotifier.value = index; + + KeyEventResult onKeyEvent(focus, KeyEvent event) { + if (event is! KeyDownEvent && event is! KeyRepeatEvent) { + return KeyEventResult.ignored; + } + + int index = selectedIndexNotifier.value, + length = PasteMenuType.values.length; + if (event.logicalKey == LogicalKeyboardKey.enter) { + onSelect(PasteMenuType.values[index]); + return KeyEventResult.handled; + } else if (event.logicalKey == LogicalKeyboardKey.escape) { + dismiss(); + } else if (event.logicalKey == LogicalKeyboardKey.backspace) { + dismiss(); + } else if ([LogicalKeyboardKey.arrowUp, LogicalKeyboardKey.arrowLeft] + .contains(event.logicalKey)) { + if (index == 0) { + index = length - 1; + } else { + index--; + } + changeIndex(index); + return KeyEventResult.handled; + } else if ([LogicalKeyboardKey.arrowDown, LogicalKeyboardKey.arrowRight] + .contains(event.logicalKey)) { + if (index == length - 1) { + index = 0; + } else { + index++; + } + changeIndex(index); + return KeyEventResult.handled; + } + return KeyEventResult.ignored; + } + + void onSelect(PasteMenuType type) => widget.onSelect.call(type); + + void dismiss() => widget.onDismiss.call(); +} + +enum PasteMenuType { + mention, + url, + bookmark, + embed, +} + +extension PasteMenuTypeExtension on PasteMenuType { + String get title { + switch (this) { + case PasteMenuType.mention: + return LocaleKeys.document_plugins_linkPreview_typeSelection_mention + .tr(); + case PasteMenuType.url: + return LocaleKeys.document_plugins_linkPreview_typeSelection_URL.tr(); + case PasteMenuType.bookmark: + return LocaleKeys.document_plugins_linkPreview_typeSelection_bookmark + .tr(); + case PasteMenuType.embed: + return LocaleKeys.document_plugins_linkPreview_typeSelection_embed.tr(); + } + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/shared.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/shared.dart index 57564c4722..8b193c70fb 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/shared.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/link_preview/shared.dart @@ -1,3 +1,5 @@ +import 'package:appflowy/plugins/document/presentation/editor_plugins/link_embed/link_embed_block_component.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; @@ -9,7 +11,7 @@ Future convertUrlPreviewNodeToLink( return; } - final url = node.attributes[ImageBlockKeys.url]; + final url = node.attributes[LinkPreviewBlockKeys.url]; final delta = Delta() ..insert( url, @@ -29,3 +31,172 @@ Future convertUrlPreviewNodeToLink( ); return editorState.apply(transaction); } + +Future convertUrlPreviewNodeToMention( + EditorState editorState, + Node node, +) async { + if (node.type != LinkPreviewBlockKeys.type) { + return; + } + + final url = node.attributes[LinkPreviewBlockKeys.url]; + final delta = Delta() + ..insert( + MentionBlockKeys.mentionChar, + attributes: { + MentionBlockKeys.mention: { + MentionBlockKeys.type: MentionType.externalLink.name, + MentionBlockKeys.url: url, + }, + }, + ); + final transaction = editorState.transaction; + transaction + ..insertNode(node.path, paragraphNode(delta: delta)) + ..deleteNode(node); + transaction.afterSelection = Selection.collapsed( + Position( + path: node.path, + offset: url.length, + ), + ); + return editorState.apply(transaction); +} + +Future removeUrlPreviewLink( + EditorState editorState, + Node node, +) async { + if (node.type != LinkPreviewBlockKeys.type) { + return; + } + + final url = node.attributes[LinkPreviewBlockKeys.url]; + final delta = Delta()..insert(url); + final transaction = editorState.transaction; + transaction + ..insertNode(node.path, paragraphNode(delta: delta)) + ..deleteNode(node); + transaction.afterSelection = Selection.collapsed( + Position( + path: node.path, + offset: url.length, + ), + ); + return editorState.apply(transaction); +} + +Future convertUrlToLinkPreview( + EditorState editorState, + Selection selection, + String url, { + String? previewType, +}) async { + final node = editorState.getNodeAtPath(selection.end.path); + if (node == null) { + return; + } + final delta = node.delta; + if (delta == null) return; + final List beforeOperations = [], afterOperations = []; + int index = 0; + for (final insert in delta.whereType()) { + if (index < selection.startIndex) { + beforeOperations.add(insert); + } else if (index >= selection.endIndex) { + afterOperations.add(insert); + } + index += insert.length; + } + final transaction = editorState.transaction; + transaction + ..deleteNode(node) + ..insertNodes(node.path.next, [ + if (beforeOperations.isNotEmpty) + paragraphNode(delta: Delta(operations: beforeOperations)), + if (previewType == LinkEmbedKeys.embed) + linkEmbedNode(url: url) + else + linkPreviewNode(url: url), + if (afterOperations.isNotEmpty) + paragraphNode(delta: Delta(operations: afterOperations)), + ]); + await editorState.apply(transaction); +} + +Future convertUrlToMention( + EditorState editorState, + Selection selection, +) async { + final node = editorState.getNodeAtPath(selection.end.path); + if (node == null) { + return; + } + final delta = node.delta; + if (delta == null) return; + String url = ''; + int index = 0; + for (final insert in delta.whereType()) { + if (index >= selection.startIndex && index < selection.endIndex) { + final href = insert.attributes?.href ?? ''; + if (href.isNotEmpty) { + url = href; + break; + } + } + index += insert.length; + } + final transaction = editorState.transaction; + transaction.replaceText( + node, + selection.startIndex, + selection.length, + MentionBlockKeys.mentionChar, + attributes: { + MentionBlockKeys.mention: { + MentionBlockKeys.type: MentionType.externalLink.name, + MentionBlockKeys.url: url, + }, + }, + ); + await editorState.apply(transaction); +} + +Future convertLinkBlockToOtherLinkBlock( + EditorState editorState, + Node node, + String toType, { + String? url, +}) async { + final nodeType = node.type; + if (nodeType != LinkPreviewBlockKeys.type || + (nodeType == toType && url == null)) { + return; + } + final insertedNode = []; + + final afterUrl = url ?? node.attributes[LinkPreviewBlockKeys.url] ?? ''; + final previewType = node.attributes[LinkEmbedKeys.previewType]; + Node afterNode = node.copyWith( + type: toType, + attributes: { + LinkPreviewBlockKeys.url: afterUrl, + LinkEmbedKeys.previewType: previewType, + blockComponentBackgroundColor: + node.attributes[blockComponentBackgroundColor], + blockComponentTextDirection: node.attributes[blockComponentTextDirection], + blockComponentDelta: (node.delta ?? Delta()).toJson(), + }, + ); + afterNode = afterNode.copyWith(children: []); + insertedNode.add(afterNode); + insertedNode.addAll(node.children.map((e) => e.deepCopy())); + final transaction = editorState.transaction; + transaction.insertNodes( + node.path, + insertedNode, + ); + transaction.deleteNodes([node]); + await editorState.apply(transaction); +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/math_equation_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/math_equation_block_component.dart index 286eac3d75..2f724061ee 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/math_equation_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/math_equation_block_component.dart @@ -79,6 +79,10 @@ class MathEquationBlockComponentBuilder extends BlockComponentBuilder { blockComponentContext, state, ), + actionTrailingBuilder: (context, state) => actionTrailingBuilder( + blockComponentContext, + state, + ), ); } @@ -94,6 +98,7 @@ class MathEquationBlockComponentWidget extends BlockComponentStatefulWidget { required super.node, super.showActions, super.actionBuilder, + super.actionTrailingBuilder, super.configuration = const BlockComponentConfiguration(), }); @@ -153,15 +158,11 @@ class MathEquationBlockComponentWidgetState ), ); - child = Padding( - padding: padding, - child: child, - ); - if (widget.showActions && widget.actionBuilder != null) { child = BlockComponentActionWrapper( node: node, actionBuilder: widget.actionBuilder!, + actionTrailingBuilder: widget.actionTrailingBuilder, child: child, ); } @@ -174,6 +175,11 @@ class MathEquationBlockComponentWidgetState ); } + child = Padding( + padding: padding, + child: child, + ); + if (UniversalPlatform.isDesktopOrWeb) { child = Stack( children: [ diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/math_equation_shortcut.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/math_equation_shortcut.dart new file mode 100644 index 0000000000..9c9fe7905b --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/math_equation_shortcut.dart @@ -0,0 +1,74 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; + +/// Windows / Linux : ctrl + shift + e +/// macOS : cmd + shift + e +/// Allows the user to insert math equation by shortcut +/// +/// - support +/// - desktop +/// - web +/// +final CommandShortcutEvent insertInlineMathEquationCommand = + CommandShortcutEvent( + key: 'Insert inline math equation', + command: 'ctrl+shift+e', + macOSCommand: 'cmd+shift+e', + getDescription: LocaleKeys.document_plugins_mathEquation_name.tr, + handler: (editorState) { + final selection = editorState.selection; + if (selection == null || selection.isCollapsed || !selection.isSingle) { + return KeyEventResult.ignored; + } + final node = editorState.getNodeAtPath(selection.start.path); + final delta = node?.delta; + if (node == null || delta == null) { + return KeyEventResult.ignored; + } + if (node.delta == null || !toolbarItemWhiteList.contains(node.type)) { + return KeyEventResult.ignored; + } + final transaction = editorState.transaction; + final nodes = editorState.getNodesInSelection(selection); + final isHighlight = nodes.allSatisfyInSelection(selection, (delta) { + return delta.everyAttributes( + (attributes) => attributes[InlineMathEquationKeys.formula] != null, + ); + }); + if (isHighlight) { + final formula = delta + .slice(selection.startIndex, selection.endIndex) + .whereType() + .firstOrNull + ?.attributes?[InlineMathEquationKeys.formula]; + assert(formula != null); + if (formula == null) { + return KeyEventResult.ignored; + } + // clear the format + transaction.replaceText( + node, + selection.startIndex, + selection.length, + formula, + attributes: {}, + ); + } else { + final text = editorState.getTextInSelection(selection).join(); + transaction.replaceText( + node, + selection.startIndex, + selection.length, + MentionBlockKeys.mentionChar, + attributes: { + InlineMathEquationKeys.formula: text, + }, + ); + } + editorState.apply(transaction); + return KeyEventResult.handled; + }, +); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/child_page_transaction_handler.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/child_page_transaction_handler.dart index ef32ad1098..77f8c8d0a1 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/child_page_transaction_handler.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/child_page_transaction_handler.dart @@ -100,7 +100,6 @@ class ChildPageTransactionHandler extends MentionTransactionHandler { Log.error(error); if (context.mounted) { showToastNotification( - context, message: LocaleKeys.document_plugins_subPage_errors_failedDeletePage .tr(), ); @@ -179,13 +178,6 @@ class ChildPageTransactionHandler extends MentionTransactionHandler { await duplicatedViewOrFailure.fold( (newView) async { - final newMentionAttributes = { - MentionBlockKeys.mention: { - MentionBlockKeys.type: MentionType.childPage.name, - MentionBlockKeys.pageId: newView.id, - }, - }; - // The index is the index of the delta, to get the index of the mention character // in all the text, we need to calculate it based on the deltas before the current delta. int mentionIndex = 0; @@ -202,7 +194,11 @@ class ChildPageTransactionHandler extends MentionTransactionHandler { node, mentionIndex, MentionBlockKeys.mentionChar.length, - newMentionAttributes, + MentionBlockKeys.buildMentionPageAttributes( + mentionType: MentionType.childPage, + pageId: newView.id, + blockId: null, + ), ); await editorState.apply( transaction, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/date_transaction_handler.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/date_transaction_handler.dart index 972ed229dd..cb3196e9b7 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/date_transaction_handler.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/date_transaction_handler.dart @@ -192,15 +192,12 @@ class DateTransactionHandler extends MentionTransactionHandler { ), ); - final newMentionAttributes = { - MentionBlockKeys.mention: { - MentionBlockKeys.type: MentionType.date.name, - MentionBlockKeys.date: dateTime.toIso8601String(), - MentionBlockKeys.reminderId: reminderId, - MentionBlockKeys.includeTime: data.includeTime, - MentionBlockKeys.reminderOption: data.reminderOption.name, - }, - }; + final newMentionAttributes = MentionBlockKeys.buildMentionDateAttributes( + date: dateTime.toIso8601String(), + reminderId: reminderId, + reminderOption: data.reminderOption.name, + includeTime: data.includeTime, + ); // The index is the index of the delta, to get the index of the mention character // in all the text, we need to calculate it based on the deltas before the current delta. diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_block.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_block.dart index a5bc340f71..0060d65bb7 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_block.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_block.dart @@ -6,14 +6,18 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'mention_link_block.dart'; + enum MentionType { page, date, + externalLink, childPage; static MentionType fromString(String value) => switch (value) { 'page' => page, 'date' => date, + 'externalLink' => externalLink, 'childPage' => childPage, // Backwards compatibility 'reminder' => date, @@ -27,12 +31,12 @@ Node dateMentionNode() { operations: [ TextInsert( MentionBlockKeys.mentionChar, - attributes: { - MentionBlockKeys.mention: { - MentionBlockKeys.type: MentionType.date.name, - MentionBlockKeys.date: DateTime.now().toIso8601String(), - }, - }, + attributes: MentionBlockKeys.buildMentionDateAttributes( + date: DateTime.now().toIso8601String(), + reminderId: null, + reminderOption: null, + includeTime: false, + ), ), ], ), @@ -42,18 +46,52 @@ Node dateMentionNode() { class MentionBlockKeys { const MentionBlockKeys._(); - static const reminderId = 'reminder_id'; // ReminderID static const mention = 'mention'; static const type = 'type'; // MentionType, String + static const pageId = 'page_id'; static const blockId = 'block_id'; + static const url = 'url'; // Related to Reminder and Date blocks static const date = 'date'; // Start Date static const includeTime = 'include_time'; + static const reminderId = 'reminder_id'; // ReminderID static const reminderOption = 'reminder_option'; static const mentionChar = '\$'; + + static Map buildMentionPageAttributes({ + required MentionType mentionType, + required String pageId, + required String? blockId, + }) { + return { + MentionBlockKeys.mention: { + MentionBlockKeys.type: mentionType.name, + MentionBlockKeys.pageId: pageId, + if (blockId != null) MentionBlockKeys.blockId: blockId, + }, + }; + } + + static Map buildMentionDateAttributes({ + required String date, + required String? reminderId, + required String? reminderOption, + required bool includeTime, + }) { + return { + MentionBlockKeys.mention: { + MentionBlockKeys.type: MentionType.date.name, + MentionBlockKeys.date: date, + MentionBlockKeys.includeTime: includeTime, + if (reminderId != null) MentionBlockKeys.reminderId: reminderId, + if (reminderOption != null) + MentionBlockKeys.reminderOption: reminderOption, + }, + }; + } } class MentionBlock extends StatelessWidget { @@ -124,8 +162,17 @@ class MentionBlock extends StatelessWidget { reminderOption: reminderOption ?? ReminderOption.none, includeTime: mention[MentionBlockKeys.includeTime] ?? false, ); - default: - return const SizedBox.shrink(); + case MentionType.externalLink: + final String? url = mention[MentionBlockKeys.url] as String?; + if (url == null) { + return const SizedBox.shrink(); + } + return MentionLinkBlock( + url: url, + editorState: editorState, + node: node, + index: index, + ); } } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_date_block.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_date_block.dart index e1df115e15..20f60be23d 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_date_block.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_date_block.dart @@ -60,8 +60,6 @@ class MentionDateBlock extends StatefulWidget { } class _MentionDateBlockState extends State { - final PopoverMutex mutex = PopoverMutex(); - late bool _includeTime = widget.includeTime; late DateTime? parsedDate = DateTime.tryParse(widget.date); @@ -71,12 +69,6 @@ class _MentionDateBlockState extends State { super.didUpdateWidget(oldWidget); } - @override - void dispose() { - mutex.dispose(); - super.dispose(); - } - @override Widget build(BuildContext context) { if (parsedDate == null) { @@ -105,7 +97,6 @@ class _MentionDateBlockState extends State { final options = DatePickerOptions( focusedDay: parsedDate, - popoverMutex: mutex, selectedDay: parsedDate, includeTime: _includeTime, dateFormat: appearance.dateFormat, @@ -210,16 +201,17 @@ class _MentionDateBlockState extends State { (reminderOption == ReminderOption.none ? null : widget.reminderId); final transaction = widget.editorState.transaction - ..formatText(widget.node, widget.index, 1, { - MentionBlockKeys.mention: { - MentionBlockKeys.type: MentionType.date.name, - MentionBlockKeys.date: date.toIso8601String(), - MentionBlockKeys.reminderId: rId, - MentionBlockKeys.includeTime: includeTime, - MentionBlockKeys.reminderOption: - reminderOption?.name ?? widget.reminderOption.name, - }, - }); + ..formatText( + widget.node, + widget.index, + 1, + MentionBlockKeys.buildMentionDateAttributes( + date: date.toIso8601String(), + reminderId: rId, + includeTime: includeTime, + reminderOption: reminderOption?.name ?? widget.reminderOption.name, + ), + ); widget.editorState.apply(transaction, withUpdateSelection: false); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_link_block.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_link_block.dart new file mode 100644 index 0000000000..06ebcb5002 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_link_block.dart @@ -0,0 +1,353 @@ +import 'dart:async'; +import 'dart:math'; +import 'package:appflowy/core/helpers/url_launcher.dart'; +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_hover_menu.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/link_embed/link_embed_block_component.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/custom_link_parser.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/paste_as/paste_as_menu.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/shared.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flowy_infra_ui/style_widget/hover.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import 'mention_link_error_preview.dart'; +import 'mention_link_preview.dart'; + +class MentionLinkBlock extends StatefulWidget { + const MentionLinkBlock({ + super.key, + required this.url, + required this.editorState, + required this.node, + required this.index, + this.delayToShow = const Duration(milliseconds: 50), + this.delayToHide = const Duration(milliseconds: 300), + }); + + final String url; + final Duration delayToShow; + final Duration delayToHide; + final EditorState editorState; + final Node node; + final int index; + + @override + State createState() => _MentionLinkBlockState(); +} + +class _MentionLinkBlockState extends State { + final parser = LinkParser(); + _LoadingStatus status = _LoadingStatus.loading; + late LinkInfo linkInfo = LinkInfo(url: url); + final previewController = PopoverController(); + bool isHovering = false; + int previewFocusNum = 0; + bool isPreviewHovering = false; + bool showAtBottom = false; + final key = GlobalKey(); + + bool get isPreviewShowing => previewFocusNum > 0; + String get url => widget.url; + + EditorState get editorState => widget.editorState; + + Node get node => widget.node; + + int get index => widget.index; + + bool get readyForPreview => + status == _LoadingStatus.idle && !linkInfo.isEmpty(); + + @override + void initState() { + super.initState(); + + parser.addLinkInfoListener((v) { + final hasNewInfo = !v.isEmpty(), hasOldInfo = !linkInfo.isEmpty(); + if (mounted) { + setState(() { + if (hasNewInfo) { + linkInfo = v; + status = _LoadingStatus.idle; + } else if (!hasOldInfo) { + status = _LoadingStatus.error; + } + }); + } + }); + parser.start(url); + } + + @override + void dispose() { + super.dispose(); + parser.dispose(); + previewController.close(); + } + + @override + Widget build(BuildContext context) { + return AppFlowyPopover( + key: ValueKey(showAtBottom), + controller: previewController, + direction: showAtBottom + ? PopoverDirection.bottomWithLeftAligned + : PopoverDirection.topWithLeftAligned, + offset: Offset(0, showAtBottom ? -20 : 20), + onOpen: () { + keepEditorFocusNotifier.increase(); + previewFocusNum++; + }, + onClose: () { + keepEditorFocusNotifier.decrease(); + previewFocusNum--; + }, + decorationColor: Colors.transparent, + popoverDecoration: BoxDecoration(), + margin: EdgeInsets.zero, + constraints: getConstraints(), + borderRadius: BorderRadius.circular(16), + popupBuilder: (context) => readyForPreview + ? MentionLinkPreview( + linkInfo: linkInfo, + showAtBottom: showAtBottom, + triggerSize: getSizeFromKey(), + onEnter: (e) { + isPreviewHovering = true; + }, + onExit: (e) { + isPreviewHovering = false; + tryToDismissPreview(); + }, + onCopyLink: () => copyLink(context), + onConvertTo: (s) => convertTo(s), + onRemoveLink: removeLink, + onOpenLink: openLink, + ) + : MentionLinkErrorPreview( + url: url, + triggerSize: getSizeFromKey(), + onEnter: (e) { + isPreviewHovering = true; + }, + onExit: (e) { + isPreviewHovering = false; + tryToDismissPreview(); + }, + onCopyLink: () => copyLink(context), + onConvertTo: (s) => convertTo(s), + onRemoveLink: removeLink, + onOpenLink: openLink, + ), + child: buildIconWithTitle(context), + ); + } + + Widget buildIconWithTitle(BuildContext context) { + final theme = AppFlowyTheme.of(context); + final siteName = linkInfo.siteName, linkTitle = linkInfo.title ?? url; + + return MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: onEnter, + onExit: onExit, + child: GestureDetector( + onTap: () async { + await afLaunchUrlString(url, addingHttpSchemeWhenFailed: true); + }, + child: FlowyHoverContainer( + style: + HoverStyle(hoverColor: Theme.of(context).colorScheme.secondary), + applyStyle: isHovering, + child: Row( + mainAxisSize: MainAxisSize.min, + key: key, + children: [ + HSpace(2), + buildIcon(), + HSpace(4), + Flexible( + child: RichText( + overflow: TextOverflow.ellipsis, + text: TextSpan( + children: [ + if (siteName != null) ...[ + TextSpan( + text: siteName, + style: theme.textStyle.body + .standard(color: theme.textColorScheme.secondary), + ), + WidgetSpan(child: HSpace(2)), + ], + TextSpan( + text: linkTitle, + style: theme.textStyle.body + .standard(color: theme.textColorScheme.primary), + ), + ], + ), + ), + ), + HSpace(2), + ], + ), + ), + ), + ); + } + + Widget buildIcon() { + const defaultWidget = FlowySvg(FlowySvgs.toolbar_link_earth_m); + Widget icon = defaultWidget; + if (status == _LoadingStatus.loading) { + icon = Padding( + padding: const EdgeInsets.all(2.0), + child: const CircularProgressIndicator(strokeWidth: 1), + ); + } else { + icon = linkInfo.buildIconWidget(); + } + return SizedBox( + height: 20, + width: 20, + child: icon, + ); + } + + RenderBox? get box => key.currentContext?.findRenderObject() as RenderBox?; + + Size getSizeFromKey() => box?.size ?? Size.zero; + + Future copyLink(BuildContext context) async { + await context.copyLink(url); + previewController.close(); + } + + Future openLink() async { + await afLaunchUrlString(url, addingHttpSchemeWhenFailed: true); + } + + Future removeLink() async { + final transaction = editorState.transaction + ..replaceText(widget.node, widget.index, 1, url, attributes: {}); + await editorState.apply(transaction); + } + + Future convertTo(PasteMenuType type) async { + if (type == PasteMenuType.url) { + await toUrl(); + } else if (type == PasteMenuType.bookmark) { + await toLinkPreview(); + } else if (type == PasteMenuType.embed) { + await toLinkPreview(previewType: LinkEmbedKeys.embed); + } + } + + Future toUrl() async { + final transaction = editorState.transaction + ..replaceText( + widget.node, + widget.index, + 1, + url, + attributes: { + AppFlowyRichTextKeys.href: url, + }, + ); + await editorState.apply(transaction); + } + + Future toLinkPreview({String? previewType}) async { + final selection = Selection( + start: Position(path: node.path, offset: index), + end: Position(path: node.path, offset: index + 1), + ); + await convertUrlToLinkPreview( + editorState, + selection, + url, + previewType: previewType, + ); + } + + void changeHovering(bool hovering) { + if (isHovering == hovering) return; + if (mounted) { + setState(() { + isHovering = hovering; + }); + } + } + + void changeShowAtBottom(bool bottom) { + if (showAtBottom == bottom) return; + if (mounted) { + setState(() { + showAtBottom = bottom; + }); + } + } + + void tryToDismissPreview() { + Future.delayed(widget.delayToHide, () { + if (isHovering || isPreviewHovering) { + return; + } + previewController.close(); + }); + } + + void onEnter(PointerEnterEvent e) { + changeHovering(true); + final location = box?.localToGlobal(Offset.zero) ?? Offset.zero; + if (readyForPreview) { + if (location.dy < 300) { + changeShowAtBottom(true); + } else { + changeShowAtBottom(false); + } + } + Future.delayed(widget.delayToShow, () { + if (isHovering && !isPreviewShowing && status != _LoadingStatus.loading) { + showPreview(); + } + }); + } + + void onExit(PointerExitEvent e) { + changeHovering(false); + tryToDismissPreview(); + } + + void showPreview() { + if (!mounted) return; + keepEditorFocusNotifier.increase(); + previewController.show(); + previewFocusNum++; + } + + BoxConstraints getConstraints() { + final size = getSizeFromKey(); + if (!readyForPreview) { + return BoxConstraints( + maxWidth: max(320, size.width), + maxHeight: 48 + size.height, + ); + } + final hasImage = linkInfo.imageUrl?.isNotEmpty ?? false; + return BoxConstraints( + maxWidth: max(300, size.width), + maxHeight: hasImage ? 300 : 180, + ); + } +} + +enum _LoadingStatus { + loading, + idle, + error, +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_link_error_preview.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_link_error_preview.dart new file mode 100644 index 0000000000..df396108e4 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_link_error_preview.dart @@ -0,0 +1,232 @@ +import 'dart:math'; + +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_create_menu.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/paste_as/paste_as_menu.dart'; +import 'package:appflowy/util/theme_extension.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class MentionLinkErrorPreview extends StatefulWidget { + const MentionLinkErrorPreview({ + super.key, + required this.url, + required this.onEnter, + required this.onExit, + required this.onCopyLink, + required this.onRemoveLink, + required this.onConvertTo, + required this.onOpenLink, + required this.triggerSize, + }); + + final String url; + final PointerEnterEventListener onEnter; + final PointerExitEventListener onExit; + final VoidCallback onCopyLink; + final VoidCallback onRemoveLink; + final VoidCallback onOpenLink; + final ValueChanged onConvertTo; + final Size triggerSize; + + @override + State createState() => + _MentionLinkErrorPreviewState(); +} + +class _MentionLinkErrorPreviewState extends State { + final menuController = PopoverController(); + bool isConvertButtonSelected = false; + + @override + void dispose() { + super.dispose(); + menuController.close(); + } + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + MouseRegion( + onEnter: widget.onEnter, + onExit: widget.onExit, + child: SizedBox( + width: max(320, widget.triggerSize.width), + height: 48, + child: Align( + alignment: Alignment.centerLeft, + child: Container( + width: 320, + height: 48, + decoration: buildToolbarLinkDecoration(context), + padding: EdgeInsets.fromLTRB(12, 8, 8, 8), + child: Row( + children: [ + Expanded(child: buildLinkWidget()), + Container( + height: 20, + width: 1, + color: Color(0xffE8ECF3) + .withAlpha(Theme.of(context).isLightMode ? 255 : 40), + margin: EdgeInsets.symmetric(horizontal: 6), + ), + FlowyIconButton( + icon: FlowySvg(FlowySvgs.toolbar_link_m), + tooltipText: LocaleKeys.editor_copyLink.tr(), + preferBelow: false, + width: 36, + height: 32, + onPressed: widget.onCopyLink, + ), + buildConvertButton(), + ], + ), + ), + ), + ), + ), + MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: widget.onEnter, + onExit: widget.onExit, + child: GestureDetector( + onTap: widget.onOpenLink, + child: Container( + width: widget.triggerSize.width, + height: widget.triggerSize.height, + color: Colors.black.withAlpha(1), + ), + ), + ), + ], + ); + } + + Widget buildLinkWidget() { + final url = widget.url; + return FlowyTooltip( + message: url, + preferBelow: false, + child: FlowyText.regular( + url, + overflow: TextOverflow.ellipsis, + figmaLineHeight: 20, + fontSize: 14, + ), + ); + } + + Widget buildConvertButton() { + return AppFlowyPopover( + offset: Offset(8, 10), + direction: PopoverDirection.bottomWithRightAligned, + margin: EdgeInsets.zero, + controller: menuController, + onOpen: () => keepEditorFocusNotifier.increase(), + onClose: () => keepEditorFocusNotifier.decrease(), + popupBuilder: (context) => buildConvertMenu(), + child: FlowyIconButton( + icon: FlowySvg(FlowySvgs.turninto_m), + isSelected: isConvertButtonSelected, + tooltipText: LocaleKeys.editor_convertTo.tr(), + preferBelow: false, + width: 36, + height: 32, + onPressed: () { + setState(() { + isConvertButtonSelected = true; + }); + showPopover(); + }, + ), + ); + } + + Widget buildConvertMenu() { + return MouseRegion( + onEnter: widget.onEnter, + onExit: widget.onExit, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: SeparatedColumn( + mainAxisSize: MainAxisSize.min, + separatorBuilder: () => const VSpace(0.0), + children: List.generate(MentionLinktErrorMenuCommand.values.length, + (index) { + final command = MentionLinktErrorMenuCommand.values[index]; + return SizedBox( + height: 36, + child: FlowyButton( + text: FlowyText( + command.title, + fontWeight: FontWeight.w400, + figmaLineHeight: 20, + ), + onTap: () => onTap(command), + ), + ); + }), + ), + ), + ); + } + + void showPopover() { + keepEditorFocusNotifier.increase(); + menuController.show(); + } + + void closePopover() { + menuController.close(); + } + + void onTap(MentionLinktErrorMenuCommand command) { + switch (command) { + case MentionLinktErrorMenuCommand.toURL: + widget.onConvertTo(PasteMenuType.url); + break; + case MentionLinktErrorMenuCommand.toBookmark: + widget.onConvertTo(PasteMenuType.bookmark); + break; + case MentionLinktErrorMenuCommand.toEmbed: + widget.onConvertTo(PasteMenuType.embed); + break; + case MentionLinktErrorMenuCommand.removeLink: + widget.onRemoveLink(); + break; + } + closePopover(); + } +} + +enum MentionLinktErrorMenuCommand { + toURL, + toBookmark, + toEmbed, + removeLink; + + String get title { + switch (this) { + case toURL: + return LocaleKeys.document_plugins_linkPreview_linkPreviewMenu_toUrl + .tr(); + case toBookmark: + return LocaleKeys + .document_plugins_linkPreview_linkPreviewMenu_toBookmark + .tr(); + case toEmbed: + return LocaleKeys.document_plugins_linkPreview_linkPreviewMenu_toEmbed + .tr(); + case removeLink: + return LocaleKeys + .document_plugins_linkPreview_linkPreviewMenu_removeLink + .tr(); + } + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_link_preview.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_link_preview.dart new file mode 100644 index 0000000000..00b161379e --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_link_preview.dart @@ -0,0 +1,276 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_create_menu.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/custom_link_parser.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/paste_as/paste_as_menu.dart'; +import 'package:appflowy/plugins/document/presentation/editor_style.dart'; +import 'package:appflowy/shared/appflowy_network_image.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class MentionLinkPreview extends StatefulWidget { + const MentionLinkPreview({ + super.key, + required this.linkInfo, + required this.onEnter, + required this.onExit, + required this.onCopyLink, + required this.onRemoveLink, + required this.onConvertTo, + required this.onOpenLink, + required this.triggerSize, + required this.showAtBottom, + }); + + final LinkInfo linkInfo; + final PointerEnterEventListener onEnter; + final PointerExitEventListener onExit; + final VoidCallback onCopyLink; + final VoidCallback onRemoveLink; + final VoidCallback onOpenLink; + final ValueChanged onConvertTo; + final Size triggerSize; + final bool showAtBottom; + + @override + State createState() => _MentionLinkPreviewState(); +} + +class _MentionLinkPreviewState extends State { + final menuController = PopoverController(); + bool isSelected = false; + + LinkInfo get linkInfo => widget.linkInfo; + + @override + void dispose() { + super.dispose(); + menuController.close(); + } + + @override + Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context), + textColorScheme = theme.textColorScheme; + final imageUrl = linkInfo.imageUrl ?? '', + description = linkInfo.description ?? ''; + final imageHeight = 120.0; + final card = MouseRegion( + onEnter: widget.onEnter, + onExit: widget.onExit, + child: Container( + decoration: buildToolbarLinkDecoration(context, radius: 16), + width: 280, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (imageUrl.isNotEmpty) + ClipRRect( + borderRadius: + const BorderRadius.vertical(top: Radius.circular(16)), + child: FlowyNetworkImage( + url: linkInfo.imageUrl ?? '', + width: 280, + height: imageHeight, + ), + ), + VSpace(12), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: FlowyText.semibold( + linkInfo.title ?? linkInfo.siteName ?? '', + fontSize: 14, + figmaLineHeight: 20, + color: textColorScheme.primary, + overflow: TextOverflow.ellipsis, + ), + ), + VSpace(4), + if (description.isNotEmpty) ...[ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: FlowyText( + description, + fontSize: 12, + figmaLineHeight: 16, + color: textColorScheme.secondary, + maxLines: 3, + overflow: TextOverflow.ellipsis, + ), + ), + VSpace(36), + ], + Container( + margin: const EdgeInsets.symmetric(horizontal: 16), + height: 28, + child: Row( + children: [ + linkInfo.buildIconWidget(size: Size.square(16)), + HSpace(6), + Expanded( + child: FlowyText( + linkInfo.siteName ?? linkInfo.url, + fontSize: 12, + figmaLineHeight: 16, + color: textColorScheme.primary, + overflow: TextOverflow.ellipsis, + fontWeight: FontWeight.w700, + ), + ), + buildMoreOptionButton(), + ], + ), + ), + VSpace(12), + ], + ), + ), + ); + + final clickPlaceHolder = MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: widget.onEnter, + onExit: widget.onExit, + child: GestureDetector( + child: Container( + height: 20, + width: widget.triggerSize.width, + color: Colors.white.withAlpha(1), + ), + onTap: () { + widget.onOpenLink.call(); + closePopover(); + }, + ), + ); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: widget.showAtBottom + ? [clickPlaceHolder, card] + : [card, clickPlaceHolder], + ); + } + + Widget buildMoreOptionButton() { + return AppFlowyPopover( + controller: menuController, + direction: PopoverDirection.topWithLeftAligned, + onOpen: () => keepEditorFocusNotifier.increase(), + onClose: () => keepEditorFocusNotifier.decrease(), + margin: EdgeInsets.zero, + borderRadius: BorderRadius.circular(12), + popupBuilder: (context) => buildConvertMenu(), + child: FlowyIconButton( + width: 28, + height: 28, + isSelected: isSelected, + hoverColor: EditorStyleCustomizer.toolbarHoverColor(context), + icon: FlowySvg( + FlowySvgs.toolbar_more_m, + size: Size.square(20), + ), + onPressed: () { + setState(() { + isSelected = true; + }); + showPopover(); + }, + ), + ); + } + + Widget buildConvertMenu() { + return MouseRegion( + onEnter: widget.onEnter, + onExit: widget.onExit, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: SeparatedColumn( + mainAxisSize: MainAxisSize.min, + separatorBuilder: () => const VSpace(0.0), + children: + List.generate(MentionLinktMenuCommand.values.length, (index) { + final command = MentionLinktMenuCommand.values[index]; + return SizedBox( + height: 36, + child: FlowyButton( + text: FlowyText( + command.title, + fontWeight: FontWeight.w400, + figmaLineHeight: 20, + ), + onTap: () => onTap(command), + ), + ); + }), + ), + ), + ); + } + + void showPopover() { + keepEditorFocusNotifier.increase(); + menuController.show(); + } + + void closePopover() { + menuController.close(); + } + + void onTap(MentionLinktMenuCommand command) { + switch (command) { + case MentionLinktMenuCommand.toURL: + widget.onConvertTo(PasteMenuType.url); + break; + case MentionLinktMenuCommand.toBookmark: + widget.onConvertTo(PasteMenuType.bookmark); + break; + case MentionLinktMenuCommand.toEmbed: + widget.onConvertTo(PasteMenuType.embed); + break; + case MentionLinktMenuCommand.copyLink: + widget.onCopyLink(); + break; + case MentionLinktMenuCommand.removeLink: + widget.onRemoveLink(); + break; + } + closePopover(); + } +} + +enum MentionLinktMenuCommand { + toURL, + toBookmark, + toEmbed, + copyLink, + removeLink; + + String get title { + switch (this) { + case toURL: + return LocaleKeys.document_plugins_linkPreview_linkPreviewMenu_toUrl + .tr(); + case toBookmark: + return LocaleKeys + .document_plugins_linkPreview_linkPreviewMenu_toBookmark + .tr(); + case toEmbed: + return LocaleKeys.document_plugins_linkPreview_linkPreviewMenu_toEmbed + .tr(); + case copyLink: + return LocaleKeys.document_plugins_linkPreview_linkPreviewMenu_copyLink + .tr(); + case removeLink: + return LocaleKeys + .document_plugins_linkPreview_linkPreviewMenu_removeLink + .tr(); + } + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart index 7ca8f6b140..ede690eb30 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart @@ -9,6 +9,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mo import 'package:appflowy/plugins/trash/application/trash_service.dart'; import 'package:appflowy/shared/clipboard_state.dart'; import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/util/string_extension.dart'; import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart'; @@ -49,18 +50,23 @@ Node pageMentionNode(String viewId) { operations: [ TextInsert( MentionBlockKeys.mentionChar, - attributes: { - MentionBlockKeys.mention: { - MentionBlockKeys.type: MentionType.page.name, - MentionBlockKeys.pageId: viewId, - }, - }, + attributes: MentionBlockKeys.buildMentionPageAttributes( + mentionType: MentionType.page, + pageId: viewId, + blockId: null, + ), ), ], ), ); } +class ReferenceState { + ReferenceState(this.isReference); + + final bool isReference; +} + class MentionPageBlock extends StatefulWidget { const MentionPageBlock({ super.key, @@ -111,7 +117,7 @@ class _MentionPageBlockState extends State { view: view, content: state.blockContent, textStyle: widget.textStyle, - handleTap: () => _handleTap( + handleTap: () => handleMentionBlockTap( context, widget.editorState, view, @@ -131,7 +137,7 @@ class _MentionPageBlockState extends State { content: state.blockContent, textStyle: widget.textStyle, showTrashHint: state.isInTrash, - handleTap: () => _handleTap( + handleTap: () => handleMentionBlockTap( context, widget.editorState, view, @@ -214,7 +220,8 @@ class _MentionSubPageBlockState extends State { view: view, showTrashHint: state.isInTrash, textStyle: widget.textStyle, - handleTap: () => _handleTap(context, widget.editorState, view), + handleTap: () => + handleMentionBlockTap(context, widget.editorState, view), isChildPage: true, content: '', handleDoubleTap: () => _handleDoubleTap( @@ -232,7 +239,8 @@ class _MentionSubPageBlockState extends State { content: null, textStyle: widget.textStyle, isChildPage: true, - handleTap: () => _handleTap(context, widget.editorState, view), + handleTap: () => + handleMentionBlockTap(context, widget.editorState, view), ); } }, @@ -275,12 +283,11 @@ class _MentionSubPageBlockState extends State { widget.node, widget.index, MentionBlockKeys.mentionChar.length, - { - MentionBlockKeys.mention: { - MentionBlockKeys.type: MentionType.page.name, - MentionBlockKeys.pageId: widget.pageId, - }, - }, + MentionBlockKeys.buildMentionPageAttributes( + mentionType: MentionType.page, + pageId: widget.pageId, + blockId: null, + ), ); widget.editorState.apply( @@ -314,7 +321,7 @@ Path? _findNodePathByBlockId(EditorState editorState, String blockId) { return null; } -Future _handleTap( +Future handleMentionBlockTap( BuildContext context, EditorState editorState, ViewPB view, { @@ -339,6 +346,11 @@ Future _handleTap( await context.pushView( view, blockId: blockId, + tabs: [ + PickerTabType.emoji, + PickerTabType.icon, + PickerTabType.custom, + ].map((e) => e.name).toList(), ); } } else { @@ -369,25 +381,24 @@ Future _handleDoubleTap( } final currentViewId = context.read().documentId; - final newViewId = await showPageSelectorSheet( + final newView = await showPageSelectorSheet( context, currentViewId: currentViewId, selectedViewId: viewId, ); - if (newViewId != null) { + if (newView != null) { // Update this nodes pageId final transaction = editorState.transaction ..formatText( node, index, 1, - { - MentionBlockKeys.mention: { - MentionBlockKeys.type: MentionType.page.name, - MentionBlockKeys.pageId: newViewId, - }, - }, + MentionBlockKeys.buildMentionPageAttributes( + mentionType: MentionType.page, + pageId: newView.id, + blockId: null, + ), ); await editorState.apply(transaction, withUpdateSelection: false); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/menu/menu_extension.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/menu/menu_extension.dart new file mode 100644 index 0000000000..7dcd21f423 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/menu/menu_extension.dart @@ -0,0 +1,116 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter/material.dart'; + +extension MenuExtension on EditorState { + MenuPosition? calculateMenuOffset({ + Rect? rect, + required double menuWidth, + required double menuHeight, + Offset menuOffset = const Offset(0, 10), + }) { + final selectionService = service.selectionService; + final selectionRects = selectionService.selectionRects; + late Rect startRect; + if (rect != null) { + startRect = rect; + } else { + if (selectionRects.isEmpty) return null; + startRect = selectionRects.first; + } + + final editorOffset = renderBox?.localToGlobal(Offset.zero) ?? Offset.zero; + final editorHeight = renderBox!.size.height; + final editorWidth = renderBox!.size.width; + + // show below default + Alignment alignment = Alignment.topLeft; + final bottomRight = startRect.bottomRight; + final topRight = startRect.topRight; + var startOffset = bottomRight + menuOffset; + Offset offset = Offset( + startOffset.dx, + startOffset.dy, + ); + + // show above + if (startOffset.dy + menuHeight >= editorOffset.dy + editorHeight) { + startOffset = topRight - menuOffset; + alignment = Alignment.bottomLeft; + + offset = Offset( + startOffset.dx, + editorHeight + editorOffset.dy - startOffset.dy, + ); + } + + // show on right + if (offset.dx + menuWidth < editorOffset.dx + editorWidth) { + offset = Offset( + offset.dx, + offset.dy, + ); + } else if (startOffset.dx - editorOffset.dx > menuWidth) { + // show on left + alignment = alignment == Alignment.topLeft + ? Alignment.topRight + : Alignment.bottomRight; + + offset = Offset( + editorWidth - offset.dx + editorOffset.dx, + offset.dy, + ); + } + return MenuPosition(align: alignment, offset: offset); + } +} + +class MenuPosition { + MenuPosition({ + required this.align, + required this.offset, + }); + + final Alignment align; + final Offset offset; + + LTRB get ltrb { + double? left, top, right, bottom; + switch (align) { + case Alignment.topLeft: + left = offset.dx; + top = offset.dy; + break; + case Alignment.bottomLeft: + left = offset.dx; + bottom = offset.dy; + break; + case Alignment.topRight: + right = offset.dx; + top = offset.dy; + break; + case Alignment.bottomRight: + right = offset.dx; + bottom = offset.dy; + break; + } + + return LTRB(left: left, top: top, right: right, bottom: bottom); + } +} + +class LTRB { + LTRB({this.left, this.top, this.right, this.bottom}); + + final double? left; + final double? top; + final double? right; + final double? bottom; + + Positioned buildPositioned({required Widget child}) => Positioned( + left: left, + top: top, + right: right, + bottom: bottom, + child: child, + ); +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/migration/editor_migration.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/migration/editor_migration.dart index 8463030667..41bb8ce873 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/migration/editor_migration.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/migration/editor_migration.dart @@ -2,11 +2,13 @@ import 'dart:convert'; import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; +import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy/workspace/application/view/view_service.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor/appflowy_editor.dart' + hide QuoteBlockComponentBuilder, quoteNode, QuoteBlockKeys; import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; import 'package:collection/collection.dart'; import 'package:string_validator/string_validator.dart'; @@ -52,7 +54,9 @@ class EditorMigration { node = pageNode(children: children); } } else if (id == 'callout') { - final emoji = nodeV0.attributes['emoji'] ?? '📌'; + final icon = nodeV0.attributes[CalloutBlockKeys.icon] ?? '📌'; + final iconType = nodeV0.attributes[CalloutBlockKeys.iconType] ?? + FlowyIconType.emoji.name; final delta = nodeV0.children.whereType().fold(Delta(), (p, e) { final delta = migrateDelta(e.delta); @@ -62,8 +66,18 @@ class EditorMigration { } return p..insert('\n'); }); + EmojiIconData? emojiIconData; + try { + emojiIconData = + EmojiIconData(FlowyIconType.values.byName(iconType), icon); + } catch (e) { + Log.error( + 'migrateNode get EmojiIconData error with :${nodeV0.attributes}', + e, + ); + } node = calloutNode( - emoji: emoji, + emoji: emojiIconData, delta: delta, ); } else if (id == 'divider') { @@ -226,6 +240,14 @@ class EditorMigration { }, }; } + } else { + extra = { + ViewExtKeys.coverKey: { + ViewExtKeys.coverTypeKey: + PageStyleCoverImageType.localImage.toString(), + ViewExtKeys.coverValueKey: coverDetails, + }, + }; } break; default: diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/aa_menu/_block_items.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/aa_menu/_block_items.dart index 57670afadd..8e1a8533e0 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/aa_menu/_block_items.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/aa_menu/_block_items.dart @@ -6,7 +6,8 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_too import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/aa_menu/_popup_menu.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/aa_menu/_toolbar_theme.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor/appflowy_editor.dart' + hide QuoteBlockKeys, quoteNode; import 'package:collection/collection.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/aa_menu/_color_list.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/aa_menu/_color_list.dart index d3d4c5daa9..6ec777429c 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/aa_menu/_color_list.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/aa_menu/_color_list.dart @@ -86,7 +86,7 @@ class _TextColorAndBackgroundColorState EditorTextColorWidget( selectedColor: selectedTextColor?.tryToColor(), onSelectedColor: (textColor) async { - final hex = textColor.alpha == 0 ? null : textColor.toHex(); + final hex = textColor.a == 0 ? null : textColor.toHex(); final selection = widget.selection; if (selection.isCollapsed) { widget.editorState.updateToggledStyle( @@ -123,8 +123,7 @@ class _TextColorAndBackgroundColorState EditorBackgroundColors( selectedColor: selectedBackgroundColor?.tryToColor(), onSelectedColor: (backgroundColor) async { - final hex = - backgroundColor.alpha == 0 ? null : backgroundColor.toHex(); + final hex = backgroundColor.a == 0 ? null : backgroundColor.toHex(); final selection = widget.selection; if (selection.isCollapsed) { widget.editorState.updateToggledStyle( @@ -296,7 +295,7 @@ class _TextColorItem extends StatelessWidget { child: FlowyText( 'A', fontSize: 24, - color: color.alpha == 0 ? null : color, + color: color.a == 0 ? null : color, ), ), ); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/add_attachment_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/add_attachment_item.dart index 8aa9ca7aac..e31d9f68a6 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/add_attachment_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/add_attachment_item.dart @@ -117,21 +117,20 @@ class _AddAttachmentMenu extends StatelessWidget { iconSize: const Size.square(20), onTap: () async => selectPhoto(context), ), - const Divider(height: 8.5, thickness: 0.5), + const MobileQuickActionDivider(), MobileQuickActionButton( text: LocaleKeys.document_attachmentMenu_takePicture.tr(), icon: FlowySvgs.camera_s, iconSize: const Size.square(20), onTap: () async => selectCamera(context), ), - const Divider(height: 8.5, thickness: 0.5), + const MobileQuickActionDivider(), MobileQuickActionButton( text: LocaleKeys.document_attachmentMenu_chooseFile.tr(), icon: FlowySvgs.file_s, iconSize: const Size.square(20), onTap: () async => selectFile(context), ), - const Divider(height: 8.5, thickness: 0.5), ], ), ); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/add_block_menu_item_builder.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/add_block_menu_item_builder.dart index 4ae243575c..b3df4dfd39 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/add_block_menu_item_builder.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/add_block_menu_item_builder.dart @@ -12,7 +12,8 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.da import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/tasks/app_widget.dart'; import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor/appflowy_editor.dart' + hide QuoteBlockComponentBuilder, quoteNode, QuoteBlockKeys; import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/appflowy_mobile_toolbar.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/appflowy_mobile_toolbar.dart index cd806f6c56..787ccfda9f 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/appflowy_mobile_toolbar.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/appflowy_mobile_toolbar.dart @@ -20,6 +20,7 @@ import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; abstract class AppFlowyMobileToolbarWidgetService { void closeItemMenu(); + void closeKeyboard(); PropertyValueNotifier get showMenuNotifier; @@ -179,7 +180,13 @@ class _MobileToolbarState extends State<_MobileToolbar> // but in this case, we don't want to update the cached keyboard height. // This is because we want to keep the same height when the menu is shown. bool canUpdateCachedKeyboardHeight = true; - ValueNotifier cachedKeyboardHeight = ValueNotifier(0.0); + + /// when the [_MobileToolbar] disposed before the keyboard height can be updated in time, + /// there will be an issue with the height being 0 + /// this is used to globally record the height. + static double _globalCachedKeyboardHeight = 0.0; + ValueNotifier cachedKeyboardHeight = + ValueNotifier(_globalCachedKeyboardHeight); // used to check if click the same item again int? selectedMenuIndex; @@ -408,6 +415,9 @@ class _MobileToolbarState extends State<_MobileToolbar> ); } } + if (keyboardHeight > 0) { + _globalCachedKeyboardHeight = keyboardHeight; + } return SizedBox( height: keyboardHeight, child: (showingMenu && selectedMenuIndex != null) diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/numbered_list/numbered_list_icon.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/numbered_list/numbered_list_icon.dart index 2f31a68a73..f77083d21d 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/numbered_list/numbered_list_icon.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/numbered_list/numbered_list_icon.dart @@ -1,3 +1,4 @@ +import 'package:appflowy/plugins/document/presentation/editor_plugins/base/markdown_text_robot.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -29,9 +30,9 @@ class NumberedListIcon extends StatelessWidget { ); return Padding( - padding: const EdgeInsets.only(right: 8.0), + padding: const EdgeInsets.only(left: 6.0, right: 10.0), child: Text( - node.levelString, + node.buildLevelString(context), style: adjustedTextStyle, strutStyle: StrutStyle.fromTextStyle(combinedTextStyle), textHeightBehavior: TextHeightBehavior( @@ -47,9 +48,12 @@ class NumberedListIcon extends StatelessWidget { } } -extension on Node { - String get levelString { - final builder = _NumberedListIconBuilder(node: this); +extension NumberedListNodeIndex on Node { + String buildLevelString(BuildContext context) { + final builder = NumberedListIndexBuilder( + editorState: context.read(), + node: this, + ); final indexInRootLevel = builder.indexInRootLevel; final indexInSameLevel = builder.indexInSameLevel; final level = indexInRootLevel % 3; @@ -62,11 +66,13 @@ extension on Node { } } -class _NumberedListIconBuilder { - _NumberedListIconBuilder({ +class NumberedListIndexBuilder { + NumberedListIndexBuilder({ + required this.editorState, required this.node, }); + final EditorState editorState; final Node node; // the level of the current node @@ -88,7 +94,13 @@ class _NumberedListIconBuilder { Node? previous = node.previous; // if the previous one is not a numbered list, then it is the first one - if (previous == null || previous.type != NumberedListBlockKeys.type) { + final aiNodeExternalValues = + node.externalValues?.unwrapOrNull(); + + if (previous == null || + previous.type != NumberedListBlockKeys.type || + (aiNodeExternalValues != null && + aiNodeExternalValues.isFirstNumberedListNode)) { return node.attributes[NumberedListBlockKeys.number] ?? level; } @@ -97,10 +109,17 @@ class _NumberedListIconBuilder { startNumber = previous.attributes[NumberedListBlockKeys.number] as int?; level++; previous = previous.previous; + + // break the loop if the start number is found when the current node is an AI node + if (aiNodeExternalValues != null && startNumber != null) { + return startNumber + level - 1; + } } + if (startNumber != null) { - return startNumber + level - 1; + level = startNumber + level - 1; } + return level; } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/service/ai_client.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/service/ai_client.dart deleted file mode 100644 index 801f15a77f..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/service/ai_client.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/error.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/text_completion.dart'; -import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart'; -import 'package:appflowy_result/appflowy_result.dart'; - -abstract class AIRepository { - Future getStreamedCompletions({ - required String prompt, - required Future Function() onStart, - required Future Function(TextCompletionResponse response) onProcess, - required Future Function() onEnd, - required void Function(AIError error) onError, - String? suffix, - int maxTokens = 2048, - double temperature = 0.3, - bool useAction = false, - }); - - Future streamCompletion({ - required String text, - required CompletionTypePB completionType, - required Future Function() onStart, - required Future Function(String text) onProcess, - required Future Function() onEnd, - required void Function(AIError error) onError, - }); - - Future, AIError>> generateImage({ - required String prompt, - int n = 1, - }); -} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/service/openai_client.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/service/openai_client.dart deleted file mode 100644 index 3b52963125..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/service/openai_client.dart +++ /dev/null @@ -1,172 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/ai_client.dart'; -import 'package:appflowy_backend/protobuf/flowy-ai/entities.pbenum.dart'; -import 'package:appflowy_result/appflowy_result.dart'; -import 'package:http/http.dart' as http; - -import 'error.dart'; -import 'text_completion.dart'; - -enum OpenAIRequestType { - textCompletion, - textEdit, - imageGenerations; - - Uri get uri { - switch (this) { - case OpenAIRequestType.textCompletion: - return Uri.parse('https://api.openai.com/v1/completions'); - case OpenAIRequestType.textEdit: - return Uri.parse('https://api.openai.com/v1/chat/completions'); - case OpenAIRequestType.imageGenerations: - return Uri.parse('https://api.openai.com/v1/images/generations'); - } - } -} - -class HttpOpenAIRepository implements AIRepository { - const HttpOpenAIRepository({ - required this.client, - required this.apiKey, - }); - - final http.Client client; - final String apiKey; - - Map get headers => { - 'Authorization': 'Bearer $apiKey', - 'Content-Type': 'application/json', - }; - - @override - Future getStreamedCompletions({ - required String prompt, - required Future Function() onStart, - required Future Function(TextCompletionResponse response) onProcess, - required Future Function() onEnd, - required void Function(AIError error) onError, - String? suffix, - int maxTokens = 2048, - double temperature = 0.3, - bool useAction = false, - }) async { - final parameters = { - 'model': 'gpt-3.5-turbo-instruct', - 'prompt': prompt, - 'suffix': suffix, - 'max_tokens': maxTokens, - 'temperature': temperature, - 'stream': true, - }; - - final request = http.Request('POST', OpenAIRequestType.textCompletion.uri); - request.headers.addAll(headers); - request.body = jsonEncode(parameters); - - final response = await client.send(request); - - // NEED TO REFACTOR. - // WHY OPENAI USE TWO LINES TO INDICATE THE START OF THE STREAMING RESPONSE? - // AND WHY OPENAI USE [DONE] TO INDICATE THE END OF THE STREAMING RESPONSE? - int syntax = 0; - var previousSyntax = ''; - if (response.statusCode == 200) { - await for (final chunk in response.stream - .transform(const Utf8Decoder()) - .transform(const LineSplitter())) { - syntax += 1; - if (!useAction) { - if (syntax == 3) { - await onStart(); - continue; - } else if (syntax < 3) { - continue; - } - } else { - if (syntax == 2) { - await onStart(); - continue; - } else if (syntax < 2) { - continue; - } - } - final data = chunk.trim().split('data: '); - if (data.length > 1) { - if (data[1] != '[DONE]') { - final response = TextCompletionResponse.fromJson( - json.decode(data[1]), - ); - if (response.choices.isNotEmpty) { - final text = response.choices.first.text; - if (text == previousSyntax && text == '\n') { - continue; - } - await onProcess(response); - previousSyntax = response.choices.first.text; - } - } else { - await onEnd(); - } - } - } - } else { - final body = await response.stream.bytesToString(); - onError( - AIError.fromJson(json.decode(body)['error']), - ); - } - return; - } - - @override - Future, AIError>> generateImage({ - required String prompt, - int n = 1, - }) async { - final parameters = { - 'prompt': prompt, - 'n': n, - 'size': '512x512', - }; - - try { - final response = await client.post( - OpenAIRequestType.imageGenerations.uri, - headers: headers, - body: json.encode(parameters), - ); - - if (response.statusCode == 200) { - final data = json.decode( - utf8.decode(response.bodyBytes), - )['data'] as List; - final urls = data - .map((e) => e.values) - .expand((e) => e) - .map((e) => e.toString()) - .toList(); - return FlowyResult.success(urls); - } else { - return FlowyResult.failure( - AIError.fromJson(json.decode(response.body)['error']), - ); - } - } catch (error) { - return FlowyResult.failure(AIError(message: error.toString())); - } - } - - @override - Future streamCompletion({ - required String text, - required CompletionTypePB completionType, - required Future Function() onStart, - required Future Function(String text) onProcess, - required Future Function() onEnd, - required void Function(AIError error) onError, - }) { - throw UnimplementedError(); - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/service/text_completion.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/service/text_completion.dart deleted file mode 100644 index 067049adbf..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/service/text_completion.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; -part 'text_completion.freezed.dart'; -part 'text_completion.g.dart'; - -@freezed -class TextCompletionChoice with _$TextCompletionChoice { - factory TextCompletionChoice({ - required String text, - required int index, - // ignore: invalid_annotation_target - @JsonKey(name: 'finish_reason') String? finishReason, - }) = _TextCompletionChoice; - - factory TextCompletionChoice.fromJson(Map json) => - _$TextCompletionChoiceFromJson(json); -} - -@freezed -class TextCompletionResponse with _$TextCompletionResponse { - const factory TextCompletionResponse({ - required List choices, - }) = _TextCompletionResponse; - - factory TextCompletionResponse.fromJson(Map json) => - _$TextCompletionResponseFromJson(json); -} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/service/text_edit.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/service/text_edit.dart deleted file mode 100644 index 52cce9da4f..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/service/text_edit.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; -part 'text_edit.freezed.dart'; -part 'text_edit.g.dart'; - -@freezed -class TextEditChoice with _$TextEditChoice { - factory TextEditChoice({ - required String text, - required int index, - }) = _TextEditChoice; - - factory TextEditChoice.fromJson(Map json) => - _$TextEditChoiceFromJson(json); -} - -@freezed -class TextEditResponse with _$TextEditResponse { - const factory TextEditResponse({ - required List choices, - }) = _TextEditResponse; - - factory TextEditResponse.fromJson(Map json) => - _$TextEditResponseFromJson(json); -} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/util/ask_ai_node_extension.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/util/ask_ai_node_extension.dart deleted file mode 100644 index 933712217f..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/util/ask_ai_node_extension.dart +++ /dev/null @@ -1,68 +0,0 @@ -import 'package:appflowy/shared/markdown_to_document.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; - -extension AskAINodeExtension on EditorState { - String getMarkdownInSelection(Selection? selection) { - selection ??= this.selection?.normalized; - if (selection == null || selection.isCollapsed) { - return ''; - } - - // if the selected nodes are not entirely selected, slice the nodes - final slicedNodes = []; - final nodes = getNodesInSelection(selection); - - for (final node in nodes) { - final delta = node.delta; - if (delta == null) { - continue; - } - - final slicedDelta = delta.slice( - node == nodes.first ? selection.startIndex : 0, - node == nodes.last ? selection.endIndex : delta.length, - ); - - final copiedNode = node.copyWith( - attributes: { - ...node.attributes, - blockComponentDelta: slicedDelta.toJson(), - }, - ); - - slicedNodes.add(copiedNode); - } - - final markdown = customDocumentToMarkdown( - Document.blank()..insert([0], slicedNodes), - ); - - return markdown; - } - - List getPlainTextInSelection(Selection? selection) { - selection ??= this.selection?.normalized; - if (selection == null || selection.isCollapsed) { - return []; - } - - final res = []; - if (selection.isCollapsed) { - return res; - } - - final nodes = getNodesInSelection(selection); - - for (final node in nodes) { - final delta = node.delta; - if (delta == null) { - continue; - } - final startIndex = node == nodes.first ? selection.startIndex : 0; - final endIndex = node == nodes.last ? selection.endIndex : delta.length; - res.add(delta.slice(startIndex, endIndex).toPlainText()); - } - - return res; - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/util/learn_more_action.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/util/learn_more_action.dart deleted file mode 100644 index 17e89b1bca..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/util/learn_more_action.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'package:appflowy/core/helpers/url_launcher.dart'; - -const String learnMoreUrl = - 'https://docs.appflowy.io/docs/appflowy/product/appflowy-x-openai'; - -Future openLearnMorePage() async { - await afLaunchUrlString(learnMoreUrl); -} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/ai_limit_dialog.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/ai_limit_dialog.dart deleted file mode 100644 index 3a57a7f49c..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/ai_limit_dialog.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; -import 'package:easy_localization/easy_localization.dart'; - -void showAILimitDialog(BuildContext context, String message) { - showConfirmDialog( - context: context, - title: LocaleKeys.sideBar_aiResponseLimitDialogTitle.tr(), - description: message, - ); -} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/ai_writer_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/ai_writer_block_component.dart deleted file mode 100644 index 2938e87516..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/ai_writer_block_component.dart +++ /dev/null @@ -1,404 +0,0 @@ -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/document/application/prelude.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/base/build_context_extension.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/base/markdown_text_robot.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/error.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/ai_writer_block_operations.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/ai_writer_block_widgets.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/discard_dialog.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart'; -import 'package:appflowy/user/application/ai_service.dart'; -import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; -import 'package:appflowy_backend/log.dart'; -import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:provider/provider.dart'; -import 'package:universal_platform/universal_platform.dart'; - -import 'ai_limit_dialog.dart'; - -class AIWriterBlockKeys { - const AIWriterBlockKeys._(); - - static const String type = 'ai_writer'; - static const String prompt = 'prompt'; - static const String startSelection = 'start_selection'; - static const String generationCount = 'generation_count'; - - static String getRewritePrompt(String previousOutput, String prompt) { - return 'I am not satisfied with your previous response ($previousOutput) to the query ($prompt). Please provide an alternative response.'; - } -} - -Node aiWriterNode({ - String prompt = '', - required Selection start, -}) { - return Node( - type: AIWriterBlockKeys.type, - attributes: { - AIWriterBlockKeys.prompt: prompt, - AIWriterBlockKeys.startSelection: start.toJson(), - AIWriterBlockKeys.generationCount: 0, - }, - ); -} - -class AIWriterBlockComponentBuilder extends BlockComponentBuilder { - AIWriterBlockComponentBuilder(); - - @override - BlockComponentWidget build(BlockComponentContext blockComponentContext) { - final node = blockComponentContext.node; - return AIWriterBlockComponent( - key: node.key, - node: node, - showActions: showActions(node), - actionBuilder: (context, state) => actionBuilder( - blockComponentContext, - state, - ), - ); - } - - @override - BlockComponentValidate get validate => (node) => - node.children.isEmpty && - node.attributes[AIWriterBlockKeys.prompt] is String && - node.attributes[AIWriterBlockKeys.startSelection] is Map; -} - -class AIWriterBlockComponent extends BlockComponentStatefulWidget { - const AIWriterBlockComponent({ - super.key, - required super.node, - super.showActions, - super.actionBuilder, - super.configuration = const BlockComponentConfiguration(), - }); - - @override - State createState() => _AIWriterBlockComponentState(); -} - -class _AIWriterBlockComponentState extends State { - final controller = TextEditingController(); - final textFieldFocusNode = FocusNode(); - - late final editorState = context.read(); - late final SelectionGestureInterceptor interceptor; - late final AIWriterBlockOperations aiWriterOperations = - AIWriterBlockOperations( - editorState: editorState, - aiWriterNode: widget.node, - ); - - String get prompt => widget.node.attributes[AIWriterBlockKeys.prompt]; - int get generationCount => - widget.node.attributes[AIWriterBlockKeys.generationCount] ?? 0; - Selection? get startSelection { - final selection = widget.node.attributes[AIWriterBlockKeys.startSelection]; - if (selection != null) { - return Selection.fromJson(selection); - } - return null; - } - - bool isGenerating = false; - - @override - void initState() { - super.initState(); - - _subscribeSelectionGesture(); - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - editorState.selection = null; - textFieldFocusNode.requestFocus(); - }); - } - - @override - void dispose() { - _onExit(); - _unsubscribeSelectionGesture(); - controller.dispose(); - textFieldFocusNode.dispose(); - - super.dispose(); - } - - @override - Widget build(BuildContext context) { - if (UniversalPlatform.isMobile) { - return const SizedBox.shrink(); - } - - final child = Focus( - onKeyEvent: (node, event) { - if (event is! KeyDownEvent) { - return KeyEventResult.ignored; - } - if (event.logicalKey == LogicalKeyboardKey.enter) { - if (!isGenerating) { - _onGenerate(); - } - return KeyEventResult.handled; - } - return KeyEventResult.ignored; - }, - child: Card( - elevation: 5, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - color: Theme.of(context).colorScheme.surface, - child: Container( - margin: const EdgeInsets.all(10), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const AIWriterBlockHeader(), - const Space(0, 10), - if (prompt.isEmpty && generationCount < 1) ...[ - _buildInputWidget(context), - const Space(0, 10), - AIWriterBlockInputField( - onGenerate: _onGenerate, - onExit: _onExit, - ), - ] else ...[ - AIWriterBlockFooter( - onKeep: _onExit, - onRewrite: _onRewrite, - onDiscard: _onDiscard, - ), - ], - ], - ), - ), - ), - ); - - return Padding( - padding: const EdgeInsets.only(left: 40), - child: child, - ); - } - - Widget _buildInputWidget(BuildContext context) { - return FlowyTextField( - hintText: LocaleKeys.document_plugins_autoGeneratorHintText.tr(), - controller: controller, - maxLines: 5, - focusNode: textFieldFocusNode, - autoFocus: false, - hintTextConstraints: const BoxConstraints(), - ); - } - - Future _onExit() async { - await aiWriterOperations.removeAIWriterNode(widget.node); - } - - Future _onGenerate() async { - if (isGenerating) { - return; - } - - isGenerating = true; - - await aiWriterOperations.updatePromptText(controller.text); - - if (!_isAIWriterEnabled) { - Log.error('AI Writer is not enabled'); - return; - } - - final markdownTextRobot = MarkdownTextRobot( - editorState: editorState, - ); - - BarrierDialog? barrierDialog; - - final aiRepository = AppFlowyAIService(); - await aiRepository.streamCompletion( - text: controller.text, - completionType: CompletionTypePB.ContinueWriting, - onStart: () async { - if (mounted) { - barrierDialog = BarrierDialog(context); - barrierDialog?.show(); - await aiWriterOperations.ensurePreviousNodeIsEmptyParagraphNode(); - markdownTextRobot.start(); - } - }, - onProcess: (text) async { - await markdownTextRobot.appendMarkdownText(text); - }, - onEnd: () async { - barrierDialog?.dismiss(); - await markdownTextRobot.stop(); - editorState.service.keyboardService?.enable(); - }, - onError: (error) async { - barrierDialog?.dismiss(); - _showAIWriterError(error); - }, - ); - - await aiWriterOperations.updateGenerationCount(generationCount + 1); - - isGenerating = false; - } - - Future _onDiscard() async { - await aiWriterOperations.discardCurrentResponse( - aiWriterNode: widget.node, - selection: startSelection, - ); - return _onExit(); - } - - Future _onRewrite() async { - if (isGenerating) { - return; - } - - isGenerating = true; - - final previousOutput = _getPreviousOutput(); - if (previousOutput == null) { - return; - } - - // discard the current response - await aiWriterOperations.discardCurrentResponse( - aiWriterNode: widget.node, - selection: startSelection, - ); - - if (!_isAIWriterEnabled) { - return; - } - - final markdownTextRobot = MarkdownTextRobot( - editorState: editorState, - ); - final aiService = AppFlowyAIService(); - await aiService.streamCompletion( - text: AIWriterBlockKeys.getRewritePrompt(previousOutput, prompt), - completionType: CompletionTypePB.ContinueWriting, - onStart: () async { - await aiWriterOperations.ensurePreviousNodeIsEmptyParagraphNode(); - - markdownTextRobot.start(); - }, - onProcess: (text) async { - await markdownTextRobot.appendMarkdownText(text); - }, - onEnd: () async { - await markdownTextRobot.stop(); - }, - onError: (error) { - _showAIWriterError(error); - }, - ); - - await aiWriterOperations.updateGenerationCount(generationCount + 1); - - isGenerating = false; - } - - String? _getPreviousOutput() { - final startSelection = this.startSelection; - if (startSelection != null) { - final end = widget.node.previous?.path; - - if (end != null) { - final result = editorState - .getNodesInSelection( - startSelection.copyWith(end: Position(path: end)), - ) - .fold( - '', - (previousValue, element) { - final delta = element.delta; - if (delta != null) { - return "$previousValue\n${delta.toPlainText()}"; - } else { - return previousValue; - } - }, - ); - return result.trim(); - } - } - return null; - } - - void _subscribeSelectionGesture() { - interceptor = SelectionGestureInterceptor( - key: AIWriterBlockKeys.type, - canTap: (details) { - if (!context.isOffsetInside(details.globalPosition)) { - if (prompt.isNotEmpty || controller.text.isNotEmpty) { - // show dialog - showDialog( - context: context, - builder: (_) => DiscardDialog( - onConfirm: _onDiscard, - onCancel: () {}, - ), - ); - } else if (controller.text.isEmpty) { - _onExit(); - } - } - editorState.service.keyboardService?.disable(); - return false; - }, - ); - editorState.service.selectionService.registerGestureInterceptor( - interceptor, - ); - } - - void _unsubscribeSelectionGesture() { - editorState.service.selectionService.unregisterGestureInterceptor( - AIWriterBlockKeys.type, - ); - } - - void _showAIWriterError(AIError error) { - if (mounted) { - if (error.isLimitExceeded) { - showAILimitDialog(context, error.message); - } else { - showToastNotification( - context, - message: error.message, - type: ToastificationType.error, - ); - } - } - } - - bool get _isAIWriterEnabled { - final userProfile = context.read().state.userProfilePB; - final isAIWriterEnabled = userProfile != null; - - if (!isAIWriterEnabled) { - showToastNotification( - context, - message: LocaleKeys.document_plugins_autoGeneratorCantGetOpenAIKey.tr(), - type: ToastificationType.error, - ); - } - - return isAIWriterEnabled; - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/ai_writer_block_operations.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/ai_writer_block_operations.dart deleted file mode 100644 index 08e242574f..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/ai_writer_block_operations.dart +++ /dev/null @@ -1,112 +0,0 @@ -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/ai_writer_block_component.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; - -/// Notes: All the operation related to the AI writer block will be applied -/// in memory. -class AIWriterBlockOperations { - AIWriterBlockOperations({ - required this.editorState, - required this.aiWriterNode, - }) : assert(aiWriterNode.type == AIWriterBlockKeys.type); - - final EditorState editorState; - final Node aiWriterNode; - - /// Update the prompt text in the node. - Future updatePromptText(String prompt) async { - final transaction = editorState.transaction; - transaction.updateNode( - aiWriterNode, - {AIWriterBlockKeys.prompt: prompt}, - ); - await editorState.apply( - transaction, - options: const ApplyOptions( - inMemoryUpdate: true, - recordUndo: false, - ), - ); - } - - /// Update the generation count in the node. - Future updateGenerationCount(int count) async { - final transaction = editorState.transaction; - transaction.updateNode( - aiWriterNode, - {AIWriterBlockKeys.generationCount: count}, - ); - await editorState.apply( - transaction, - options: const ApplyOptions(inMemoryUpdate: true), - ); - } - - /// Ensure the previous node is a empty paragraph node without any styles. - Future ensurePreviousNodeIsEmptyParagraphNode() async { - final previous = aiWriterNode.previous; - final Selection selection; - - // 1. previous node is null or - // 2. previous node is not a paragraph node or - // 3. previous node is a paragraph node but not empty - final isNotEmptyParagraphNode = previous == null || - previous.type != ParagraphBlockKeys.type || - (previous.delta?.toPlainText().isNotEmpty ?? false); - - if (isNotEmptyParagraphNode) { - final path = aiWriterNode.path; - final transaction = editorState.transaction; - selection = Selection.collapsed(Position(path: path)); - transaction - ..insertNode( - path, - paragraphNode(), - ) - ..afterSelection = selection; - await editorState.apply(transaction); - } else { - selection = Selection.collapsed(Position(path: previous.path)); - } - - final transaction = editorState.transaction; - transaction.updateNode(aiWriterNode, { - AIWriterBlockKeys.startSelection: selection.toJson(), - }); - transaction.afterSelection = selection; - await editorState.apply( - transaction, - options: const ApplyOptions(inMemoryUpdate: true), - ); - } - - /// Discard the current response and delete the previous node. - Future discardCurrentResponse({ - required Node aiWriterNode, - Selection? selection, - }) async { - if (selection != null) { - final start = selection.start.path; - final end = aiWriterNode.previous?.path; - if (end != null) { - final transaction = editorState.transaction; - transaction.deleteNodesAtPath( - start, - end.last - start.last + 1, - ); - await editorState.apply(transaction); - await ensurePreviousNodeIsEmptyParagraphNode(); - } - } - } - - /// Remove the ai writer node from the editor. - Future removeAIWriterNode(Node aiWriterNode) async { - final transaction = editorState.transaction; - transaction.deleteNode(aiWriterNode); - await editorState.apply( - transaction, - options: const ApplyOptions(inMemoryUpdate: true, recordUndo: false), - withUpdateSelection: false, - ); - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/ai_writer_block_widgets.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/ai_writer_block_widgets.dart deleted file mode 100644 index b6681ed7fd..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/ai_writer_block_widgets.dart +++ /dev/null @@ -1,104 +0,0 @@ -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/material.dart'; - -class AIWriterBlockHeader extends StatelessWidget { - const AIWriterBlockHeader({super.key}); - - @override - Widget build(BuildContext context) { - return FlowyText.medium( - LocaleKeys.document_plugins_autoGeneratorTitleName.tr(), - fontSize: 14, - ); - } -} - -class AIWriterBlockInputField extends StatelessWidget { - const AIWriterBlockInputField({ - super.key, - required this.onGenerate, - required this.onExit, - }); - - final VoidCallback onGenerate; - final VoidCallback onExit; - - @override - Widget build(BuildContext context) { - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - PrimaryRoundedButton( - text: LocaleKeys.button_generate.tr(), - margin: const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 10.0, - ), - radius: 8.0, - onTap: onGenerate, - ), - const Space(10, 0), - OutlinedRoundedButton( - text: LocaleKeys.button_cancel.tr(), - margin: const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 10.0, - ), - onTap: onExit, - ), - Flexible( - child: Container( - alignment: Alignment.centerRight, - child: FlowyText.regular( - LocaleKeys.document_plugins_warning.tr(), - color: Theme.of(context).hintColor, - overflow: TextOverflow.ellipsis, - fontSize: 12, - ), - ), - ), - ], - ); - } -} - -class AIWriterBlockFooter extends StatelessWidget { - const AIWriterBlockFooter({ - super.key, - required this.onKeep, - required this.onRewrite, - required this.onDiscard, - }); - - final VoidCallback onKeep; - final VoidCallback onRewrite; - final VoidCallback onDiscard; - - @override - Widget build(BuildContext context) { - return Row( - children: [ - PrimaryRoundedButton( - text: LocaleKeys.button_keep.tr(), - margin: const EdgeInsets.symmetric( - horizontal: 16.0, - vertical: 9.0, - ), - onTap: onKeep, - ), - const HSpace(10), - OutlinedRoundedButton( - text: LocaleKeys.document_plugins_autoGeneratorRewrite.tr(), - onTap: onRewrite, - ), - const HSpace(10), - OutlinedRoundedButton( - text: LocaleKeys.button_discard.tr(), - onTap: onDiscard, - ), - ], - ); - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/ask_ai_action.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/ask_ai_action.dart deleted file mode 100644 index d47c26d655..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/ask_ai_action.dart +++ /dev/null @@ -1,77 +0,0 @@ -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; - -enum AskAIAction { - summarize, - fixSpelling, - improveWriting, - makeItLonger; - - String get toInstruction { - switch (this) { - case AskAIAction.summarize: - return 'Tl;dr'; - case AskAIAction.fixSpelling: - return 'Correct this to standard English:'; - case AskAIAction.improveWriting: - return 'Rewrite this in your own words:'; - case AskAIAction.makeItLonger: - return 'Make this text longer:'; - } - } - - String prompt(String input) { - switch (this) { - case AskAIAction.summarize: - return '$input\n\nTl;dr'; - case AskAIAction.fixSpelling: - return 'Correct this to standard English:\n\n$input'; - case AskAIAction.improveWriting: - return 'Rewrite this:\n\n$input'; - case AskAIAction.makeItLonger: - return 'Make this text longer:\n\n$input'; - } - } - - static AskAIAction from(int index) { - switch (index) { - case 0: - return AskAIAction.summarize; - case 1: - return AskAIAction.fixSpelling; - case 2: - return AskAIAction.improveWriting; - case 3: - return AskAIAction.makeItLonger; - } - return AskAIAction.fixSpelling; - } - - String get name { - switch (this) { - case AskAIAction.summarize: - return LocaleKeys.document_plugins_smartEditSummarize.tr(); - case AskAIAction.fixSpelling: - return LocaleKeys.document_plugins_smartEditFixSpelling.tr(); - case AskAIAction.improveWriting: - return LocaleKeys.document_plugins_smartEditImproveWriting.tr(); - case AskAIAction.makeItLonger: - return LocaleKeys.document_plugins_smartEditMakeLonger.tr(); - } - } -} - -class AskAIActionWrapper extends ActionCell { - AskAIActionWrapper(this.inner); - - final AskAIAction inner; - - Widget? icon(Color iconColor) => null; - - @override - String get name { - return inner.name; - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/ask_ai_action_bloc.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/ask_ai_action_bloc.dart deleted file mode 100644 index 82cd627180..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/ask_ai_action_bloc.dart +++ /dev/null @@ -1,297 +0,0 @@ -import 'dart:async'; - -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/ai_client.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/error.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/ask_ai_action.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; -import 'package:appflowy/shared/markdown_to_document.dart'; -import 'package:appflowy/user/application/ai_service.dart'; -import 'package:appflowy_backend/log.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'ask_ai_action_bloc.freezed.dart'; - -enum AskAIReplacementType { - markdown, - plainText, -} - -const _defaultReplacementType = AskAIReplacementType.markdown; - -class AskAIActionBloc extends Bloc { - AskAIActionBloc({ - required this.node, - required this.editorState, - required this.action, - this.enableLogging = true, - }) : super( - AskAIState.initial(action), - ) { - on((event, emit) async { - await event.when( - initial: (aiRepositoryProvider) async { - aiRepository = await aiRepositoryProvider; - aiRepositoryCompleter.complete(); - }, - started: () async { - await _requestCompletions(); - }, - rewrite: () async { - await _requestCompletions(rewrite: true); - }, - replace: () async { - await _replace(); - await _exit(); - }, - insertBelow: () async { - await _insertBelow(); - await _exit(); - }, - cancel: () async { - isCanceled = true; - await _exit(); - }, - update: (result, isLoading, aiError) { - emit( - state.copyWith( - result: result, - loading: isLoading, - requestError: aiError, - ), - ); - }, - ); - }); - } - - final Node node; - final EditorState editorState; - final AskAIAction action; - final bool enableLogging; - // used to wait for the aiRepository to be initialized - final aiRepositoryCompleter = Completer(); - late final AIRepository aiRepository; - - bool isCanceled = false; - - Future _requestCompletions({ - bool rewrite = false, - }) async { - await aiRepositoryCompleter.future; - - if (rewrite) { - add(const AskAIEvent.update('', true, null)); - } - - if (enableLogging) { - Log.info('[smart_edit] request completions'); - } - - final content = node.attributes[AskAIBlockKeys.content] as String; - await aiRepository.streamCompletion( - text: content, - completionType: completionTypeFromInt(state.action), - onStart: () async { - if (isCanceled) { - return; - } - if (enableLogging) { - Log.info('[smart_edit] start generating'); - } - add(const AskAIEvent.update('', true, null)); - }, - onProcess: (text) async { - if (isCanceled) { - return; - } - // only display the log in debug mode - if (enableLogging) { - Log.debug('[smart_edit] onProcess: $text'); - } - final newResult = state.result + text; - add(AskAIEvent.update(newResult, false, null)); - }, - onEnd: () async { - if (isCanceled) { - return; - } - if (enableLogging) { - Log.info('[smart_edit] end generating'); - } - add(AskAIEvent.update('${state.result}\n', false, null)); - }, - onError: (error) async { - if (isCanceled) { - return; - } - if (enableLogging) { - Log.info('[smart_edit] onError: $error'); - } - add(AskAIEvent.update('', false, error)); - await _exit(); - await _clearSelection(); - }, - ); - } - - Future _insertBelow() async { - // check the selection is not empty - final selection = editorState.selection?.normalized; - if (selection == null) { - return; - } - final nodes = customMarkdownToDocument(state.result) - .root - .children - .map((e) => e.deepCopy()) - .toList(); - final insertedPath = selection.end.path.next; - final transaction = editorState.transaction; - transaction.insertNodes( - insertedPath, - nodes, - ); - final lastDeltaLength = nodes.lastOrNull?.delta?.length ?? 0; - transaction.afterSelection = Selection( - start: Position(path: insertedPath), - end: Position( - path: insertedPath.nextNPath(nodes.length - 1), - offset: lastDeltaLength, - ), - ); - await editorState.apply(transaction); - } - - Future _replace() async { - switch (_defaultReplacementType) { - case AskAIReplacementType.markdown: - await _replaceWithMarkdown(); - case AskAIReplacementType.plainText: - await _replaceWithPlainText(); - } - } - - Future _replaceWithMarkdown() async { - final selection = editorState.selection?.normalized; - if (selection == null) { - return; - } - - final nodes = customMarkdownToDocument(state.result) - .root - .children - .map((e) => e.deepCopy()) - .toList(); - if (nodes.isEmpty) { - return; - } - - final nodesInSelection = editorState.getNodesInSelection(selection); - final transaction = editorState.transaction; - transaction.insertNodes( - selection.start.path, - nodes, - ); - transaction.deleteNodes(nodesInSelection); - transaction.afterSelection = Selection( - start: selection.start, - end: Position( - path: selection.start.path.nextNPath(nodes.length - 1), - offset: nodes.lastOrNull?.delta?.length ?? 0, - ), - ); - await editorState.apply(transaction); - } - - Future _replaceWithPlainText() async { - final result = state.result.trim(); - if (result.isEmpty) { - return; - } - - final selection = editorState.selection?.normalized; - if (selection == null) { - return; - } - final nodes = editorState.getNodesInSelection(selection); - if (nodes.isEmpty || !nodes.every((element) => element.delta != null)) { - return; - } - - final replaceTexts = result.split('\n') - ..removeWhere((element) => element.isEmpty); - final transaction = editorState.transaction; - transaction.replaceTexts( - nodes, - selection, - replaceTexts, - ); - await editorState.apply(transaction); - - int endOffset = replaceTexts.last.length; - if (replaceTexts.length == 1) { - endOffset += selection.start.offset; - } - final end = Position( - path: [selection.start.path.first + replaceTexts.length - 1], - offset: endOffset, - ); - editorState.selection = Selection( - start: selection.start, - end: end, - ); - } - - Future _exit() async { - final transaction = editorState.transaction..deleteNode(node); - await editorState.apply( - transaction, - options: const ApplyOptions( - recordUndo: false, - ), - ); - } - - Future _clearSelection() async { - final selection = editorState.selection; - if (selection == null) { - return; - } - editorState.selection = null; - } -} - -@freezed -class AskAIEvent with _$AskAIEvent { - const factory AskAIEvent.initial( - Future aiRepositoryProvider, - ) = _Initial; - const factory AskAIEvent.started() = _Started; - const factory AskAIEvent.rewrite() = _Rewrite; - const factory AskAIEvent.replace() = _Replace; - const factory AskAIEvent.insertBelow() = _InsertBelow; - const factory AskAIEvent.cancel() = _Cancel; - const factory AskAIEvent.update( - String result, - bool isLoading, - AIError? error, - ) = _Update; -} - -@freezed -class AskAIState with _$AskAIState { - const factory AskAIState({ - required bool loading, - required String result, - required AskAIAction action, - @Default(null) AIError? requestError, - }) = _AskAIState; - - factory AskAIState.initial(AskAIAction action) => AskAIState( - loading: true, - action: action, - result: '', - ); -} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/ask_ai_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/ask_ai_block_component.dart deleted file mode 100644 index 7babb48ba5..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/ask_ai_block_component.dart +++ /dev/null @@ -1,208 +0,0 @@ -import 'dart:async'; - -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/ai_client.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/error.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/ai_limit_dialog.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/ask_ai_action.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/ask_ai_action_bloc.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/ask_ai_block_widgets.dart'; -import 'package:appflowy/startup/startup.dart'; -import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:universal_platform/universal_platform.dart'; - -class AskAIBlockKeys { - const AskAIBlockKeys._(); - - static const type = 'ask_ai'; - - /// The instruction of the smart edit. - /// - /// It is a [AskAIAction] value. - static const action = 'action'; - - /// The input of the smart edit. - /// - /// The content is a string that using '\n\n' as separator. - static const content = 'content'; -} - -Node askAINode({ - required AskAIAction action, - required String content, -}) { - return Node( - type: AskAIBlockKeys.type, - attributes: { - AskAIBlockKeys.action: action.index, - AskAIBlockKeys.content: content, - }, - ); -} - -class AskAIBlockComponentBuilder extends BlockComponentBuilder { - AskAIBlockComponentBuilder(); - - @override - BlockComponentWidget build(BlockComponentContext blockComponentContext) { - final node = blockComponentContext.node; - return AskAIBlockComponentWidget( - key: node.key, - node: node, - showActions: showActions(node), - actionBuilder: (context, state) => actionBuilder( - blockComponentContext, - state, - ), - ); - } - - @override - BlockComponentValidate get validate => (node) => - node.attributes[AskAIBlockKeys.action] is int && - node.attributes[AskAIBlockKeys.content] is String; -} - -class AskAIBlockComponentWidget extends BlockComponentStatefulWidget { - const AskAIBlockComponentWidget({ - super.key, - required super.node, - super.showActions, - super.actionBuilder, - super.configuration = const BlockComponentConfiguration(), - }); - - @override - State createState() => - _AskAIBlockComponentWidgetState(); -} - -class _AskAIBlockComponentWidgetState extends State { - final popoverController = PopoverController(); - - late final editorState = context.read(); - late final action = - AskAIAction.values[widget.node.attributes[AskAIBlockKeys.action] as int]; - late AskAIActionBloc askAIBloc; - - @override - void initState() { - super.initState(); - - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - popoverController.show(); - }); - - askAIBloc = AskAIActionBloc( - node: widget.node, - editorState: editorState, - action: action, - )..add(AskAIEvent.initial(getIt.getAsync())); - } - - @override - void dispose() { - askAIBloc.close(); - - super.dispose(); - } - - @override - void reassemble() { - super.reassemble(); - - _removeNode(); - } - - @override - Widget build(BuildContext context) { - if (UniversalPlatform.isMobile) { - return const SizedBox.shrink(); - } - - final width = _getEditorWidth(); - - return BlocProvider.value( - value: askAIBloc, - child: BlocListener( - listener: _onListen, - child: AppFlowyPopover( - controller: popoverController, - direction: PopoverDirection.bottomWithLeftAligned, - triggerActions: PopoverTriggerFlags.none, - margin: EdgeInsets.zero, - offset: const Offset(40, 0), // align the editor block - windowPadding: EdgeInsets.zero, - constraints: BoxConstraints(maxWidth: width), - canClose: () async { - final completer = Completer(); - final state = askAIBloc.state; - if (state.result.isEmpty) { - completer.complete(true); - } else { - await showCancelAndConfirmDialog( - context: context, - title: LocaleKeys.document_plugins_discardResponse.tr(), - description: '', - confirmLabel: LocaleKeys.button_discard.tr(), - onConfirm: () => completer.complete(true), - onCancel: () => completer.complete(false), - ); - } - return completer.future; - }, - onClose: _removeNode, - popupBuilder: (BuildContext popoverContext) { - return BlocProvider.value( - // request the result when opening the popover - value: askAIBloc..add(const AskAIEvent.started()), - child: const AskAiInputContent(), - ); - }, - child: const SizedBox( - width: double.infinity, - ), - ), - ), - ); - } - - double _getEditorWidth() { - var width = double.infinity; - try { - final editorSize = editorState.renderBox?.size; - final editorWidth = - editorSize?.width.clamp(0, editorState.editorStyle.maxWidth ?? width); - final padding = editorState.editorStyle.padding; - if (editorWidth != null) { - width = editorWidth - padding.left - padding.right; - } - } catch (_) {} - return width; - } - - void _removeNode() { - final transaction = editorState.transaction..deleteNode(widget.node); - editorState.apply(transaction); - } - - void _onListen(BuildContext context, AskAIState state) { - final error = state.requestError; - if (error != null) { - if (error.isLimitExceeded) { - showAILimitDialog(context, error.message); - } else { - showToastNotification( - context, - message: error.message, - type: ToastificationType.error, - ); - } - } - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/ask_ai_block_widgets.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/ask_ai_block_widgets.dart deleted file mode 100644 index 5c70f774ed..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/ask_ai_block_widgets.dart +++ /dev/null @@ -1,112 +0,0 @@ -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/ai_chat/presentation/message/ai_markdown_text.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/ask_ai_action_bloc.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -class AskAiInputContent extends StatelessWidget { - const AskAiInputContent({super.key}); - - @override - Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - return Card( - elevation: 5, - color: Theme.of(context).colorScheme.surface, - margin: EdgeInsets.zero, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), - ), - child: Container( - margin: const EdgeInsets.all(16), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - FlowyText.medium( - state.action.name, - fontSize: 14, - ), - const VSpace(16), - state.loading - ? _buildLoadingWidget(context) - : _buildResultWidget(context, state), - const VSpace(16), - const AskAIFooter(), - ], - ), - ), - ); - }, - ); - } - - Widget _buildResultWidget(BuildContext context, AskAIState state) { - return Flexible( - child: AIMarkdownText( - markdown: state.result, - ), - ); - } - - Widget _buildLoadingWidget(BuildContext context) { - return const Padding( - padding: EdgeInsets.symmetric(horizontal: 4.0), - child: SizedBox.square( - dimension: 14, - child: CircularProgressIndicator( - strokeWidth: 2.0, - ), - ), - ); - } -} - -class AskAIFooter extends StatelessWidget { - const AskAIFooter({super.key}); - - @override - Widget build(BuildContext context) { - return Row( - children: [ - OutlinedRoundedButton( - text: LocaleKeys.document_plugins_autoGeneratorRewrite.tr(), - onTap: () => - context.read().add(const AskAIEvent.rewrite()), - ), - const HSpace(10), - OutlinedRoundedButton( - text: LocaleKeys.button_replace.tr(), - onTap: () => - context.read().add(const AskAIEvent.replace()), - ), - const HSpace(10), - OutlinedRoundedButton( - text: LocaleKeys.button_insertBelow.tr(), - onTap: () => context - .read() - .add(const AskAIEvent.insertBelow()), - ), - const HSpace(10), - OutlinedRoundedButton( - text: LocaleKeys.button_cancel.tr(), - onTap: () => - context.read().add(const AskAIEvent.cancel()), - ), - Expanded( - child: Container( - alignment: Alignment.centerRight, - child: Text( - LocaleKeys.document_plugins_warning.tr(), - style: TextStyle(color: Theme.of(context).hintColor), - overflow: TextOverflow.ellipsis, - ), - ), - ), - ], - ); - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/ask_ai_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/ask_ai_toolbar_item.dart deleted file mode 100644 index 232b5872a6..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/ask_ai_toolbar_item.dart +++ /dev/null @@ -1,147 +0,0 @@ -import 'package:appflowy/generated/flowy_svgs.g.dart'; -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/document/application/document_bloc.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/util/ask_ai_node_extension.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/ask_ai_action.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/ask_ai_block_component.dart'; -import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; -import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -const _kAskAIToolbarItemId = 'appflowy.editor.ask_ai'; - -final ToolbarItem askAIItem = ToolbarItem( - id: _kAskAIToolbarItemId, - group: 0, - isActive: onlyShowInSingleSelectionAndTextType, - builder: (context, editorState, _, __, tooltipBuilder) => AskAIActionList( - editorState: editorState, - tooltipBuilder: tooltipBuilder, - ), -); - -class AskAIActionList extends StatefulWidget { - const AskAIActionList({ - super.key, - required this.editorState, - this.tooltipBuilder, - }); - - final EditorState editorState; - final ToolbarTooltipBuilder? tooltipBuilder; - - @override - State createState() => _AskAIActionListState(); -} - -class _AskAIActionListState extends State { - bool isAIEnabled = true; - - EditorState get editorState => widget.editorState; - - @override - void initState() { - super.initState(); - - isAIEnabled = _isAIEnabled(); - } - - @override - Widget build(BuildContext context) { - return PopoverActionList( - offset: const Offset(-5, 5), - direction: PopoverDirection.bottomWithLeftAligned, - actions: AskAIAction.values - .map((action) => AskAIActionWrapper(action)) - .toList(), - onClosed: () => keepEditorFocusNotifier.decrease(), - buildChild: (controller) { - keepEditorFocusNotifier.increase(); - final child = FlowyButton( - text: FlowyText.regular( - LocaleKeys.document_plugins_smartEdit.tr(), - fontSize: 13.0, - figmaLineHeight: 16.0, - color: Colors.white, - ), - hoverColor: Colors.transparent, - useIntrinsicWidth: true, - leftIcon: const FlowySvg( - FlowySvgs.toolbar_item_ai_s, - size: Size.square(16.0), - color: Colors.white, - ), - onTap: () { - if (isAIEnabled) { - keepEditorFocusNotifier.increase(); - controller.show(); - } else { - showToastNotification( - context, - message: - LocaleKeys.document_plugins_appflowyAIEditDisabled.tr(), - ); - } - }, - ); - - if (widget.tooltipBuilder != null) { - return widget.tooltipBuilder!( - context, - _kAskAIToolbarItemId, - isAIEnabled - ? LocaleKeys.document_plugins_smartEdit.tr() - : LocaleKeys.document_plugins_appflowyAIEditDisabled.tr(), - child, - ); - } - - return child; - }, - onSelected: (action, controller) { - controller.close(); - _insertAskAINode(action); - }, - ); - } - - Future _insertAskAINode( - AskAIActionWrapper actionWrapper, - ) async { - final selection = editorState.selection?.normalized; - if (selection == null) { - return; - } - - final markdown = editorState.getMarkdownInSelection(selection); - - final transaction = editorState.transaction; - transaction.insertNode( - selection.normalized.end.path.next, - askAINode( - action: actionWrapper.inner, - content: markdown, - ), - ); - await editorState.apply( - transaction, - options: const ApplyOptions( - recordUndo: false, - inMemoryUpdate: true, - ), - withUpdateSelection: false, - ); - } - - bool _isAIEnabled() { - final documentContext = widget.editorState.document.root.context; - if (documentContext == null) { - return true; - } - return !documentContext.read().isLocalMode; - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/discard_dialog.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/discard_dialog.dart deleted file mode 100644 index 4c0c2bc91a..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/discard_dialog.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; - -import 'package:flutter/material.dart'; -import 'package:appflowy/generated/locale_keys.g.dart'; - -import 'package:easy_localization/easy_localization.dart'; - -class DiscardDialog extends StatelessWidget { - const DiscardDialog({ - super.key, - required this.onConfirm, - required this.onCancel, - }); - - final VoidCallback onConfirm; - final VoidCallback onCancel; - - @override - Widget build(BuildContext context) { - return NavigatorOkCancelDialog( - message: LocaleKeys.document_plugins_discardResponse.tr(), - okTitle: LocaleKeys.button_discard.tr(), - cancelTitle: LocaleKeys.button_cancel.tr(), - onOkPressed: onConfirm, - onCancelPressed: onCancel, - ); - } -} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart deleted file mode 100644 index 6f475bc627..0000000000 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -class Loading { - Loading(this.context); - - BuildContext? loadingContext; - final BuildContext context; - - bool hasStopped = false; - - void start() => unawaited( - showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - loadingContext = context; - - if (hasStopped) { - WidgetsBinding.instance.addPostFrameCallback((_) { - Navigator.of(loadingContext!).maybePop(); - loadingContext = null; - }); - } - - return const SimpleDialog( - elevation: 0.0, - backgroundColor: - Colors.transparent, // can change this to your preferred color - children: [ - Center( - child: CircularProgressIndicator(), - ), - ], - ); - }, - ), - ); - - void stop() { - if (loadingContext != null) { - Navigator.of(loadingContext!).pop(); - loadingContext = null; - } - - hasStopped = true; - } -} - -class BarrierDialog { - BarrierDialog(this.context); - - late BuildContext loadingContext; - final BuildContext context; - - void show() => unawaited( - showDialog( - context: context, - barrierDismissible: false, - barrierColor: Colors.transparent, - builder: (BuildContext context) { - loadingContext = context; - return const SizedBox.shrink(); - }, - ), - ); - - void dismiss() => Navigator.of(loadingContext).pop(); -} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/outline/outline_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/outline/outline_block_component.dart index 8fc3fd9145..e3120356d9 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/outline/outline_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/outline/outline_block_component.dart @@ -52,6 +52,10 @@ class OutlineBlockComponentBuilder extends BlockComponentBuilder { blockComponentContext, state, ), + actionTrailingBuilder: (context, state) => actionTrailingBuilder( + blockComponentContext, + state, + ), ); } @@ -65,6 +69,7 @@ class OutlineBlockWidget extends BlockComponentStatefulWidget { required super.node, super.showActions, super.actionBuilder, + super.actionTrailingBuilder, super.configuration = const BlockComponentConfiguration(), }); @@ -76,7 +81,9 @@ class _OutlineBlockWidgetState extends State with BlockComponentConfigurable, BlockComponentTextDirectionMixin, - BlockComponentBackgroundColorMixin { + BlockComponentBackgroundColorMixin, + DefaultSelectableMixin, + SelectableMixin { // Change the value if the heading block type supports heading levels greater than '3' static const maxVisibleDepth = 6; @@ -90,6 +97,17 @@ class _OutlineBlockWidgetState extends State late EditorState editorState = context.read(); late Stream stream = editorState.transactionStream; + @override + GlobalKey> blockComponentKey = GlobalKey( + debugLabel: OutlineBlockKeys.type, + ); + + @override + GlobalKey> get containerKey => widget.node.key; + + @override + GlobalKey> get forwardKey => widget.node.key; + @override Widget build(BuildContext context) { return StreamBuilder( @@ -97,19 +115,36 @@ class _OutlineBlockWidgetState extends State builder: (context, snapshot) { Widget child = _buildOutlineBlock(); + child = BlockSelectionContainer( + node: node, + delegate: this, + listenable: editorState.selectionNotifier, + remoteSelection: editorState.remoteSelections, + blockColor: editorState.editorStyle.selectionColor, + selectionAboveBlock: true, + supportTypes: const [ + BlockSelectionType.block, + ], + child: child, + ); + if (UniversalPlatform.isDesktopOrWeb) { if (widget.showActions && widget.actionBuilder != null) { child = BlockComponentActionWrapper( node: widget.node, actionBuilder: widget.actionBuilder!, + actionTrailingBuilder: widget.actionTrailingBuilder, child: child, ); } } else { - child = MobileBlockActionButtons( - node: node, - editorState: editorState, - child: child, + child = Padding( + padding: padding, + child: MobileBlockActionButtons( + node: node, + editorState: editorState, + child: child, + ), ); } @@ -167,10 +202,11 @@ class _OutlineBlockWidgetState extends State } return Container( + key: blockComponentKey, constraints: const BoxConstraints( minHeight: 40.0, ), - padding: padding, + padding: UniversalPlatform.isMobile ? EdgeInsets.zero : padding, child: Container( padding: const EdgeInsets.symmetric( vertical: 2.0, @@ -260,10 +296,13 @@ class OutlineItemWidget extends StatelessWidget { textDirection: textDirection, children: [ HSpace(node.leftIndent), - Text( - node.outlineItemText, - textDirection: textDirection, - style: style, + Flexible( + child: Text( + node.outlineItemText, + textDirection: textDirection, + style: style, + overflow: TextOverflow.ellipsis, + ), ), ], ), diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_block/custom_page_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_block/custom_page_block_component.dart new file mode 100644 index 0000000000..731ba4c7cd --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_block/custom_page_block_component.dart @@ -0,0 +1,117 @@ +import 'package:appflowy/plugins/document/presentation/editor_plugins/database/database_view_block_component.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +// ignore: implementation_imports +import 'package:appflowy_editor/src/editor/block_component/base_component/widget/ignore_parent_gesture.dart'; +// ignore: implementation_imports +import 'package:appflowy_editor/src/flutter/scrollable_positioned_list/scrollable_positioned_list.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class CustomPageBlockComponentBuilder extends BlockComponentBuilder { + @override + BlockComponentWidget build(BlockComponentContext blockComponentContext) { + return CustomPageBlockComponent( + key: blockComponentContext.node.key, + node: blockComponentContext.node, + header: blockComponentContext.header, + footer: blockComponentContext.footer, + ); + } +} + +class CustomPageBlockComponent extends BlockComponentStatelessWidget { + const CustomPageBlockComponent({ + super.key, + required super.node, + super.showActions, + super.actionBuilder, + super.actionTrailingBuilder, + super.configuration = const BlockComponentConfiguration(), + this.header, + this.footer, + }); + + final Widget? header; + final Widget? footer; + + @override + Widget build(BuildContext context) { + final editorState = context.read(); + final scrollController = context.read(); + final items = node.children; + + if (scrollController == null || scrollController.shrinkWrap) { + return SingleChildScrollView( + child: Builder( + builder: (context) { + final scroller = Scrollable.maybeOf(context); + if (scroller != null) { + editorState.updateAutoScroller(scroller); + } + return Column( + children: [ + if (header != null) header!, + ...items.map( + (e) => Container( + constraints: BoxConstraints( + maxWidth: + editorState.editorStyle.maxWidth ?? double.infinity, + ), + padding: editorState.editorStyle.padding, + child: editorState.renderer.build(context, e), + ), + ), + if (footer != null) footer!, + ], + ); + }, + ), + ); + } else { + int extentCount = 0; + if (header != null) extentCount++; + if (footer != null) extentCount++; + + return ScrollablePositionedList.builder( + shrinkWrap: scrollController.shrinkWrap, + itemCount: items.length + extentCount, + itemBuilder: (context, index) { + editorState.updateAutoScroller(Scrollable.of(context)); + if (header != null && index == 0) { + return IgnoreEditorSelectionGesture( + child: header!, + ); + } + + if (footer != null && index == (items.length - 1) + extentCount) { + return IgnoreEditorSelectionGesture( + child: footer!, + ); + } + + final childNode = items[index - (header != null ? 1 : 0)]; + final isOverflowType = overflowTypes.contains(childNode.type); + + final item = Container( + constraints: BoxConstraints( + maxWidth: editorState.editorStyle.maxWidth ?? double.infinity, + ), + padding: isOverflowType + ? EdgeInsets.zero + : editorState.editorStyle.padding, + child: editorState.renderer.build( + context, + childNode, + ), + ); + + return isOverflowType ? item : Center(child: item); + }, + itemScrollController: scrollController.itemScrollController, + scrollOffsetController: scrollController.scrollOffsetController, + itemPositionsListener: scrollController.itemPositionsListener, + scrollOffsetListener: scrollController.scrollOffsetListener, + ); + } + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_cover_image.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_cover_image.dart index 27498cc65e..45a23bc6ac 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_cover_image.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_cover_image.dart @@ -226,7 +226,7 @@ class PageStyleCoverImage extends StatelessWidget { (f) => null, ); final isAppFlowyCloud = - userProfile?.authenticator == AuthenticatorPB.AppFlowyCloud; + userProfile?.workspaceAuthType == AuthTypePB.Server; final PageStyleCoverImageType type; if (!isAppFlowyCloud) { result = await saveImageToLocalStorage(path); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_icon.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_icon.dart index 6b640be1c4..a71b083c81 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_icon.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_icon.dart @@ -4,6 +4,7 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_icon_bloc.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_util.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; @@ -17,9 +18,11 @@ class PageStyleIcon extends StatefulWidget { const PageStyleIcon({ super.key, required this.view, + required this.tabs, }); final ViewPB view; + final List tabs; @override State createState() => _PageStyleIconState(); @@ -33,9 +36,9 @@ class _PageStyleIconState extends State { ..add(const PageStyleIconEvent.initial()), child: BlocBuilder( builder: (context, state) { - final icon = state.icon ?? EmojiIconData.none(); + final icon = state.icon; return GestureDetector( - onTap: () => _showIconSelector(context), + onTap: () => icon == null ? null : _showIconSelector(context, icon), behavior: HitTestBehavior.opaque, child: Container( height: 52, @@ -48,12 +51,15 @@ class _PageStyleIconState extends State { const HSpace(16.0), FlowyText(LocaleKeys.document_plugins_emoji.tr()), const Spacer(), - RawEmojiIconWidget( - emoji: icon.isNotEmpty - ? icon - : EmojiIconData.emoji(LocaleKeys.pageStyle_none.tr()), - emojiSize: icon.isNotEmpty ? 22.0 : 16.0, - ), + (icon?.isEmpty ?? true) + ? FlowyText( + LocaleKeys.pageStyle_none.tr(), + fontSize: 16.0, + ) + : RawEmojiIconWidget( + emoji: icon!, + emojiSize: 16.0, + ), const HSpace(6.0), const FlowySvg(FlowySvgs.m_page_style_arrow_right_s), const HSpace(12.0), @@ -66,7 +72,7 @@ class _PageStyleIconState extends State { ); } - void _showIconSelector(BuildContext context) { + void _showIconSelector(BuildContext context, EmojiIconData icon) { Navigator.pop(context); final pageStyleIconBloc = PageStyleIconBloc(view: widget.view) ..add(const PageStyleIconEvent.initial()); @@ -85,11 +91,14 @@ class _PageStyleIconState extends State { value: pageStyleIconBloc, child: Expanded( child: FlowyIconEmojiPicker( + initialType: icon.type.toPickerTabType(), + documentId: widget.view.id, + tabs: widget.tabs, onSelectedEmoji: (r) { pageStyleIconBloc.add( - PageStyleIconEvent.updateIcon(r, true), + PageStyleIconEvent.updateIcon(r.data, true), ); - Navigator.pop(ctx); + if (!r.keepOpen) Navigator.pop(ctx); }, ), ), diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_icon_bloc.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_icon_bloc.dart index a930c8d87e..b2cd77f312 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_icon_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_icon_bloc.dart @@ -34,14 +34,10 @@ class PageStyleIconBloc extends Bloc { ); }, updateIcon: (icon, shouldUpdateRemote) async { - emit( - state.copyWith( - icon: icon, - ), - ); + emit(state.copyWith(icon: icon)); if (shouldUpdateRemote && icon != null) { await ViewBackendService.updateViewIcon( - viewId: view.id, + view: view, viewIcon: icon, ); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/page_style_bottom_sheet.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/page_style_bottom_sheet.dart index 555881a38f..013a056a7c 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/page_style_bottom_sheet.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/page_style_bottom_sheet.dart @@ -3,6 +3,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_icon.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_layout.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_util.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; @@ -12,9 +13,11 @@ class PageStyleBottomSheet extends StatelessWidget { const PageStyleBottomSheet({ super.key, required this.view, + required this.tabs, }); final ViewPB view; + final List tabs; @override Widget build(BuildContext context) { @@ -50,6 +53,7 @@ class PageStyleBottomSheet extends StatelessWidget { const VSpace(8.0), PageStyleIcon( view: view, + tabs: tabs, ), ], ), diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/parsers/callout_node_parser.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/parsers/callout_node_parser.dart index 867fcf236f..d9cf060e3b 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/parsers/callout_node_parser.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/parsers/callout_node_parser.dart @@ -1,4 +1,5 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; +import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; class CalloutNodeParser extends NodeParser { @@ -9,8 +10,6 @@ class CalloutNodeParser extends NodeParser { @override String transform(Node node, DocumentMarkdownEncoder? encoder) { - assert(node.children.isEmpty); - final icon = node.attributes[CalloutBlockKeys.icon]; final delta = node.delta ?? Delta() ..insert(''); final String markdown = DeltaMarkdownEncoder() @@ -18,9 +17,15 @@ class CalloutNodeParser extends NodeParser { .split('\n') .map((e) => '> $e') .join('\n'); + final type = node.attributes[CalloutBlockKeys.iconType]; + final icon = type == FlowyIconType.emoji.name || type == null || type == "" + ? node.attributes[CalloutBlockKeys.icon] + : null; + + final content = icon == null ? markdown : "> $icon\n$markdown"; + return ''' -> $icon -$markdown +$content '''; } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/parsers/custom_image_node_parser.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/parsers/custom_image_node_parser.dart index 91398302ed..d4b6bb444f 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/parsers/custom_image_node_parser.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/parsers/custom_image_node_parser.dart @@ -1,4 +1,9 @@ +import 'dart:io'; + +import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_block_component.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:archive/archive.dart'; +import 'package:path/path.dart' as p; import '../image/custom_image_block_component/custom_image_block_component.dart'; @@ -16,3 +21,64 @@ class CustomImageNodeParser extends NodeParser { return '![]($url)\n'; } } + +class CustomImageNodeFileParser extends NodeParser { + const CustomImageNodeFileParser(this.files, this.dirPath); + + final List> files; + final String dirPath; + + @override + String get id => ImageBlockKeys.type; + + @override + String transform(Node node, DocumentMarkdownEncoder? encoder) { + assert(node.children.isEmpty); + final url = node.attributes[CustomImageBlockKeys.url]; + final hasFile = File(url).existsSync(); + if (hasFile) { + final bytes = File(url).readAsBytesSync(); + files.add( + Future.value( + ArchiveFile(p.join(dirPath, p.basename(url)), bytes.length, bytes), + ), + ); + return '![](${p.join(dirPath, p.basename(url))})\n'; + } + assert(url != null); + return '![]($url)\n'; + } +} + +class CustomMultiImageNodeFileParser extends NodeParser { + const CustomMultiImageNodeFileParser(this.files, this.dirPath); + + final List> files; + final String dirPath; + + @override + String get id => MultiImageBlockKeys.type; + + @override + String transform(Node node, DocumentMarkdownEncoder? encoder) { + assert(node.children.isEmpty); + final images = node.attributes[MultiImageBlockKeys.images] as List; + final List markdownImages = []; + for (final image in images) { + final String url = image['url'] ?? ''; + if (url.isEmpty) continue; + final hasFile = File(url).existsSync(); + if (hasFile) { + final bytes = File(url).readAsBytesSync(); + final filePath = p.join(dirPath, p.basename(url)); + files.add( + Future.value(ArchiveFile(filePath, bytes.length, bytes)), + ); + markdownImages.add('![]($filePath)'); + } else { + markdownImages.add('![]($url)'); + } + } + return markdownImages.join('\n'); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/parsers/custom_paragraph_node_parser.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/parsers/custom_paragraph_node_parser.dart new file mode 100644 index 0000000000..b7d7674137 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/parsers/custom_paragraph_node_parser.dart @@ -0,0 +1,37 @@ +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:easy_localization/easy_localization.dart'; + +class CustomParagraphNodeParser extends NodeParser { + const CustomParagraphNodeParser(); + + @override + String get id => ParagraphBlockKeys.type; + + @override + String transform(Node node, DocumentMarkdownEncoder? encoder) { + final delta = node.delta; + if (delta != null) { + for (final o in delta) { + final attribute = o.attributes ?? {}; + final Map? mention = attribute[MentionBlockKeys.mention] ?? {}; + if (mention == null) continue; + + /// filter date reminder node, and return it + final String date = mention[MentionBlockKeys.date] ?? ''; + if (date.isNotEmpty) { + final dateTime = DateTime.tryParse(date); + if (dateTime == null) continue; + return '${DateFormat.yMMMd().format(dateTime)}\n'; + } + + /// filter reference page + final String pageId = mention[MentionBlockKeys.pageId] ?? ''; + if (pageId.isNotEmpty) { + return '[]($pageId)\n'; + } + } + } + return const TextNodeParser().transform(node, encoder); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/parsers/database_node_parser.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/parsers/database_node_parser.dart new file mode 100644 index 0000000000..3ba599d491 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/parsers/database_node_parser.dart @@ -0,0 +1,53 @@ +import 'package:appflowy/plugins/document/presentation/editor_plugins/database/database_view_block_component.dart'; +import 'package:appflowy/workspace/application/settings/share/export_service.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:archive/archive.dart'; +import 'package:path/path.dart' as p; + +abstract class DatabaseNodeParser extends NodeParser { + DatabaseNodeParser(this.files, this.dirPath); + + final List> files; + final String dirPath; + + @override + String transform(Node node, DocumentMarkdownEncoder? encoder) { + final String viewId = node.attributes[DatabaseBlockKeys.viewID] ?? ''; + if (viewId.isEmpty) return ''; + files.add(_convertDatabaseToCSV(viewId)); + return '[](${p.join(dirPath, '$viewId.csv')})\n'; + } + + Future _convertDatabaseToCSV(String viewId) async { + final result = await BackendExportService.exportDatabaseAsCSV(viewId); + final filePath = p.join(dirPath, '$viewId.csv'); + ArchiveFile file = ArchiveFile.string(filePath, ''); + result.fold( + (s) => file = ArchiveFile.string(filePath, s.data), + (f) => Log.error('convertDatabaseToCSV error with $viewId, error: $f'), + ); + return file; + } +} + +class GridNodeParser extends DatabaseNodeParser { + GridNodeParser(super.files, super.dirPath); + + @override + String get id => DatabaseBlockKeys.gridType; +} + +class BoardNodeParser extends DatabaseNodeParser { + BoardNodeParser(super.files, super.dirPath); + + @override + String get id => DatabaseBlockKeys.boardType; +} + +class CalendarNodeParser extends DatabaseNodeParser { + CalendarNodeParser(super.files, super.dirPath); + + @override + String get id => DatabaseBlockKeys.calendarType; +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/parsers/document_markdown_parsers.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/parsers/document_markdown_parsers.dart index 3f2895d57e..c0a15629b8 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/parsers/document_markdown_parsers.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/parsers/document_markdown_parsers.dart @@ -1,5 +1,7 @@ export 'callout_node_parser.dart'; export 'custom_image_node_parser.dart'; +export 'custom_paragraph_node_parser.dart'; +export 'database_node_parser.dart'; export 'file_block_node_parser.dart'; export 'link_preview_node_parser.dart'; export 'math_equation_node_parser.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/parsers/markdown_simple_table_parser.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/parsers/markdown_simple_table_parser.dart index 47668ec0ff..09973021f1 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/parsers/markdown_simple_table_parser.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/parsers/markdown_simple_table_parser.dart @@ -1,9 +1,14 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:markdown/markdown.dart' as md; +import 'package:universal_platform/universal_platform.dart'; class MarkdownSimpleTableParser extends CustomMarkdownParser { - const MarkdownSimpleTableParser(); + const MarkdownSimpleTableParser({ + this.tableWidth, + }); + + final double? tableWidth; @override List transform( @@ -98,6 +103,14 @@ class MarkdownSimpleTableParser extends CustomMarkdownParser { ); } - return [simpleTableBlockNode(children: rows)]; + return [ + simpleTableBlockNode( + children: rows, + enableHeaderRow: true, + columnWidths: UniversalPlatform.isMobile || tableWidth == null + ? null + : {for (var i = 0; i < th.length; i++) i.toString(): tableWidth!}, + ), + ]; } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/parsers/sub_page_node_parser.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/parsers/sub_page_node_parser.dart new file mode 100644 index 0000000000..1cf0c569bc --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/parsers/sub_page_node_parser.dart @@ -0,0 +1,20 @@ +import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; + +class SubPageNodeParser extends NodeParser { + const SubPageNodeParser(); + + @override + String get id => SubPageBlockKeys.type; + + @override + String transform(Node node, DocumentMarkdownEncoder? encoder) { + final String viewId = node.attributes[SubPageBlockKeys.viewId] ?? ''; + if (viewId.isNotEmpty) { + final view = pageMemorizer[viewId]; + return '[$viewId](${view?.name ?? ''})\n'; + } + return ''; + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart index 571ac53bc0..4161036a08 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart @@ -1,5 +1,7 @@ export 'actions/block_action_list.dart'; export 'actions/option/option_actions.dart'; +export 'ai/ai_writer_block_component.dart'; +export 'ai/ai_writer_toolbar_item.dart'; export 'align_toolbar_item/align_toolbar_item.dart'; export 'base/backtick_character_command.dart'; export 'base/cover_title_command.dart'; @@ -8,6 +10,10 @@ export 'bulleted_list/bulleted_list_icon.dart'; export 'callout/callout_block_component.dart'; export 'code_block/code_block_language_selector.dart'; export 'code_block/code_block_menu_item.dart'; +export 'columns/simple_column_block_component.dart'; +export 'columns/simple_column_block_width_resizer.dart'; +export 'columns/simple_column_node_extension.dart'; +export 'columns/simple_columns_block_component.dart'; export 'context_menu/custom_context_menu.dart'; export 'copy_and_paste/custom_copy_command.dart'; export 'copy_and_paste/custom_cut_command.dart'; @@ -34,7 +40,6 @@ export 'inline_math_equation/inline_math_equation.dart'; export 'inline_math_equation/inline_math_equation_toolbar_item.dart'; export 'keyboard_interceptor/keyboard_interceptor.dart'; export 'link_preview/custom_link_preview.dart'; -export 'link_preview/link_preview_cache.dart'; export 'link_preview/link_preview_menu.dart'; export 'math_equation/math_equation_block_component.dart'; export 'math_equation/mobile_math_equation_toolbar_item.dart'; @@ -53,13 +58,11 @@ export 'mobile_toolbar_v3/toolbar_item_builder.dart'; export 'mobile_toolbar_v3/undo_redo_toolbar_item.dart'; export 'mobile_toolbar_v3/util.dart'; export 'numbered_list/numbered_list_icon.dart'; -export 'openai/widgets/ai_writer_block_component.dart'; -export 'openai/widgets/ask_ai_block_component.dart'; -export 'openai/widgets/ask_ai_toolbar_item.dart'; export 'outline/outline_block_component.dart'; export 'parsers/document_markdown_parsers.dart'; export 'parsers/markdown_parsers.dart'; export 'parsers/markdown_simple_table_parser.dart'; +export 'quote/quote_block_component.dart'; export 'quote/quote_block_shortcuts.dart'; export 'shortcuts/character_shortcuts.dart'; export 'shortcuts/command_shortcuts.dart'; @@ -72,3 +75,4 @@ export 'table/table_option_action.dart'; export 'todo_list/todo_list_icon.dart'; export 'toggle/toggle_block_component.dart'; export 'toggle/toggle_block_shortcuts.dart'; +export 'video/video_block_component.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/quote/quote_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/quote/quote_block_component.dart new file mode 100644 index 0000000000..39ab2c5327 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/quote/quote_block_component.dart @@ -0,0 +1,324 @@ +import 'dart:async'; + +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:universal_platform/universal_platform.dart'; + +/// In memory cache of the quote block height to avoid flashing when the quote block is updated. +Map _quoteBlockHeightCache = {}; + +typedef QuoteBlockIconBuilder = Widget Function( + BuildContext context, + Node node, +); + +class QuoteBlockKeys { + const QuoteBlockKeys._(); + + static const String type = 'quote'; + + static const String delta = blockComponentDelta; + + static const String backgroundColor = blockComponentBackgroundColor; + + static const String textDirection = blockComponentTextDirection; +} + +Node quoteNode({ + Delta? delta, + String? textDirection, + Attributes? attributes, + Iterable? children, +}) { + attributes ??= {'delta': (delta ?? Delta()).toJson()}; + return Node( + type: QuoteBlockKeys.type, + attributes: { + ...attributes, + if (textDirection != null) QuoteBlockKeys.textDirection: textDirection, + }, + children: children ?? [], + ); +} + +class QuoteBlockComponentBuilder extends BlockComponentBuilder { + QuoteBlockComponentBuilder({ + super.configuration, + this.iconBuilder, + }); + + final QuoteBlockIconBuilder? iconBuilder; + + @override + BlockComponentWidget build(BlockComponentContext blockComponentContext) { + final node = blockComponentContext.node; + return QuoteBlockComponentWidget( + key: node.key, + node: node, + configuration: configuration, + iconBuilder: iconBuilder, + showActions: showActions(node), + actionBuilder: (context, state) => actionBuilder( + blockComponentContext, + state, + ), + actionTrailingBuilder: (context, state) => actionTrailingBuilder( + blockComponentContext, + state, + ), + ); + } + + @override + BlockComponentValidate get validate => (node) => node.delta != null; +} + +class QuoteBlockComponentWidget extends BlockComponentStatefulWidget { + const QuoteBlockComponentWidget({ + super.key, + required super.node, + super.showActions, + super.actionBuilder, + super.actionTrailingBuilder, + super.configuration = const BlockComponentConfiguration(), + this.iconBuilder, + }); + + final QuoteBlockIconBuilder? iconBuilder; + + @override + State createState() => + _QuoteBlockComponentWidgetState(); +} + +class _QuoteBlockComponentWidgetState extends State + with + SelectableMixin, + DefaultSelectableMixin, + BlockComponentConfigurable, + BlockComponentBackgroundColorMixin, + BlockComponentTextDirectionMixin, + BlockComponentAlignMixin, + NestedBlockComponentStatefulWidgetMixin { + @override + final forwardKey = GlobalKey(debugLabel: 'flowy_rich_text'); + + @override + GlobalKey> get containerKey => widget.node.key; + + @override + GlobalKey> blockComponentKey = GlobalKey( + debugLabel: QuoteBlockKeys.type, + ); + + @override + BlockComponentConfiguration get configuration => widget.configuration; + + @override + Node get node => widget.node; + + late ValueNotifier quoteBlockHeightNotifier = ValueNotifier( + _quoteBlockHeightCache[node.id] ?? 0, + ); + + StreamSubscription? _transactionSubscription; + + final GlobalKey layoutBuilderKey = GlobalKey(); + + @override + void initState() { + super.initState(); + + _updateQuoteBlockHeight(); + } + + @override + void dispose() { + _transactionSubscription?.cancel(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return NotificationListener( + key: layoutBuilderKey, + onNotification: (notification) { + _updateQuoteBlockHeight(); + return true; + }, + child: SizeChangedLayoutNotifier( + child: node.children.isEmpty + ? buildComponent(context) + : buildComponentWithChildren(context), + ), + ); + } + + @override + Widget buildComponentWithChildren(BuildContext context) { + final Widget child = Stack( + children: [ + Positioned.fill( + left: UniversalPlatform.isMobile ? padding.left : cachedLeft, + right: UniversalPlatform.isMobile ? padding.right : 0, + child: Container( + color: backgroundColor, + ), + ), + NestedListWidget( + indentPadding: indentPadding, + child: buildComponent(context, withBackgroundColor: false), + children: editorState.renderer.buildList( + context, + widget.node.children, + ), + ), + ], + ); + + return child; + } + + @override + Widget buildComponent( + BuildContext context, { + bool withBackgroundColor = true, + }) { + final textDirection = calculateTextDirection( + layoutDirection: Directionality.maybeOf(context), + ); + + Widget child = AppFlowyRichText( + key: forwardKey, + delegate: this, + node: widget.node, + editorState: editorState, + textAlign: alignment?.toTextAlign ?? textAlign, + placeholderText: placeholderText, + textSpanDecorator: (textSpan) => textSpan.updateTextStyle( + textStyleWithTextSpan(textSpan: textSpan), + ), + placeholderTextSpanDecorator: (textSpan) => textSpan.updateTextStyle( + placeholderTextStyleWithTextSpan(textSpan: textSpan), + ), + textDirection: textDirection, + cursorColor: editorState.editorStyle.cursorColor, + selectionColor: editorState.editorStyle.selectionColor, + cursorWidth: editorState.editorStyle.cursorWidth, + ); + + child = Container( + width: double.infinity, + alignment: alignment, + child: IntrinsicHeight( + child: Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + textDirection: textDirection, + children: [ + widget.iconBuilder != null + ? widget.iconBuilder!(context, node) + : ValueListenableBuilder( + valueListenable: quoteBlockHeightNotifier, + builder: (context, height, child) { + return QuoteIcon(height: height); + }, + ), + Flexible( + child: child, + ), + ], + ), + ), + ); + + child = Container( + color: withBackgroundColor ? backgroundColor : null, + child: Padding( + key: blockComponentKey, + padding: padding, + child: child, + ), + ); + + child = BlockSelectionContainer( + node: node, + delegate: this, + listenable: editorState.selectionNotifier, + remoteSelection: editorState.remoteSelections, + blockColor: editorState.editorStyle.selectionColor, + selectionAboveBlock: true, + supportTypes: const [ + BlockSelectionType.block, + ], + child: child, + ); + + if (widget.showActions && widget.actionBuilder != null) { + child = BlockComponentActionWrapper( + node: node, + actionBuilder: widget.actionBuilder!, + actionTrailingBuilder: widget.actionTrailingBuilder, + child: child, + ); + } + + return child; + } + + void _updateQuoteBlockHeight() { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + final renderObject = layoutBuilderKey.currentContext?.findRenderObject(); + double height = _quoteBlockHeightCache[node.id] ?? 0; + if (renderObject != null && renderObject is RenderBox) { + if (UniversalPlatform.isMobile) { + height = renderObject.size.height - padding.top; + } else { + height = renderObject.size.height - padding.top * 2; + } + } else { + height = 0; + } + + quoteBlockHeightNotifier.value = height; + _quoteBlockHeightCache[node.id] = height; + }); + } +} + +class QuoteIcon extends StatelessWidget { + const QuoteIcon({ + super.key, + this.height = 0, + }); + + final double height; + + @override + Widget build(BuildContext context) { + final textScaleFactor = + context.read().editorStyle.textScaleFactor; + return Container( + alignment: Alignment.center, + constraints: + const BoxConstraints(minWidth: 22, minHeight: 22, maxHeight: 22) * + textScaleFactor, + padding: const EdgeInsets.only(right: 6.0), + child: SizedBox( + width: 3 * textScaleFactor, + // use overflow box to ensure the container can overflow the height so that the children of the quote block can have the quote + child: OverflowBox( + alignment: Alignment.topCenter, + maxHeight: height, + child: Container( + width: 3 * textScaleFactor, + height: height, + color: Theme.of(context).colorScheme.primary, + ), + ), + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/quote/quote_block_shortcuts.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/quote/quote_block_shortcuts.dart index ba3ad6e7df..47c6549923 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/quote/quote_block_shortcuts.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/quote/quote_block_shortcuts.dart @@ -30,10 +30,25 @@ CharacterShortcutEventHandler _insertNewLineHandler = (editorState) async { await editorState.deleteSelection(selection); if (HardwareKeyboard.instance.isShiftPressed) { - await editorState.insertNewLine(); - } else { - await editorState.insertTextAtCurrentSelection('\n'); + // ignore the shift+enter event, fallback to the default behavior + return false; + } else if (node.children.isEmpty && + selection.endIndex == node.delta?.length) { + // insert a new paragraph within the callout block + final path = node.path.child(0); + final transaction = editorState.transaction; + transaction.insertNode( + path, + paragraphNode(), + ); + transaction.afterSelection = Selection.collapsed( + Position( + path: path, + ), + ); + await editorState.apply(transaction); + return true; } - return true; + return false; }; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/shortcuts/character_shortcuts.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/shortcuts/character_shortcuts.dart index 080027d179..13b2fea5ee 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/shortcuts/character_shortcuts.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/shortcuts/character_shortcuts.dart @@ -7,6 +7,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.da import 'package:appflowy/plugins/document/presentation/editor_plugins/shortcuts/heading_block_shortcuts.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/shortcuts/numbered_list_block_shortcuts.dart'; import 'package:appflowy/plugins/document/presentation/editor_style.dart'; +import 'package:appflowy/plugins/emoji/emoji_actions_command.dart'; import 'package:appflowy/plugins/inline_actions/inline_actions_command.dart'; import 'package:appflowy/plugins/inline_actions/inline_actions_service.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; @@ -43,6 +44,8 @@ List buildCharacterShortcutEvents( ), customFormatGreaterEqual, + customFormatDashGreater, + customFormatDoubleHyphenEmDash, customFormatNumberToNumberedList, customFormatSignToHeading, @@ -54,6 +57,7 @@ List buildCharacterShortcutEvents( formatGreaterEqual, // Overridden by customFormatGreaterEqual formatNumberToNumberedList, // Overridden by customFormatNumberToNumberedList formatSignToHeading, // Overridden by customFormatSignToHeading + formatDoubleHyphenEmDash, // Overridden by customFormatDoubleHyphenEmDash ].contains(shortcut), ), @@ -79,5 +83,9 @@ List buildCharacterShortcutEvents( documentBloc.documentId, styleCustomizer.inlineActionsMenuStyleBuilder(), ), + + /// show emoji list + /// - Using `:` + emojiCommand(context), ]; } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/shortcuts/command_shortcuts.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/shortcuts/command_shortcuts.dart index 8b168bcd30..aedfcff432 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/shortcuts/command_shortcuts.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/shortcuts/command_shortcuts.dart @@ -1,6 +1,8 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/align_toolbar_item/custom_text_align_command.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/math_equation/math_equation_shortcut.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/shortcuts/custom_delete_command.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/undo_redo/custom_undo_redo_commands.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/emoji_picker/emoji_picker.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; @@ -37,6 +39,9 @@ List commandShortcutEvents = [ ...customTextAlignCommands, + customDeleteCommand, + insertInlineMathEquationCommand, + // remove standard shortcuts for copy, cut, paste, todo ...standardCommandShortcutEvents ..removeWhere( @@ -50,6 +55,7 @@ List commandShortcutEvents = [ redoCommand, exitEditingCommand, ...tableCommands, + deleteCommand, ].contains(shortcut), ), diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/shortcuts/custom_delete_command.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/shortcuts/custom_delete_command.dart new file mode 100644 index 0000000000..e0663c2bcf --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/shortcuts/custom_delete_command.dart @@ -0,0 +1,115 @@ +import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_block_component.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter/material.dart'; + +/// Delete key event. +/// +/// - support +/// - desktop +/// - web +/// +final CommandShortcutEvent customDeleteCommand = CommandShortcutEvent( + key: 'Delete Key', + getDescription: () => AppFlowyEditorL10n.current.cmdDeleteRight, + command: 'delete, shift+delete', + handler: _deleteCommandHandler, +); + +CommandShortcutEventHandler _deleteCommandHandler = (editorState) { + final selection = editorState.selection; + final selectionType = editorState.selectionType; + if (selection == null) { + return KeyEventResult.ignored; + } + if (selectionType == SelectionType.block) { + return _deleteInBlockSelection(editorState); + } else if (selection.isCollapsed) { + return _deleteInCollapsedSelection(editorState); + } else { + return _deleteInNotCollapsedSelection(editorState); + } +}; + +/// Handle delete key event when selection is collapsed. +CommandShortcutEventHandler _deleteInCollapsedSelection = (editorState) { + final selection = editorState.selection; + if (selection == null || !selection.isCollapsed) { + return KeyEventResult.ignored; + } + + final position = selection.start; + final node = editorState.getNodeAtPath(position.path); + final delta = node?.delta; + if (node == null || delta == null) { + return KeyEventResult.ignored; + } + + final transaction = editorState.transaction; + + if (position.offset == delta.length) { + final Node? tableParent = + node.findParent((element) => element.type == SimpleTableBlockKeys.type); + Node? nextTableParent; + final next = node.findDownward((element) { + nextTableParent = element + .findParent((element) => element.type == SimpleTableBlockKeys.type); + // break if only one is in a table or they're in different tables + return tableParent != nextTableParent || + // merge the next node with delta + element.delta != null; + }); + // table nodes should be deleted using the table menu + // in-table paragraphs should only be deleted inside the table + if (next != null && tableParent == nextTableParent) { + if (next.children.isNotEmpty) { + final path = node.path + [node.children.length]; + transaction.insertNodes(path, next.children); + } + transaction + ..deleteNode(next) + ..mergeText( + node, + next, + ); + editorState.apply(transaction); + return KeyEventResult.handled; + } + } else { + final nextIndex = delta.nextRunePosition(position.offset); + if (nextIndex <= delta.length) { + transaction.deleteText( + node, + position.offset, + nextIndex - position.offset, + ); + editorState.apply(transaction); + return KeyEventResult.handled; + } + } + + return KeyEventResult.ignored; +}; + +/// Handle delete key event when selection is not collapsed. +CommandShortcutEventHandler _deleteInNotCollapsedSelection = (editorState) { + final selection = editorState.selection; + if (selection == null || selection.isCollapsed) { + return KeyEventResult.ignored; + } + editorState.deleteSelection(selection); + return KeyEventResult.handled; +}; + +CommandShortcutEventHandler _deleteInBlockSelection = (editorState) { + final selection = editorState.selection; + if (selection == null || editorState.selectionType != SelectionType.block) { + return KeyEventResult.ignored; + } + final transaction = editorState.transaction; + transaction.deleteNodesAtPath(selection.start.path); + editorState + .apply(transaction) + .then((value) => editorState.selectionType = null); + + return KeyEventResult.handled; +}; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_block_component.dart index 495fb7c922..ee6020793c 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_block_component.dart @@ -143,8 +143,11 @@ Node createSimpleTableBlockNode({ class SimpleTableBlockComponentBuilder extends BlockComponentBuilder { SimpleTableBlockComponentBuilder({ super.configuration, + this.alwaysDistributeColumnWidths = false, }); + final bool alwaysDistributeColumnWidths; + @override BlockComponentWidget build(BlockComponentContext blockComponentContext) { final node = blockComponentContext.node; @@ -152,11 +155,16 @@ class SimpleTableBlockComponentBuilder extends BlockComponentBuilder { key: node.key, node: node, configuration: configuration, + alwaysDistributeColumnWidths: alwaysDistributeColumnWidths, showActions: showActions(node), actionBuilder: (context, state) => actionBuilder( blockComponentContext, state, ), + actionTrailingBuilder: (context, state) => actionTrailingBuilder( + blockComponentContext, + state, + ), ); } @@ -170,9 +178,13 @@ class SimpleTableBlockWidget extends BlockComponentStatefulWidget { required super.node, super.showActions, super.actionBuilder, + super.actionTrailingBuilder, super.configuration = const BlockComponentConfiguration(), + required this.alwaysDistributeColumnWidths, }); + final bool alwaysDistributeColumnWidths; + @override State createState() => _SimpleTableBlockWidgetState(); } @@ -218,6 +230,7 @@ class _SimpleTableBlockWidgetState extends State Widget child = SimpleTableWidget( node: node, simpleTableContext: simpleTableContext, + alwaysDistributeColumnWidths: widget.alwaysDistributeColumnWidths, ); if (UniversalPlatform.isDesktop) { @@ -254,6 +267,7 @@ class _SimpleTableBlockWidgetState extends State child = BlockComponentActionWrapper( node: node, actionBuilder: widget.actionBuilder!, + actionTrailingBuilder: widget.actionTrailingBuilder, child: child, ); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_cell_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_cell_block_component.dart index f234440169..29b3c3455f 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_cell_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_cell_block_component.dart @@ -29,8 +29,11 @@ Node simpleTableCellBlockNode({ class SimpleTableCellBlockComponentBuilder extends BlockComponentBuilder { SimpleTableCellBlockComponentBuilder({ super.configuration, + this.alwaysDistributeColumnWidths = false, }); + final bool alwaysDistributeColumnWidths; + @override BlockComponentWidget build(BlockComponentContext blockComponentContext) { final node = blockComponentContext.node; @@ -38,11 +41,16 @@ class SimpleTableCellBlockComponentBuilder extends BlockComponentBuilder { key: node.key, node: node, configuration: configuration, + alwaysDistributeColumnWidths: alwaysDistributeColumnWidths, showActions: showActions(node), actionBuilder: (context, state) => actionBuilder( blockComponentContext, state, ), + actionTrailingBuilder: (context, state) => actionTrailingBuilder( + blockComponentContext, + state, + ), ); } @@ -56,9 +64,13 @@ class SimpleTableCellBlockWidget extends BlockComponentStatefulWidget { required super.node, super.showActions, super.actionBuilder, + super.actionTrailingBuilder, super.configuration = const BlockComponentConfiguration(), + required this.alwaysDistributeColumnWidths, }); + final bool alwaysDistributeColumnWidths; + @override State createState() => SimpleTableCellBlockWidgetState(); @@ -239,7 +251,7 @@ class SimpleTableCellBlockWidgetState extends State constraints: const BoxConstraints( minWidth: SimpleTableConstants.minimumColumnWidth, ), - width: node.columnWidth, + width: widget.alwaysDistributeColumnWidths ? null : node.columnWidth, child: node.children.isEmpty ? Column( children: [ @@ -282,9 +294,34 @@ class SimpleTableCellBlockWidgetState extends State return ValueListenableBuilder( valueListenable: isReorderingHitCellNotifier, builder: (context, isReorderingHitCellNotifier, _) { - return DecoratedBox( - decoration: _buildDecoration(), - child: child!, + final previousCell = node.getPreviousCellInSameRow(); + return Stack( + children: [ + DecoratedBox( + decoration: _buildDecoration(), + child: child!, + ), + Positioned( + right: 0, + top: 0, + bottom: 0, + child: SimpleTableColumnResizeHandle( + node: node, + ), + ), + if (node.columnIndex != 0 && previousCell != null) + Positioned( + left: 0, + top: 0, + bottom: 0, + // pass the previous node to the resize handle + // to make the resize handle work correctly + child: SimpleTableColumnResizeHandle( + node: previousCell, + isPreviousCell: true, + ), + ), + ], ); }, ); @@ -436,7 +473,7 @@ class SimpleTableCellBlockWidgetState extends State final isSelectingTable = simpleTableContext?.isSelectingTable.value ?? false; if (isSelectingTable) { - return Theme.of(context).colorScheme.primary.withOpacity(0.1); + return Theme.of(context).colorScheme.primary.withValues(alpha: 0.1); } final columnColor = node.buildColumnColor(context); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_constants.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_constants.dart index 559b4d202d..295a636e09 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_constants.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_constants.dart @@ -86,6 +86,14 @@ class SimpleTableContext { /// This value is available on mobile only final ValueNotifier isReorderingHitIndex = ValueNotifier(null); + /// resizingCell is the cell that the user is resizing + /// + /// This value is available on mobile only + final ValueNotifier resizingCell = ValueNotifier(null); + + /// Scroll controller for the table + ScrollController? horizontalScrollController; + void _onHoveringOnColumnsAndRowsChanged() { if (!_enableTableDebugLog) { return; @@ -177,12 +185,13 @@ class SimpleTableContext { reorderingOffset.dispose(); isEditingCell.dispose(); isReorderingHitIndex.dispose(); + resizingCell.dispose(); } } class SimpleTableConstants { /// Table - static const defaultColumnWidth = 120.0; + static const defaultColumnWidth = 160.0; static const minimumColumnWidth = 36.0; static const defaultRowHeight = 36.0; @@ -285,7 +294,7 @@ extension SimpleTableColors on BuildContext { Color get simpleTableDividerColor => Theme.of(this).isLightMode ? const Color(0x141F2329) - : const Color(0xFF23262B).withOpacity(0.5); + : const Color(0xFF23262B).withValues(alpha: 0.5); Color get simpleTableMoreActionBackgroundColor => Theme.of(this).isLightMode ? const Color(0xFFF2F3F5) diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_more_action.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_more_action.dart index be82ee3627..4906ed85eb 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_more_action.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_more_action.dart @@ -412,7 +412,9 @@ class SimpleTableActionMenu extends StatelessWidget { editorState.service.keyboardService?.closeKeyboard(); // delay the bottom sheet show to make sure the keyboard is closed Future.delayed(Durations.short3, () { - _showTableActionBottomSheet(context); + if (context.mounted) { + _showTableActionBottomSheet(context); + } }); }, child: Container( diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_header_operation.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_header_operation.dart index 021181591f..a60ece2c2c 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_header_operation.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_header_operation.dart @@ -1,4 +1,4 @@ -import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_block_component.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; @@ -18,9 +18,15 @@ extension TableHeaderOperation on EditorState { 'toggle enable header column: $enable in table ${tableNode.id}', ); + final columnColors = tableNode.columnColors; + final transaction = this.transaction; transaction.updateNode(tableNode, { SimpleTableBlockKeys.enableHeaderColumn: enable, + // remove the previous background color if the header column is enable again + if (enable) + SimpleTableBlockKeys.columnColors: columnColors + ..removeWhere((key, _) => key == '0'), }); await apply(transaction); } @@ -38,9 +44,15 @@ extension TableHeaderOperation on EditorState { Log.info('toggle enable header row: $enable in table ${tableNode.id}'); + final rowColors = tableNode.rowColors; + final transaction = this.transaction; transaction.updateNode(tableNode, { SimpleTableBlockKeys.enableHeaderRow: enable, + // remove the previous background color if the header row is enable again + if (enable) + SimpleTableBlockKeys.rowColors: rowColors + ..removeWhere((key, _) => key == '0'), }); await apply(transaction); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_map_operation.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_map_operation.dart index cb42571704..875da5fffe 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_map_operation.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_map_operation.dart @@ -104,6 +104,18 @@ extension TableMapOperation on Node { comparator: (iKey, index) => iKey >= index, ); + final rowBoldAttributes = _remapSource( + this.rowBoldAttributes, + index, + comparator: (iKey, index) => iKey >= index, + ); + + final rowTextColors = _remapSource( + this.rowTextColors, + index, + comparator: (iKey, index) => iKey >= index, + ); + return attributes .mergeValues( SimpleTableBlockKeys.rowColors, @@ -112,6 +124,14 @@ extension TableMapOperation on Node { .mergeValues( SimpleTableBlockKeys.rowAligns, rowAligns, + ) + .mergeValues( + SimpleTableBlockKeys.rowBoldAttributes, + rowBoldAttributes, + ) + .mergeValues( + SimpleTableBlockKeys.rowTextColors, + rowTextColors, ); } catch (e) { Log.warn('Failed to map row insertion attributes: $e'); @@ -167,6 +187,18 @@ extension TableMapOperation on Node { comparator: (iKey, index) => iKey >= index, ); + final columnBoldAttributes = _remapSource( + this.columnBoldAttributes, + index, + comparator: (iKey, index) => iKey >= index, + ); + + final columnTextColors = _remapSource( + this.columnTextColors, + index, + comparator: (iKey, index) => iKey >= index, + ); + final bool distributeColumnWidthsEvenly = attributes[SimpleTableBlockKeys.distributeColumnWidthsEvenly] ?? false; @@ -189,6 +221,14 @@ extension TableMapOperation on Node { .mergeValues( SimpleTableBlockKeys.columnWidths, columnWidths, + ) + .mergeValues( + SimpleTableBlockKeys.columnBoldAttributes, + columnBoldAttributes, + ) + .mergeValues( + SimpleTableBlockKeys.columnTextColors, + columnTextColors, ); } catch (e) { Log.warn('Failed to map row insertion attributes: $e'); @@ -238,6 +278,18 @@ extension TableMapOperation on Node { index, ); + final (rowBoldAttributes, duplicatedRowBoldAttribute) = + _findDuplicatedEntryAndRemap( + this.rowBoldAttributes, + index, + ); + + final (rowTextColors, duplicatedRowTextColor) = + _findDuplicatedEntryAndRemap( + this.rowTextColors, + index, + ); + return attributes .mergeValues( SimpleTableBlockKeys.rowColors, @@ -248,6 +300,16 @@ extension TableMapOperation on Node { SimpleTableBlockKeys.rowAligns, rowAligns, duplicatedEntry: duplicatedRowAlign, + ) + .mergeValues( + SimpleTableBlockKeys.rowBoldAttributes, + rowBoldAttributes, + duplicatedEntry: duplicatedRowBoldAttribute, + ) + .mergeValues( + SimpleTableBlockKeys.rowTextColors, + rowTextColors, + duplicatedEntry: duplicatedRowTextColor, ); } catch (e) { Log.warn('Failed to map row insertion attributes: $e'); @@ -304,6 +366,18 @@ extension TableMapOperation on Node { index, ); + final (columnBoldAttributes, duplicatedColumnBoldAttribute) = + _findDuplicatedEntryAndRemap( + this.columnBoldAttributes, + index, + ); + + final (columnTextColors, duplicatedColumnTextColor) = + _findDuplicatedEntryAndRemap( + this.columnTextColors, + index, + ); + return attributes .mergeValues( SimpleTableBlockKeys.columnColors, @@ -319,6 +393,16 @@ extension TableMapOperation on Node { SimpleTableBlockKeys.columnWidths, columnWidths, duplicatedEntry: duplicatedColumnWidth, + ) + .mergeValues( + SimpleTableBlockKeys.columnBoldAttributes, + columnBoldAttributes, + duplicatedEntry: duplicatedColumnBoldAttribute, + ) + .mergeValues( + SimpleTableBlockKeys.columnTextColors, + columnTextColors, + duplicatedEntry: duplicatedColumnTextColor, ); } catch (e) { Log.warn('Failed to map column duplication attributes: $e'); @@ -364,6 +448,7 @@ extension TableMapOperation on Node { comparator: (iKey, index) => iKey > index, filterIndex: index, ); + final columnAligns = _remapSource( this.columnAligns, index, @@ -371,6 +456,7 @@ extension TableMapOperation on Node { comparator: (iKey, index) => iKey > index, filterIndex: index, ); + final columnWidths = _remapSource( this.columnWidths, index, @@ -379,6 +465,22 @@ extension TableMapOperation on Node { filterIndex: index, ); + final columnBoldAttributes = _remapSource( + this.columnBoldAttributes, + index, + increment: false, + comparator: (iKey, index) => iKey > index, + filterIndex: index, + ); + + final columnTextColors = _remapSource( + this.columnTextColors, + index, + increment: false, + comparator: (iKey, index) => iKey > index, + filterIndex: index, + ); + return attributes .mergeValues( SimpleTableBlockKeys.columnColors, @@ -391,6 +493,14 @@ extension TableMapOperation on Node { .mergeValues( SimpleTableBlockKeys.columnWidths, columnWidths, + ) + .mergeValues( + SimpleTableBlockKeys.columnBoldAttributes, + columnBoldAttributes, + ) + .mergeValues( + SimpleTableBlockKeys.columnTextColors, + columnTextColors, ); } catch (e) { Log.warn('Failed to map column deletion attributes: $e'); @@ -443,6 +553,22 @@ extension TableMapOperation on Node { filterIndex: index, ); + final rowBoldAttributes = _remapSource( + this.rowBoldAttributes, + index, + increment: false, + comparator: (iKey, index) => iKey > index, + filterIndex: index, + ); + + final rowTextColors = _remapSource( + this.rowTextColors, + index, + increment: false, + comparator: (iKey, index) => iKey > index, + filterIndex: index, + ); + return attributes .mergeValues( SimpleTableBlockKeys.rowColors, @@ -451,6 +577,14 @@ extension TableMapOperation on Node { .mergeValues( SimpleTableBlockKeys.rowAligns, rowAligns, + ) + .mergeValues( + SimpleTableBlockKeys.rowBoldAttributes, + rowBoldAttributes, + ) + .mergeValues( + SimpleTableBlockKeys.rowTextColors, + rowTextColors, ); } catch (e) { Log.warn('Failed to map row deletion attributes: $e'); @@ -531,6 +665,10 @@ extension TableMapOperation on Node { final duplicatedColumnColor = this.columnColors[fromIndex.toString()]; final duplicatedColumnAlign = this.columnAligns[fromIndex.toString()]; final duplicatedColumnWidth = this.columnWidths[fromIndex.toString()]; + final duplicatedColumnBoldAttribute = + this.columnBoldAttributes[fromIndex.toString()]; + final duplicatedColumnTextColor = + this.columnTextColors[fromIndex.toString()]; /// Case 1: fromIndex > toIndex /// Before: @@ -619,6 +757,34 @@ extension TableMapOperation on Node { filterIndex: fromIndex, ); + final columnBoldAttributes = _remapSource( + this.columnBoldAttributes, + fromIndex, + increment: fromIndex > toIndex, + comparator: (iKey, index) { + if (fromIndex > toIndex) { + return iKey < fromIndex && iKey >= toIndex; + } else { + return iKey > fromIndex && iKey <= toIndex; + } + }, + filterIndex: fromIndex, + ); + + final columnTextColors = _remapSource( + this.columnTextColors, + fromIndex, + increment: fromIndex > toIndex, + comparator: (iKey, index) { + if (fromIndex > toIndex) { + return iKey < fromIndex && iKey >= toIndex; + } else { + return iKey > fromIndex && iKey <= toIndex; + } + }, + filterIndex: fromIndex, + ); + return attributes .mergeValues( SimpleTableBlockKeys.columnColors, @@ -652,6 +818,28 @@ extension TableMapOperation on Node { ) : null, removeNullValue: true, + ) + .mergeValues( + SimpleTableBlockKeys.columnBoldAttributes, + columnBoldAttributes, + duplicatedEntry: duplicatedColumnBoldAttribute != null + ? MapEntry( + toIndex.toString(), + duplicatedColumnBoldAttribute, + ) + : null, + removeNullValue: true, + ) + .mergeValues( + SimpleTableBlockKeys.columnTextColors, + columnTextColors, + duplicatedEntry: duplicatedColumnTextColor != null + ? MapEntry( + toIndex.toString(), + duplicatedColumnTextColor, + ) + : null, + removeNullValue: true, ); } catch (e) { Log.warn('Failed to map column deletion attributes: $e'); @@ -667,6 +855,9 @@ extension TableMapOperation on Node { try { final duplicatedRowColor = this.rowColors[fromIndex.toString()]; final duplicatedRowAlign = this.rowAligns[fromIndex.toString()]; + final duplicatedRowBoldAttribute = + this.rowBoldAttributes[fromIndex.toString()]; + final duplicatedRowTextColor = this.rowTextColors[fromIndex.toString()]; /// Example: /// Case 1: fromIndex > toIndex @@ -742,6 +933,34 @@ extension TableMapOperation on Node { filterIndex: fromIndex, ); + final rowBoldAttributes = _remapSource( + this.rowBoldAttributes, + fromIndex, + increment: fromIndex > toIndex, + comparator: (iKey, index) { + if (fromIndex > toIndex) { + return iKey < fromIndex && iKey >= toIndex; + } else { + return iKey > fromIndex && iKey <= toIndex; + } + }, + filterIndex: fromIndex, + ); + + final rowTextColors = _remapSource( + this.rowTextColors, + fromIndex, + increment: fromIndex > toIndex, + comparator: (iKey, index) { + if (fromIndex > toIndex) { + return iKey < fromIndex && iKey >= toIndex; + } else { + return iKey > fromIndex && iKey <= toIndex; + } + }, + filterIndex: fromIndex, + ); + return attributes .mergeValues( SimpleTableBlockKeys.rowColors, @@ -764,6 +983,28 @@ extension TableMapOperation on Node { ) : null, removeNullValue: true, + ) + .mergeValues( + SimpleTableBlockKeys.rowBoldAttributes, + rowBoldAttributes, + duplicatedEntry: duplicatedRowBoldAttribute != null + ? MapEntry( + toIndex.toString(), + duplicatedRowBoldAttribute, + ) + : null, + removeNullValue: true, + ) + .mergeValues( + SimpleTableBlockKeys.rowTextColors, + rowTextColors, + duplicatedEntry: duplicatedRowTextColor != null + ? MapEntry( + toIndex.toString(), + duplicatedRowTextColor, + ) + : null, + removeNullValue: true, ); } catch (e) { Log.warn('Failed to map row reordering attributes: $e'); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_node_extension.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_node_extension.dart index 0987bc29d3..1a2e21c305 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_node_extension.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_node_extension.dart @@ -238,8 +238,13 @@ extension TableNodeExtension on Node { try { final columnWidths = parentTableNode.attributes[SimpleTableBlockKeys.columnWidths]; - final width = columnWidths?[columnIndex.toString()]; - return width ?? SimpleTableConstants.defaultColumnWidth; + final width = columnWidths?[columnIndex.toString()] as Object?; + if (width == null) { + return SimpleTableConstants.defaultColumnWidth; + } + return width.toDouble( + defaultValue: SimpleTableConstants.defaultColumnWidth, + ); } catch (e) { Log.warn('get column width: $e'); return SimpleTableConstants.defaultColumnWidth; @@ -856,3 +861,18 @@ extension TableNodeExtension on Node { return TableAlign.left; } } + +extension on Object { + double toDouble({double defaultValue = 0}) { + if (this is double) { + return this as double; + } + if (this is String) { + return double.tryParse(this as String) ?? defaultValue; + } + if (this is int) { + return (this as int).toDouble(); + } + return defaultValue; + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_style_operation.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_style_operation.dart index 48ffaae3cd..7afa7da66b 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_style_operation.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_operations/simple_table_style_operation.dart @@ -15,11 +15,6 @@ extension TableOptionOperation on EditorState { required Node tableCellNode, required double deltaX, }) async { - // Disable in mobile - if (UniversalPlatform.isMobile) { - return; - } - assert(tableCellNode.type == SimpleTableCellBlockKeys.type); if (tableCellNode.type != SimpleTableCellBlockKeys.type) { @@ -65,11 +60,6 @@ extension TableOptionOperation on EditorState { required Node tableCellNode, required double width, }) async { - // Disable in mobile - if (UniversalPlatform.isMobile) { - return; - } - assert(tableCellNode.type == SimpleTableCellBlockKeys.type); if (tableCellNode.type != SimpleTableCellBlockKeys.type) { @@ -117,6 +107,8 @@ extension TableOptionOperation on EditorState { required Node tableCellNode, required TableAlign align, }) async { + await clearColumnTextAlign(tableCellNode: tableCellNode); + final columnIndex = tableCellNode.columnIndex; await _updateTableAttributes( tableCellNode: tableCellNode, @@ -144,6 +136,8 @@ extension TableOptionOperation on EditorState { required Node tableCellNode, required TableAlign align, }) async { + await clearRowTextAlign(tableCellNode: tableCellNode); + final rowIndex = tableCellNode.rowIndex; await _updateTableAttributes( tableCellNode: tableCellNode, @@ -385,4 +379,67 @@ extension TableOptionOperation on EditorState { transaction.updateNode(parentTableNode, attributes); await apply(transaction); } + + /// Clear the text align of the column at the index where the table cell node is located. + Future clearColumnTextAlign({ + required Node tableCellNode, + }) async { + final parentTableNode = tableCellNode.parentTableNode; + if (parentTableNode == null) { + Log.warn('parent table node is null'); + return; + } + final columnIndex = tableCellNode.columnIndex; + final transaction = this.transaction; + for (var i = 0; i < parentTableNode.rowLength; i++) { + final cell = parentTableNode.getTableCellNode( + rowIndex: i, + columnIndex: columnIndex, + ); + if (cell == null) { + continue; + } + for (final child in cell.children) { + transaction.updateNode(child, { + blockComponentAlign: null, + }); + } + } + if (transaction.operations.isNotEmpty) { + await apply(transaction); + } + } + + /// Clear the text align of the row at the index where the table cell node is located. + Future clearRowTextAlign({ + required Node tableCellNode, + }) async { + final parentTableNode = tableCellNode.parentTableNode; + if (parentTableNode == null) { + Log.warn('parent table node is null'); + return; + } + final rowIndex = tableCellNode.rowIndex; + final transaction = this.transaction; + for (var i = 0; i < parentTableNode.columnLength; i++) { + final cell = parentTableNode.getTableCellNode( + rowIndex: rowIndex, + columnIndex: i, + ); + if (cell == null) { + continue; + } + for (final child in cell.children) { + transaction.updateNode( + child, + { + blockComponentAlign: null, + }, + ); + } + } + if (transaction.operations.isNotEmpty) { + await apply(transaction); + } + } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_row_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_row_block_component.dart index 712df59b82..99f23d1ee9 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_row_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_row_block_component.dart @@ -21,8 +21,11 @@ Node simpleTableRowBlockNode({ class SimpleTableRowBlockComponentBuilder extends BlockComponentBuilder { SimpleTableRowBlockComponentBuilder({ super.configuration, + this.alwaysDistributeColumnWidths = false, }); + final bool alwaysDistributeColumnWidths; + @override BlockComponentWidget build(BlockComponentContext blockComponentContext) { final node = blockComponentContext.node; @@ -30,11 +33,16 @@ class SimpleTableRowBlockComponentBuilder extends BlockComponentBuilder { key: node.key, node: node, configuration: configuration, + alwaysDistributeColumnWidths: alwaysDistributeColumnWidths, showActions: showActions(node), actionBuilder: (context, state) => actionBuilder( blockComponentContext, state, ), + actionTrailingBuilder: (context, state) => actionTrailingBuilder( + blockComponentContext, + state, + ), ); } @@ -48,9 +56,13 @@ class SimpleTableRowBlockWidget extends BlockComponentStatefulWidget { required super.node, super.showActions, super.actionBuilder, + super.actionTrailingBuilder, super.configuration = const BlockComponentConfiguration(), + required this.alwaysDistributeColumnWidths, }); + final bool alwaysDistributeColumnWidths; + @override State createState() => _SimpleTableRowBlockWidgetState(); @@ -95,7 +107,10 @@ class _SimpleTableRowBlockWidgetState extends State cells.add(const SimpleTableRowDivider()); } - cells.add(editorState.renderer.build(context, node.children[i])); + final child = editorState.renderer.build(context, node.children[i]); + cells.add( + widget.alwaysDistributeColumnWidths ? Flexible(child: child) : child, + ); // border if (SimpleTableConstants.borderType == diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_shortcuts/simple_table_enter_command.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_shortcuts/simple_table_enter_command.dart index 74bdc3fc62..bfc31e8abc 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_shortcuts/simple_table_enter_command.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_shortcuts/simple_table_enter_command.dart @@ -1,3 +1,4 @@ +import 'package:appflowy/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_cell_block_component.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_shortcuts/simple_table_command_extension.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; @@ -40,7 +41,9 @@ KeyEventResult _enterInTableCellHandler(EditorState editorState) { return KeyEventResult.handled; } } - return convertToParagraphCommand.execute(editorState); + if (node.type != CalloutBlockKeys.type) { + return convertToParagraphCommand.execute(editorState); + } } return KeyEventResult.ignored; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/_desktop_simple_table_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/_desktop_simple_table_widget.dart index 6e640e4561..187dbaf31d 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/_desktop_simple_table_widget.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/_desktop_simple_table_widget.dart @@ -13,6 +13,7 @@ class DesktopSimpleTableWidget extends StatefulWidget { this.enableAddColumnAndRowButton = true, this.enableHoverEffect = true, this.isFeedback = false, + this.alwaysDistributeColumnWidths = false, }); /// Refer to [SimpleTableWidget.node]. @@ -36,6 +37,9 @@ class DesktopSimpleTableWidget extends StatefulWidget { /// Refer to [SimpleTableWidget.isFeedback]. final bool isFeedback; + /// Refer to [SimpleTableWidget.alwaysDistributeColumnWidths]. + final bool alwaysDistributeColumnWidths; + @override State createState() => _DesktopSimpleTableWidgetState(); @@ -47,8 +51,16 @@ class _DesktopSimpleTableWidgetState extends State { final scrollController = ScrollController(); late final editorState = context.read(); + @override + void initState() { + super.initState(); + + simpleTableContext.horizontalScrollController = scrollController; + } + @override void dispose() { + simpleTableContext.horizontalScrollController = null; scrollController.dispose(); super.dispose(); @@ -76,27 +88,35 @@ class _DesktopSimpleTableWidgetState extends State { Widget _buildDesktopTable() { // table content - Widget child = Scrollbar( - controller: scrollController, - child: SingleChildScrollView( - controller: scrollController, - scrollDirection: Axis.horizontal, - child: Padding( - padding: SimpleTableConstants.tablePadding, - // IntrinsicWidth and IntrinsicHeight are used to make the table size fit the content. - child: IntrinsicWidth( - child: IntrinsicHeight( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: _buildRows(), - ), - ), - ), - ), + // IntrinsicHeight is used to make the table size fit the content. + Widget child = IntrinsicHeight( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: _buildRows(), ), ); + if (widget.alwaysDistributeColumnWidths) { + child = Padding( + padding: SimpleTableConstants.tablePadding, + child: child, + ); + } else { + child = Scrollbar( + controller: scrollController, + child: SingleChildScrollView( + controller: scrollController, + scrollDirection: Axis.horizontal, + child: Padding( + padding: SimpleTableConstants.tablePadding, + // IntrinsicWidth is used to make the table size fit the content. + child: IntrinsicWidth(child: child), + ), + ), + ); + } + if (widget.enableHoverEffect) { child = MouseRegion( onEnter: (event) => diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/_mobile_simple_table_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/_mobile_simple_table_widget.dart index 41ae29c61c..bf8720b88a 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/_mobile_simple_table_widget.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/_mobile_simple_table_widget.dart @@ -13,6 +13,7 @@ class MobileSimpleTableWidget extends StatefulWidget { this.enableAddColumnAndRowButton = true, this.enableHoverEffect = true, this.isFeedback = false, + this.alwaysDistributeColumnWidths = false, }); /// Refer to [SimpleTableWidget.node]. @@ -36,6 +37,9 @@ class MobileSimpleTableWidget extends StatefulWidget { /// Refer to [SimpleTableWidget.isFeedback]. final bool isFeedback; + /// Refer to [SimpleTableWidget.alwaysDistributeColumnWidths]. + final bool alwaysDistributeColumnWidths; + @override State createState() => _MobileSimpleTableWidgetState(); @@ -47,8 +51,16 @@ class _MobileSimpleTableWidgetState extends State { final scrollController = ScrollController(); late final editorState = context.read(); + @override + void initState() { + super.initState(); + + simpleTableContext.horizontalScrollController = scrollController; + } + @override void dispose() { + simpleTableContext.horizontalScrollController = null; scrollController.dispose(); super.dispose(); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/_simple_table_bottom_sheet_actions.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/_simple_table_bottom_sheet_actions.dart index 2d8b9025cf..b81ff89ee8 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/_simple_table_bottom_sheet_actions.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/_simple_table_bottom_sheet_actions.dart @@ -239,18 +239,20 @@ class SimpleTableInsertActions extends ISimpleTableBottomSheetActions { SimpleTableInsertAction( type: SimpleTableMoreAction.insertAbove, enableLeftBorder: true, - onTap: () => _onActionTap( + onTap: (increaseCounter) async => _onActionTap( context, - SimpleTableMoreAction.insertAbove, + type: SimpleTableMoreAction.insertAbove, + increaseCounter: increaseCounter, ), ), const HSpace(2), SimpleTableInsertAction( type: SimpleTableMoreAction.insertBelow, enableRightBorder: true, - onTap: () => _onActionTap( + onTap: (increaseCounter) async => _onActionTap( context, - SimpleTableMoreAction.insertBelow, + type: SimpleTableMoreAction.insertBelow, + increaseCounter: increaseCounter, ), ), ], @@ -260,18 +262,20 @@ class SimpleTableInsertActions extends ISimpleTableBottomSheetActions { SimpleTableInsertAction( type: SimpleTableMoreAction.insertLeft, enableLeftBorder: true, - onTap: () => _onActionTap( + onTap: (increaseCounter) async => _onActionTap( context, - SimpleTableMoreAction.insertLeft, + type: SimpleTableMoreAction.insertLeft, + increaseCounter: increaseCounter, ), ), const HSpace(2), SimpleTableInsertAction( type: SimpleTableMoreAction.insertRight, enableRightBorder: true, - onTap: () => _onActionTap( + onTap: (increaseCounter) async => _onActionTap( context, - SimpleTableMoreAction.insertRight, + type: SimpleTableMoreAction.insertRight, + increaseCounter: increaseCounter, ), ), ], @@ -279,7 +283,11 @@ class SimpleTableInsertActions extends ISimpleTableBottomSheetActions { }; } - void _onActionTap(BuildContext context, SimpleTableMoreAction type) { + Future _onActionTap( + BuildContext context, { + required SimpleTableMoreAction type, + required int increaseCounter, + }) async { final simpleTableContext = context.read(); final tableNode = cellNode.parentTableNode; if (tableNode == null) { @@ -291,34 +299,48 @@ class SimpleTableInsertActions extends ISimpleTableBottomSheetActions { case SimpleTableMoreAction.insertAbove: // update the highlight status for the selecting row simpleTableContext.selectingRow.value = cellNode.rowIndex + 1; - editorState.insertRowInTable( + await editorState.insertRowInTable( tableNode, cellNode.rowIndex, ); case SimpleTableMoreAction.insertBelow: - editorState.insertRowInTable( + await editorState.insertRowInTable( tableNode, cellNode.rowIndex + 1, ); + // scroll to the next cell position + editorState.scrollService?.scrollTo( + SimpleTableConstants.defaultRowHeight, + duration: Durations.short3, + ); case SimpleTableMoreAction.insertLeft: // update the highlight status for the selecting column simpleTableContext.selectingColumn.value = cellNode.columnIndex + 1; - editorState.insertColumnInTable( + await editorState.insertColumnInTable( tableNode, cellNode.columnIndex, ); case SimpleTableMoreAction.insertRight: - editorState.insertColumnInTable( + await editorState.insertColumnInTable( tableNode, cellNode.columnIndex + 1, ); + final horizontalScrollController = + simpleTableContext.horizontalScrollController; + if (horizontalScrollController != null) { + final previousWidth = horizontalScrollController.offset; + horizontalScrollController.jumpTo( + previousWidth + SimpleTableConstants.defaultColumnWidth, + ); + } + default: assert(false, 'Unsupported action: $type'); } } } -class SimpleTableInsertAction extends StatelessWidget { +class SimpleTableInsertAction extends StatefulWidget { const SimpleTableInsertAction({ super.key, required this.type, @@ -330,7 +352,16 @@ class SimpleTableInsertAction extends StatelessWidget { final SimpleTableMoreAction type; final bool enableLeftBorder; final bool enableRightBorder; - final void Function() onTap; + final ValueChanged onTap; + + @override + State createState() => + _SimpleTableInsertActionState(); +} + +class _SimpleTableInsertActionState extends State { + // used to count how many times the action is tapped + int increaseCounter = 0; @override Widget build(BuildContext context) { @@ -341,19 +372,19 @@ class SimpleTableInsertAction extends StatelessWidget { shape: _buildBorder(), ), child: AnimatedGestureDetector( - onTapUp: onTap, + onTapUp: () => widget.onTap(increaseCounter++), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Padding( padding: const EdgeInsets.all(1), child: FlowySvg( - type.leftIconSvg, + widget.type.leftIconSvg, size: const Size.square(22), ), ), FlowyText( - type.name, + widget.type.name, fontSize: 12, figmaLineHeight: 16, ), @@ -370,10 +401,10 @@ class SimpleTableInsertAction extends StatelessWidget { ); return RoundedRectangleBorder( borderRadius: BorderRadius.only( - topLeft: enableLeftBorder ? radius : Radius.zero, - bottomLeft: enableLeftBorder ? radius : Radius.zero, - topRight: enableRightBorder ? radius : Radius.zero, - bottomRight: enableRightBorder ? radius : Radius.zero, + topLeft: widget.enableLeftBorder ? radius : Radius.zero, + bottomLeft: widget.enableLeftBorder ? radius : Radius.zero, + topRight: widget.enableRightBorder ? radius : Radius.zero, + bottomRight: widget.enableRightBorder ? radius : Radius.zero, ), ); } @@ -591,8 +622,8 @@ class _SimpleTableHeaderActionButtonState fit: BoxFit.fill, child: CupertinoSwitch( value: value, - activeColor: Theme.of(context).colorScheme.primary, - onChanged: (_) {}, + activeTrackColor: Theme.of(context).colorScheme.primary, + onChanged: (_) => _toggle(), ), ), ); @@ -1198,19 +1229,12 @@ class SimpleTableQuickActions extends StatelessWidget { SimpleTableMoreAction.copy, ), ), - FutureBuilder( - future: getIt().getData(), - builder: (context, snapshot) { - final hasContent = snapshot.data?.tableJson != null; - return SimpleTableQuickAction( - type: SimpleTableMoreAction.paste, - isEnabled: hasContent, - onTap: () => _onActionTap( - context, - SimpleTableMoreAction.paste, - ), - ); - }, + SimpleTableQuickAction( + type: SimpleTableMoreAction.paste, + onTap: () => _onActionTap( + context, + SimpleTableMoreAction.paste, + ), ), SimpleTableQuickAction( type: SimpleTableMoreAction.delete, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_bottom_sheet.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_bottom_sheet.dart index 644aae1926..97519422ec 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_bottom_sheet.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_bottom_sheet.dart @@ -264,7 +264,7 @@ class _SimpleTableCellBottomSheetState } void _onTextColorSelected(Color color) { - final hex = color.alpha == 0 ? null : color.toHex(); + final hex = color.a == 0 ? null : color.toHex(); switch (widget.type) { case SimpleTableMoreActionType.column: widget.editorState.updateColumnTextColor( @@ -284,7 +284,7 @@ class _SimpleTableCellBottomSheetState } void _onCellBackgroundColorSelected(Color color) { - final hex = color.alpha == 0 ? null : color.toHex(); + final hex = color.a == 0 ? null : color.toHex(); switch (widget.type) { case SimpleTableMoreActionType.column: widget.editorState.updateColumnBackgroundColor( diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_column_resize_handle.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_column_resize_handle.dart index bfa1f316d7..ab941cb4d1 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_column_resize_handle.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_column_resize_handle.dart @@ -3,15 +3,19 @@ import 'dart:ui'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; +import 'package:universal_platform/universal_platform.dart'; class SimpleTableColumnResizeHandle extends StatefulWidget { const SimpleTableColumnResizeHandle({ super.key, required this.node, + this.isPreviousCell = false, }); final Node node; + final bool isPreviousCell; @override State createState() => @@ -24,8 +28,17 @@ class _SimpleTableColumnResizeHandleState bool isStartDragging = false; + // record the previous position of the drag, only used on mobile + double previousDx = 0; + @override Widget build(BuildContext context) { + return UniversalPlatform.isMobile + ? _buildMobileResizeHandle() + : _buildDesktopResizeHandle(); + } + + Widget _buildDesktopResizeHandle() { return MouseRegion( cursor: SystemMouseCursors.resizeColumn, onEnter: (_) => _onEnterHoverArea(), @@ -56,6 +69,40 @@ class _SimpleTableColumnResizeHandleState ); } + Widget _buildMobileResizeHandle() { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onLongPressStart: _onLongPressStart, + onLongPressMoveUpdate: _onLongPressMoveUpdate, + onLongPressEnd: _onLongPressEnd, + onLongPressCancel: _onLongPressCancel, + child: ValueListenableBuilder( + valueListenable: simpleTableContext.resizingCell, + builder: (context, resizingCell, child) { + final isSameColumnIndex = + widget.node.columnIndex == resizingCell?.columnIndex; + if (!isSameColumnIndex) { + return child!; + } + return Container( + width: 10, + alignment: !widget.isPreviousCell + ? Alignment.centerRight + : Alignment.centerLeft, + child: Container( + width: 2, + color: Theme.of(context).colorScheme.primary, + ), + ); + }, + child: Container( + width: 10, + color: Colors.transparent, + ), + ), + ); + } + void _onEnterHoverArea() { simpleTableContext.hoveringOnResizeHandle.value = widget.node; } @@ -79,6 +126,13 @@ class _SimpleTableColumnResizeHandleState isStartDragging = true; } + void _onLongPressStart(LongPressStartDetails details) { + isStartDragging = true; + simpleTableContext.resizingCell.value = widget.node; + + HapticFeedback.lightImpact(); + } + void _onHorizontalDragUpdate(DragUpdateDetails details) { if (!isStartDragging) { return; @@ -92,6 +146,21 @@ class _SimpleTableColumnResizeHandleState ); } + void _onLongPressMoveUpdate(LongPressMoveUpdateDetails details) { + if (!isStartDragging) { + return; + } + + // only update the column width in memory, + // the actual update will be applied in _onHorizontalDragEnd + context.read().updateColumnWidthInMemory( + tableCellNode: widget.node, + deltaX: details.offsetFromOrigin.dx - previousDx, + ); + + previousDx = details.offsetFromOrigin.dx; + } + void _onHorizontalDragEnd(DragEndDetails details) { if (!isStartDragging) { return; @@ -106,4 +175,27 @@ class _SimpleTableColumnResizeHandleState width: widget.node.columnWidth, ); } + + void _onLongPressEnd(LongPressEndDetails details) { + if (!isStartDragging) { + return; + } + + isStartDragging = false; + + // apply the updated column width + context.read().updateColumnWidth( + tableCellNode: widget.node, + width: widget.node.columnWidth, + ); + + previousDx = 0; + + simpleTableContext.resizingCell.value = null; + } + + void _onLongPressCancel() { + isStartDragging = false; + simpleTableContext.resizingCell.value = null; + } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_more_action_popup.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_more_action_popup.dart index d2e7340790..d4df194c05 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_more_action_popup.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_more_action_popup.dart @@ -31,12 +31,16 @@ class _SimpleTableMoreActionPopupState RenderBox? get renderBox => context.findRenderObject() as RenderBox?; + late final simpleTableContext = context.read(); + Node? tableNode; + Node? tableCellNode; + @override void initState() { super.initState(); - final tableCellNode = - context.read().hoveringTableCell.value; + tableCellNode = context.read().hoveringTableCell.value; + tableNode = tableCellNode?.parentTableNode; gestureInterceptor = SelectionGestureInterceptor( key: 'simple_table_more_action_popup_interceptor_${tableCellNode?.id}', canTap: (details) => !_isTapInBounds(details.globalPosition), @@ -59,10 +63,6 @@ class _SimpleTableMoreActionPopupState @override Widget build(BuildContext context) { - final simpleTableContext = context.read(); - final tableCellNode = simpleTableContext.hoveringTableCell.value; - final tableNode = tableCellNode?.parentTableNode; - if (tableNode == null) { return const SizedBox.shrink(); } @@ -70,6 +70,9 @@ class _SimpleTableMoreActionPopupState return AppFlowyPopover( onOpen: () => _onOpen(tableCellNode: tableCellNode), onClose: () => _onClose(), + canClose: () async { + return true; + }, direction: widget.type == SimpleTableMoreActionType.row ? PopoverDirection.bottomWithCenterAligned : PopoverDirection.bottomWithLeftAligned, @@ -81,7 +84,7 @@ class _SimpleTableMoreActionPopupState child: SimpleTableDraggableReorderButton( editorState: editorState, simpleTableContext: simpleTableContext, - node: tableNode, + node: tableNode!, index: widget.index, isShowingMenu: widget.isShowingMenu, type: widget.type, @@ -455,6 +458,21 @@ class _SimpleTableMoreActionItemState extends State { final columnIndex = node.columnIndex; final editorState = context.read(); editorState.insertColumnInTable(table, columnIndex); + + final cell = table.getTableCellNode( + rowIndex: 0, + columnIndex: columnIndex, + ); + if (cell == null) { + return; + } + + // update selection + editorState.selection = Selection.collapsed( + Position( + path: cell.path.child(0), + ), + ); } void _insertColumnRight() { @@ -466,6 +484,21 @@ class _SimpleTableMoreActionItemState extends State { final columnIndex = node.columnIndex; final editorState = context.read(); editorState.insertColumnInTable(table, columnIndex + 1); + + final cell = table.getTableCellNode( + rowIndex: 0, + columnIndex: columnIndex + 1, + ); + if (cell == null) { + return; + } + + // update selection + editorState.selection = Selection.collapsed( + Position( + path: cell.path.child(0), + ), + ); } void _insertRowAbove() { @@ -477,6 +510,18 @@ class _SimpleTableMoreActionItemState extends State { final rowIndex = node.rowIndex; final editorState = context.read(); editorState.insertRowInTable(table, rowIndex); + + final cell = table.getTableCellNode(rowIndex: rowIndex, columnIndex: 0); + if (cell == null) { + return; + } + + // update selection + editorState.selection = Selection.collapsed( + Position( + path: cell.path.child(0), + ), + ); } void _insertRowBelow() { @@ -488,6 +533,18 @@ class _SimpleTableMoreActionItemState extends State { final rowIndex = node.rowIndex; final editorState = context.read(); editorState.insertRowInTable(table, rowIndex + 1); + + final cell = table.getTableCellNode(rowIndex: rowIndex + 1, columnIndex: 0); + if (cell == null) { + return; + } + + // update selection + editorState.selection = Selection.collapsed( + Position( + path: cell.path.child(0), + ), + ); } void _deleteRow() { diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_widget.dart index 7f79082550..98d1e9f246 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_widget.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_widget.dart @@ -16,6 +16,7 @@ class SimpleTableWidget extends StatefulWidget { this.enableAddColumnAndRowButton = true, this.enableHoverEffect = true, this.isFeedback = false, + this.alwaysDistributeColumnWidths = false, }); /// The node of the table. @@ -49,6 +50,9 @@ class SimpleTableWidget extends StatefulWidget { /// Whether the widget is a feedback widget. final bool isFeedback; + /// Whether the columns should ignore their widths and fill available space + final bool alwaysDistributeColumnWidths; + @override State createState() => _SimpleTableWidgetState(); } @@ -65,6 +69,7 @@ class _SimpleTableWidgetState extends State { enableAddColumnAndRowButton: widget.enableAddColumnAndRowButton, enableHoverEffect: widget.enableHoverEffect, isFeedback: widget.isFeedback, + alwaysDistributeColumnWidths: widget.alwaysDistributeColumnWidths, ) : MobileSimpleTableWidget( simpleTableContext: widget.simpleTableContext, @@ -74,6 +79,7 @@ class _SimpleTableWidgetState extends State { enableAddColumnAndRowButton: widget.enableAddColumnAndRowButton, enableHoverEffect: widget.enableHoverEffect, isFeedback: widget.isFeedback, + alwaysDistributeColumnWidths: widget.alwaysDistributeColumnWidths, ); } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_command.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_command.dart index f8bf2a40a7..3d6fe113c1 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_command.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_command.dart @@ -1,8 +1,7 @@ -import 'dart:io'; - +import 'package:appflowy/mobile/presentation/selection_menu/mobile_selection_menu.dart'; +import 'package:appflowy/mobile/presentation/selection_menu/mobile_selection_menu_item_widget.dart'; import 'package:appflowy/plugins/document/presentation/editor_configuration.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:universal_platform/universal_platform.dart'; @@ -50,6 +49,7 @@ CharacterShortcutEvent customAppFlowySlashCommand({ } SelectionMenuService? _selectionMenuService; + Future _showSlashMenu( EditorState editorState, { required SlashMenuItemsBuilder itemsBuilder, @@ -59,10 +59,6 @@ Future _showSlashMenu( SelectionMenuStyle style = SelectionMenuStyle.light, required Set supportSlashMenuNodeTypes, }) async { - if (UniversalPlatform.isMobile) { - return false; - } - final selection = editorState.selection; if (selection == null) { return false; @@ -99,25 +95,35 @@ Future _showSlashMenu( final context = editorState.getNodeAtPath(selection.start.path)?.context; if (context != null && context.mounted) { - _selectionMenuService = SelectionMenu( - context: context, - editorState: editorState, - selectionMenuItems: items, - deleteSlashByDefault: shouldInsertSlash, - deleteKeywordsByDefault: deleteKeywordsByDefault, - singleColumn: singleColumn, - style: style, - ); + final isLight = Theme.of(context).brightness == Brightness.light; + _selectionMenuService?.dismiss(); + _selectionMenuService = UniversalPlatform.isMobile + ? MobileSelectionMenu( + context: context, + editorState: editorState, + selectionMenuItems: items, + deleteSlashByDefault: shouldInsertSlash, + deleteKeywordsByDefault: deleteKeywordsByDefault, + singleColumn: singleColumn, + style: isLight + ? MobileSelectionMenuStyle.light + : MobileSelectionMenuStyle.dark, + startOffset: editorState.selection?.start.offset ?? 0, + ) + : SelectionMenu( + context: context, + editorState: editorState, + selectionMenuItems: items, + deleteSlashByDefault: shouldInsertSlash, + deleteKeywordsByDefault: deleteKeywordsByDefault, + singleColumn: singleColumn, + style: style, + ); // disable the keyboard service editorState.service.keyboardService?.disable(); - if (!kIsWeb && Platform.environment.containsKey('FLUTTER_TEST')) { - await _selectionMenuService?.show(); - } else { - await _selectionMenuService?.show(); - } - + await _selectionMenuService?.show(); // enable the keyboard service editorState.service.keyboardService?.enable(); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/ai_writer_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/ai_writer_item.dart index 4e3f455522..46d1b4eabb 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/ai_writer_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/ai_writer_item.dart @@ -1,5 +1,5 @@ -import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_entities.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/base/selectable_svg_widget.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; @@ -15,41 +15,67 @@ final _keywords = [ 'autogenerator', ]; -// auto generate menu item SelectionMenuItem aiWriterSlashMenuItem = SelectionMenuItem( getName: LocaleKeys.document_slashMenu_name_aiWriter.tr, - keywords: _keywords, - handler: (editorState, _, __) async => editorState.insertAiWriter(), + keywords: [ + ..._keywords, + LocaleKeys.document_slashMenu_name_aiWriter.tr(), + ], + handler: (editorState, _, __) async => + _insertAiWriter(editorState, AiWriterCommand.userQuestion), icon: (_, isSelected, style) => SelectableSvgWidget( - data: FlowySvgs.slash_menu_icon_ai_writer_s, + data: AiWriterCommand.userQuestion.icon, isSelected: isSelected, style: style, ), nameBuilder: slashMenuItemNameBuilder, ); -extension on EditorState { - Future insertAiWriter() async { - final selection = this.selection; - if (selection == null || !selection.isCollapsed) { - return; - } - final node = getNodeAtPath(selection.end.path); - final delta = node?.delta; - if (node == null || delta == null) { - return; - } - final newNode = aiWriterNode(start: selection); +SelectionMenuItem continueWritingSlashMenuItem = SelectionMenuItem( + getName: LocaleKeys.document_plugins_aiWriter_continueWriting.tr, + keywords: [ + ..._keywords, + LocaleKeys.document_plugins_aiWriter_continueWriting.tr(), + ], + handler: (editorState, _, __) async => + _insertAiWriter(editorState, AiWriterCommand.continueWriting), + icon: (_, isSelected, style) => SelectableSvgWidget( + data: AiWriterCommand.continueWriting.icon, + isSelected: isSelected, + style: style, + ), + nameBuilder: slashMenuItemNameBuilder, +); - final transaction = this.transaction; - //default insert after - final path = node.path.next; - transaction - ..insertNode(path, newNode) - ..afterSelection = null; - await apply( - transaction, - options: const ApplyOptions(inMemoryUpdate: true), - ); +Future _insertAiWriter( + EditorState editorState, + AiWriterCommand action, +) async { + final selection = editorState.selection; + if (selection == null || !selection.isCollapsed) { + return; } + + final node = editorState.getNodeAtPath(selection.end.path); + if (node == null || node.delta == null) { + return; + } + final newNode = aiWriterNode( + selection: selection, + command: action, + ); + + // default insert after + final path = node.path.next; + final transaction = editorState.transaction + ..insertNode(path, newNode) + ..afterSelection = null; + + await editorState.apply( + transaction, + options: const ApplyOptions( + recordUndo: false, + inMemoryUpdate: true, + ), + ); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/date_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/date_item.dart index 0bb09d0a7e..04a8ee1b7e 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/date_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/date_item.dart @@ -46,12 +46,12 @@ extension on EditorState { selection.start.offset, 0, MentionBlockKeys.mentionChar, - attributes: { - MentionBlockKeys.mention: { - MentionBlockKeys.type: MentionType.date.name, - MentionBlockKeys.date: DateTime.now().toIso8601String(), - }, - }, + attributes: MentionBlockKeys.buildMentionDateAttributes( + date: DateTime.now().toIso8601String(), + reminderId: null, + reminderOption: null, + includeTime: false, + ), ); await apply(transaction); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/emoji_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/emoji_item.dart index df1c457cc2..890ba113cc 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/emoji_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/emoji_item.dart @@ -1,10 +1,13 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/base/selectable_svg_widget.dart'; -import 'package:appflowy/workspace/presentation/settings/widgets/emoji_picker/emoji_menu_item.dart'; +import 'package:appflowy/plugins/emoji/emoji_actions_command.dart'; +import 'package:appflowy/plugins/emoji/emoji_menu.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:universal_platform/universal_platform.dart'; import 'slash_menu_item_builder.dart'; @@ -37,11 +40,16 @@ extension on EditorState { }) async { final container = Overlay.of(context); menuService.dismiss(); - showEmojiPickerMenu( - container, - this, - menuService.alignment, - menuService.offset, - ); + if (UniversalPlatform.isMobile || selection == null) { + return; + } + + final node = getNodeAtPath(selection!.end.path); + final delta = node?.delta; + if (node == null || delta == null || node.type == CodeBlockKeys.type) { + return; + } + emojiMenuService = EmojiMenu(editorState: this, overlay: container); + emojiMenuService?.show(''); } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/image_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/image_item.dart index f0ce852e41..844b73c3e1 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/image_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/image_item.dart @@ -17,17 +17,20 @@ final _keywords = [ ]; /// Image menu item -final imageSlashMenuItem = SelectionMenuItem( - getName: () => LocaleKeys.document_slashMenu_name_image.tr(), - keywords: _keywords, - handler: (editorState, _, __) async => editorState.insertImageBlock(), - nameBuilder: slashMenuItemNameBuilder, - icon: (editorState, isSelected, style) => SelectableSvgWidget( - data: FlowySvgs.slash_menu_icon_image_s, - isSelected: isSelected, - style: style, - ), -); +final imageSlashMenuItem = buildImageSlashMenuItem(); + +SelectionMenuItem buildImageSlashMenuItem({FlowySvgData? svg}) => + SelectionMenuItem( + getName: () => LocaleKeys.document_slashMenu_name_image.tr(), + keywords: _keywords, + handler: (editorState, _, __) async => editorState.insertImageBlock(), + nameBuilder: slashMenuItemNameBuilder, + icon: (editorState, isSelected, style) => SelectableSvgWidget( + data: svg ?? FlowySvgs.slash_menu_icon_image_s, + isSelected: isSelected, + style: style, + ), + ); extension on EditorState { Future insertImageBlock() async { diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/mobile_items.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/mobile_items.dart new file mode 100644 index 0000000000..b71b54ad40 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/mobile_items.dart @@ -0,0 +1,131 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/mobile/presentation/selection_menu/mobile_selection_menu_item.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/base/selectable_svg_widget.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:easy_localization/easy_localization.dart'; + +import 'slash_menu_items.dart'; + +final List mobileItems = [ + textStyleMobileSlashMenuItem, + listMobileSlashMenuItem, + toggleListMobileSlashMenuItem, + fileAndMediaMobileSlashMenuItem, + mobileTableSlashMenuItem, + visualsMobileSlashMenuItem, + dateOrReminderSlashMenuItem, + buildSubpageSlashMenuItem(svg: FlowySvgs.type_page_m), + advancedMobileSlashMenuItem, +]; + +final List mobileItemsInTale = [ + textStyleMobileSlashMenuItem, + listMobileSlashMenuItem, + toggleListMobileSlashMenuItem, + fileAndMediaMobileSlashMenuItem, + visualsMobileSlashMenuItem, + dateOrReminderSlashMenuItem, + buildSubpageSlashMenuItem(svg: FlowySvgs.type_page_m), + advancedMobileSlashMenuItem, +]; + +SelectionMenuItemHandler _handler = (_, __, ___) {}; + +MobileSelectionMenuItem textStyleMobileSlashMenuItem = MobileSelectionMenuItem( + getName: LocaleKeys.document_slashMenu_name_textStyle.tr, + handler: _handler, + icon: (_, isSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_text_s, + isSelected: isSelected, + style: style, + ), + nameBuilder: slashMenuItemNameBuilder, + children: [ + paragraphSlashMenuItem, + heading1SlashMenuItem, + heading2SlashMenuItem, + heading3SlashMenuItem, + ], +); + +MobileSelectionMenuItem listMobileSlashMenuItem = MobileSelectionMenuItem( + getName: LocaleKeys.document_slashMenu_name_list.tr, + handler: _handler, + icon: (_, isSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_bulleted_list_s, + isSelected: isSelected, + style: style, + ), + nameBuilder: slashMenuItemNameBuilder, + children: [ + todoListSlashMenuItem, + bulletedListSlashMenuItem, + numberedListSlashMenuItem, + ], +); + +MobileSelectionMenuItem toggleListMobileSlashMenuItem = MobileSelectionMenuItem( + getName: LocaleKeys.document_slashMenu_name_toggle.tr, + handler: _handler, + icon: (_, isSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_toggle_s, + isSelected: isSelected, + style: style, + ), + nameBuilder: slashMenuItemNameBuilder, + children: [ + toggleListSlashMenuItem, + toggleHeading1SlashMenuItem, + toggleHeading2SlashMenuItem, + toggleHeading3SlashMenuItem, + ], +); + +MobileSelectionMenuItem fileAndMediaMobileSlashMenuItem = + MobileSelectionMenuItem( + getName: LocaleKeys.document_slashMenu_name_fileAndMedia.tr, + handler: _handler, + icon: (_, isSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_file_s, + isSelected: isSelected, + style: style, + ), + nameBuilder: slashMenuItemNameBuilder, + children: [ + buildImageSlashMenuItem(svg: FlowySvgs.slash_menu_image_m), + photoGallerySlashMenuItem, + fileSlashMenuItem, + ], +); + +MobileSelectionMenuItem visualsMobileSlashMenuItem = MobileSelectionMenuItem( + getName: LocaleKeys.document_slashMenu_name_visuals.tr, + handler: _handler, + icon: (_, isSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_visuals_s, + isSelected: isSelected, + style: style, + ), + nameBuilder: slashMenuItemNameBuilder, + children: [ + calloutSlashMenuItem, + dividerSlashMenuItem, + quoteSlashMenuItem, + ], +); + +MobileSelectionMenuItem advancedMobileSlashMenuItem = MobileSelectionMenuItem( + getName: LocaleKeys.document_slashMenu_name_advanced.tr, + handler: _handler, + icon: (_, isSelected, style) => SelectableSvgWidget( + data: FlowySvgs.drag_element_s, + isSelected: isSelected, + style: style, + ), + nameBuilder: slashMenuItemNameBuilder, + children: [ + codeBlockSlashMenuItem, + mathEquationSlashMenuItem, + ], +); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/simple_columns_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/simple_columns_item.dart new file mode 100644 index 0000000000..8609b76e70 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/simple_columns_item.dart @@ -0,0 +1,97 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/base/selectable_svg_widget.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/slash_menu_item_builder.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:easy_localization/easy_localization.dart'; + +final _baseKeywords = [ + 'columns', + 'column block', +]; + +final _twoColumnsKeywords = [ + ..._baseKeywords, + 'two columns', + '2 columns', +]; + +final _threeColumnsKeywords = [ + ..._baseKeywords, + 'three columns', + '3 columns', +]; + +final _fourColumnsKeywords = [ + ..._baseKeywords, + 'four columns', + '4 columns', +]; + +// 2 columns menu item +SelectionMenuItem twoColumnsSlashMenuItem = SelectionMenuItem.node( + getName: () => LocaleKeys.document_slashMenu_name_twoColumns.tr(), + keywords: _twoColumnsKeywords, + nodeBuilder: (editorState, __) => _buildColumnsNode(editorState, 2), + replace: (_, node) => node.delta?.isEmpty ?? false, + nameBuilder: slashMenuItemNameBuilder, + iconBuilder: (_, isSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_two_columns_s, + isSelected: isSelected, + style: style, + ), + updateSelection: (_, path, __, ___) { + return Selection.single( + path: path.child(0).child(0), + startOffset: 0, + ); + }, +); + +// 3 columns menu item +SelectionMenuItem threeColumnsSlashMenuItem = SelectionMenuItem.node( + getName: () => LocaleKeys.document_slashMenu_name_threeColumns.tr(), + keywords: _threeColumnsKeywords, + nodeBuilder: (editorState, __) => _buildColumnsNode(editorState, 3), + replace: (_, node) => node.delta?.isEmpty ?? false, + nameBuilder: slashMenuItemNameBuilder, + iconBuilder: (_, isSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_three_columns_s, + isSelected: isSelected, + style: style, + ), + updateSelection: (_, path, __, ___) { + return Selection.single( + path: path.child(0).child(0), + startOffset: 0, + ); + }, +); + +// 4 columns menu item +SelectionMenuItem fourColumnsSlashMenuItem = SelectionMenuItem.node( + getName: () => LocaleKeys.document_slashMenu_name_fourColumns.tr(), + keywords: _fourColumnsKeywords, + nodeBuilder: (editorState, __) => _buildColumnsNode(editorState, 4), + replace: (_, node) => node.delta?.isEmpty ?? false, + nameBuilder: slashMenuItemNameBuilder, + iconBuilder: (_, isSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_four_columns_s, + isSelected: isSelected, + style: style, + ), + updateSelection: (_, path, __, ___) { + return Selection.single( + path: path.child(0).child(0), + startOffset: 0, + ); + }, +); + +Node _buildColumnsNode(EditorState editorState, int columnCount) { + return simpleColumnsNode( + columnCount: columnCount, + ratio: 1.0 / columnCount, + ); +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/simple_table_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/simple_table_item.dart index 1b05d70091..9e16571d39 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/simple_table_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/simple_table_item.dart @@ -27,6 +27,18 @@ SelectionMenuItem tableSlashMenuItem = SelectionMenuItem( ), ); +SelectionMenuItem mobileTableSlashMenuItem = SelectionMenuItem( + getName: () => LocaleKeys.document_slashMenu_name_simpleTable.tr(), + keywords: _keywords, + handler: (editorState, _, __) async => editorState.insertSimpleTable(), + nameBuilder: slashMenuItemNameBuilder, + icon: (_, isSelected, style) => SelectableSvgWidget( + data: FlowySvgs.slash_menu_icon_simple_table_s, + isSelected: isSelected, + style: style, + ), +); + extension on EditorState { Future insertSimpleTable() async { final selection = this.selection; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/slash_menu_item_builder.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/slash_menu_item_builder.dart index 85f8a6895b..fada0addd9 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/slash_menu_item_builder.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/slash_menu_item_builder.dart @@ -3,6 +3,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/base/selec import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flutter/material.dart'; +import 'package:universal_platform/universal_platform.dart'; /// Builder function for the slash menu item. Widget slashMenuItemNameBuilder( @@ -49,9 +50,10 @@ class SlashMenuItemNameBuilder extends StatelessWidget { @override Widget build(BuildContext context) { + final isMobile = UniversalPlatform.isMobile; return FlowyText.regular( name, - fontSize: 12.0, + fontSize: isMobile ? 16.0 : 12.0, figmaLineHeight: 15.0, color: isSelected ? style.selectionMenuItemSelectedTextColor @@ -80,9 +82,11 @@ class SlashMenuIconBuilder extends StatelessWidget { @override Widget build(BuildContext context) { + final isMobile = UniversalPlatform.isMobile; return SelectableSvgWidget( data: data, isSelected: isSelected, + size: isMobile ? Size.square(20) : null, style: style, ); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/slash_menu_items.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/slash_menu_items.dart index ee76bc0ea3..27be8e4f03 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/slash_menu_items.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/slash_menu_items.dart @@ -15,6 +15,7 @@ export 'outline_item.dart'; export 'paragraph_item.dart'; export 'photo_gallery_item.dart'; export 'quote_item.dart'; +export 'simple_columns_item.dart'; export 'simple_table_item.dart'; export 'slash_menu_item_builder.dart'; export 'sub_page_item.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/sub_page_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/sub_page_item.dart index bc7f7e46b4..1052dbbe3e 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/sub_page_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/sub_page_item.dart @@ -22,26 +22,29 @@ final _keywords = [ ]; // Sub-page menu item -SelectionMenuItem subPageSlashMenuItem = SelectionMenuItem.node( - getName: () => LocaleKeys.document_slashMenu_subPage_name.tr(), - keywords: _keywords, - updateSelection: (editorState, path, __, ___) { - final context = editorState.document.root.context; - if (context != null) { - final isInDatabase = - context.read().isInDatabaseRowPage; - if (isInDatabase) { - Navigator.of(context).pop(); - } - } - return Selection.collapsed(Position(path: path)); - }, - replace: (_, node) => node.delta?.isEmpty ?? false, - nodeBuilder: (_, __) => subPageNode(), - nameBuilder: slashMenuItemNameBuilder, - iconBuilder: (_, isSelected, style) => SelectableSvgWidget( - data: FlowySvgs.insert_document_s, - isSelected: isSelected, - style: style, - ), -); +SelectionMenuItem subPageSlashMenuItem = buildSubpageSlashMenuItem(); + +SelectionMenuItem buildSubpageSlashMenuItem({FlowySvgData? svg}) => + SelectionMenuItem.node( + getName: () => LocaleKeys.document_slashMenu_subPage_name.tr(), + keywords: _keywords, + updateSelection: (editorState, path, __, ___) { + final context = editorState.document.root.context; + if (context != null) { + final isInDatabase = + context.read().isInDatabaseRowPage; + if (isInDatabase) { + Navigator.of(context).pop(); + } + } + return Selection.collapsed(Position(path: path)); + }, + replace: (_, node) => node.delta?.isEmpty ?? false, + nodeBuilder: (_, __) => subPageNode(), + nameBuilder: slashMenuItemNameBuilder, + iconBuilder: (_, isSelected, style) => SelectableSvgWidget( + data: svg ?? FlowySvgs.insert_document_s, + isSelected: isSelected, + style: style, + ), + ); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/todo_list_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/todo_list_item.dart index 74ffa7e075..518dccb35e 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/todo_list_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items/todo_list_item.dart @@ -15,7 +15,7 @@ final _keywords = [ ]; final todoListSlashMenuItem = SelectionMenuItem( - getName: () => LocaleKeys.document_slashMenu_name_todoList.tr(), + getName: () => LocaleKeys.editor_checkbox.tr(), keywords: _keywords, handler: (editorState, _, __) async => insertCheckboxAfterSelection( editorState, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items_builder.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items_builder.dart index 3e9bdef355..137f592902 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items_builder.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/slash_menu/slash_menu_items_builder.dart @@ -1,7 +1,11 @@ import 'package:appflowy/plugins/document/application/document_bloc.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_node_extension.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:universal_platform/universal_platform.dart'; +import 'slash_menu_items/mobile_items.dart'; import 'slash_menu_items/slash_menu_items.dart'; /// Build slash menu items @@ -11,16 +15,32 @@ List slashMenuItemsBuilder({ DocumentBloc? documentBloc, EditorState? editorState, Node? node, + ViewPB? view, }) { final isInTable = node != null && node.parentTableCellNode != null; - - if (isInTable) { - return _simpleTableSlashMenuItems(); + final isMobile = UniversalPlatform.isMobile; + bool isEmpty = false; + if (editorState == null || editorState.isEmptyForContinueWriting()) { + if (view == null || view.name.isEmpty) { + isEmpty = true; + } + } + if (isMobile) { + if (isInTable) { + return mobileItemsInTale; + } else { + return mobileItems; + } } else { - return _defaultSlashMenuItems( - isLocalMode: isLocalMode, - documentBloc: documentBloc, - ); + if (isInTable) { + return _simpleTableSlashMenuItems(); + } else { + return _defaultSlashMenuItems( + isLocalMode: isLocalMode, + documentBloc: documentBloc, + isEmpty: isEmpty, + ); + } } } @@ -38,10 +58,14 @@ List slashMenuItemsBuilder({ List _defaultSlashMenuItems({ bool isLocalMode = false, DocumentBloc? documentBloc, + bool isEmpty = false, }) { return [ - // disable ai writer in local mode - if (!isLocalMode) aiWriterSlashMenuItem, + // ai + if (!isLocalMode) ...[ + if (!isEmpty) continueWritingSlashMenuItem, + aiWriterSlashMenuItem, + ], paragraphSlashMenuItem, @@ -70,6 +94,12 @@ List _defaultSlashMenuItems({ // link to page linkToPageSlashMenuItem, + // columns + // 2-4 columns + twoColumnsSlashMenuItem, + threeColumnsSlashMenuItem, + fourColumnsSlashMenuItem, + // grid if (documentBloc != null) gridSlashMenuItem(documentBloc), referencedGridSlashMenuItem, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/sub_page/block_transaction_handler.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/sub_page/block_transaction_handler.dart index 35ff1f219b..a549e87f83 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/sub_page/block_transaction_handler.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/sub_page/block_transaction_handler.dart @@ -1,5 +1,3 @@ -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/application/mobile_router.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/block_transaction_handler/block_transaction_handler.dart'; @@ -14,6 +12,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pbenum.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/style_widget/snap_bar.dart'; +import 'package:flutter/material.dart'; import 'package:universal_platform/universal_platform.dart'; class SubPageBlockTransactionHandler extends BlockTransactionHandler { @@ -162,7 +161,9 @@ class SubPageBlockTransactionHandler extends BlockTransactionHandler { if (UniversalPlatform.isDesktop) { getIt().openPlugin(view); } else { - await context.pushView(view); + if (context.mounted) { + await context.pushView(view); + } } }); }, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/sub_page/sub_page_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/sub_page/sub_page_block_component.dart index 4b8550570a..0ce2b74a74 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/sub_page/sub_page_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/sub_page/sub_page_block_component.dart @@ -1,8 +1,10 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/application/mobile_router.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/shared_context/shared_context.dart'; import 'package:appflowy/plugins/trash/application/trash_listener.dart'; +import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; @@ -15,7 +17,6 @@ import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_result/appflowy_result.dart'; import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flutter/material.dart'; @@ -72,6 +73,7 @@ class SubPageBlockComponent extends BlockComponentStatefulWidget { required super.node, super.showActions, super.actionBuilder, + super.actionTrailingBuilder, super.configuration = const BlockComponentConfiguration(), }); @@ -200,6 +202,8 @@ class SubPageBlockComponentState extends State return const SizedBox.shrink(); } + final textStyle = textStyleWithTextSpan(); + Widget child = Padding( padding: const EdgeInsets.symmetric(vertical: 2), child: MouseRegion( @@ -242,12 +246,9 @@ class SubPageBlockComponentState extends State children: [ const HSpace(10), view.icon.value.isNotEmpty - ? FlowyText.emoji( - view.icon.value, - fontSize: textStyle.fontSize, - lineHeight: textStyle.height, - color: - AFThemeExtension.of(context).strongText, + ? RawEmojiIconWidget( + emoji: view.icon.toEmojiIconData(), + emojiSize: textStyle.fontSize ?? 16.0, ) : view.defaultIcon(), const HSpace(6), @@ -295,6 +296,14 @@ class SubPageBlockComponentState extends State child = BlockComponentActionWrapper( node: node, actionBuilder: widget.actionBuilder!, + actionTrailingBuilder: widget.actionTrailingBuilder, + child: child, + ); + } + + if (UniversalPlatform.isMobile) { + child = Padding( + padding: padding, child: child, ); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toggle/toggle_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toggle/toggle_block_component.dart index 9f26f7037f..9e93f80ce4 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toggle/toggle_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toggle/toggle_block_component.dart @@ -1,8 +1,10 @@ import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/database/tab_bar/tab_bar_view.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:easy_localization/easy_localization.dart' hide TextDirection; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; import 'package:universal_platform/universal_platform.dart'; class ToggleListBlockKeys { @@ -109,6 +111,10 @@ class ToggleListBlockComponentBuilder extends BlockComponentBuilder { blockComponentContext, state, ), + actionTrailingBuilder: (context, state) => actionTrailingBuilder( + blockComponentContext, + state, + ), ); } @@ -122,6 +128,7 @@ class ToggleListBlockComponentWidget extends BlockComponentStatefulWidget { required super.node, super.showActions, super.actionBuilder, + super.actionTrailingBuilder, super.configuration = const BlockComponentConfiguration(), this.padding = const EdgeInsets.all(0), this.textStyleBuilder, @@ -172,6 +179,7 @@ class _ToggleListBlockComponentWidgetState ); bool get collapsed => node.attributes[ToggleListBlockKeys.collapsed] ?? false; + int? get level => node.attributes[ToggleListBlockKeys.level] as int?; @override @@ -194,12 +202,16 @@ class _ToggleListBlockComponentWidgetState color: backgroundColor, ), ), - NestedListWidget( - indentPadding: indentPadding, - child: buildComponent(context), - children: editorState.renderer.buildList( - context, - widget.node.children, + Provider( + create: (context) => + DatabasePluginWidgetBuilderSize(horizontalPadding: 0.0), + child: NestedListWidget( + indentPadding: indentPadding, + child: buildComponent(context), + children: editorState.renderer.buildList( + context, + widget.node.children, + ), ), ), ], @@ -240,6 +252,7 @@ class _ToggleListBlockComponentWidgetState child = BlockComponentActionWrapper( node: node, actionBuilder: widget.actionBuilder!, + actionTrailingBuilder: widget.actionTrailingBuilder, child: child, ); } @@ -287,7 +300,9 @@ class _ToggleListBlockComponentWidgetState } return Padding( - padding: indentPadding, + padding: UniversalPlatform.isMobile + ? const EdgeInsets.symmetric(horizontal: 26.0) + : indentPadding, child: FlowyButton( text: FlowyText( buildPlaceholderText(), @@ -312,7 +327,9 @@ class _ToggleListBlockComponentWidgetState placeholderText: placeholderText, lineHeight: 1.5, textSpanDecorator: (textSpan) { - var result = textSpan.updateTextStyle(textStyle); + var result = textSpan.updateTextStyle( + textStyleWithTextSpan(textSpan: textSpan), + ); if (level != null) { result = result.updateTextStyle( widget.textStyleBuilder?.call(level), @@ -321,13 +338,17 @@ class _ToggleListBlockComponentWidgetState return result; }, placeholderTextSpanDecorator: (textSpan) { - var result = textSpan.updateTextStyle(textStyle); + var result = textSpan.updateTextStyle( + textStyleWithTextSpan(textSpan: textSpan), + ); if (level != null && widget.textStyleBuilder != null) { result = result.updateTextStyle( widget.textStyleBuilder?.call(level), ); } - return result.updateTextStyle(placeholderTextStyle); + return result.updateTextStyle( + placeholderTextStyleWithTextSpan(textSpan: textSpan), + ); }, textDirection: textDirection, textAlign: alignment?.toTextAlign ?? textAlign, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toggle/toggle_block_shortcuts.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toggle/toggle_block_shortcuts.dart index 212be0f7bf..f3059bf1be 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toggle/toggle_block_shortcuts.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toggle/toggle_block_shortcuts.dart @@ -47,15 +47,12 @@ Future _formatGreaterToToggleHeading( delta = delta.compose(Delta()..delete(_greater.length)); // if the previous block is heading block, convert it to toggle heading block if (type == HeadingBlockKeys.type && level != null) { - final cubit = BlockActionOptionCubit( - editorState: editorState, - blockComponentBuilder: {}, - ); - await cubit.turnIntoSingleToggleHeading( + await BlockActionOptionCubit.turnIntoSingleToggleHeading( type: ToggleListBlockKeys.type, selectedNodes: [node], level: level, delta: delta, + editorState: editorState, afterSelection: afterSelection, ); return; @@ -98,7 +95,8 @@ CharacterShortcutEvent insertChildNodeInsideToggleList = CharacterShortcutEvent( } final slicedDelta = delta.slice(selection.start.offset); final transaction = editorState.transaction; - final collapsed = node.attributes[ToggleListBlockKeys.collapsed] as bool; + final bool collapsed = + node.attributes[ToggleListBlockKeys.collapsed] ?? false; if (collapsed) { // if the delta is empty, clear the format if (delta.isEmpty) { diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toolbar_item/custom_format_toolbar_items.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toolbar_item/custom_format_toolbar_items.dart new file mode 100644 index 0000000000..d4f3d21f46 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toolbar_item/custom_format_toolbar_items.dart @@ -0,0 +1,135 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_page.dart'; +import 'package:appflowy/plugins/document/presentation/editor_style.dart'; +import 'package:appflowy/util/theme_extension.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +// ignore: implementation_imports +import 'package:appflowy_editor/src/editor/toolbar/desktop/items/utils/tooltip_util.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/style_widget/icon_button.dart'; +import 'package:flutter/material.dart'; + +import 'custom_placeholder_toolbar_item.dart'; +import 'toolbar_id_enum.dart'; + +final List customMarkdownFormatItems = [ + _FormatToolbarItem( + id: ToolbarId.bold, + name: 'bold', + svg: FlowySvgs.toolbar_bold_m, + ), + group1PaddingItem, + _FormatToolbarItem( + id: ToolbarId.underline, + name: 'underline', + svg: FlowySvgs.toolbar_underline_m, + ), + group1PaddingItem, + _FormatToolbarItem( + id: ToolbarId.italic, + name: 'italic', + svg: FlowySvgs.toolbar_inline_italic_m, + ), +]; + +final ToolbarItem customInlineCodeItem = _FormatToolbarItem( + id: ToolbarId.code, + name: 'code', + svg: FlowySvgs.toolbar_inline_code_m, + group: 2, +); + +class _FormatToolbarItem extends ToolbarItem { + _FormatToolbarItem({ + required ToolbarId id, + required String name, + required FlowySvgData svg, + super.group = 1, + }) : super( + id: id.id, + isActive: showInAnyTextType, + builder: ( + context, + editorState, + highlightColor, + iconColor, + tooltipBuilder, + ) { + final selection = editorState.selection!; + final nodes = editorState.getNodesInSelection(selection); + final isHighlight = nodes.allSatisfyInSelection( + selection, + (delta) => + delta.isNotEmpty && + delta.everyAttributes((attr) => attr[name] == true), + ); + + final hoverColor = isHighlight + ? highlightColor + : EditorStyleCustomizer.toolbarHoverColor(context); + final isDark = !Theme.of(context).isLightMode; + final theme = AppFlowyTheme.of(context); + + final child = FlowyIconButton( + width: 36, + height: 32, + hoverColor: hoverColor, + isSelected: isHighlight, + icon: FlowySvg( + svg, + size: Size.square(20.0), + color: (isDark && isHighlight) + ? Color(0xFF282E3A) + : theme.iconColorScheme.primary, + ), + onPressed: () => editorState.toggleAttribute( + name, + selection: selection, + ), + ); + + if (tooltipBuilder != null) { + return tooltipBuilder( + context, + id.id, + _getTooltipText(id), + child, + ); + } + return child; + }, + ); +} + +String _getTooltipText(ToolbarId id) { + switch (id) { + case ToolbarId.underline: + return '${LocaleKeys.toolbar_underline.tr()}${shortcutTooltips( + '⌘ + U', + 'CTRL + U', + 'CTRL + U', + )}'; + case ToolbarId.bold: + return '${LocaleKeys.toolbar_bold.tr()}${shortcutTooltips( + '⌘ + B', + 'CTRL + B', + 'CTRL + B', + )}'; + case ToolbarId.italic: + return '${LocaleKeys.toolbar_italic.tr()}${shortcutTooltips( + '⌘ + I', + 'CTRL + I', + 'CTRL + I', + )}'; + case ToolbarId.code: + return '${LocaleKeys.document_toolbar_inlineCode.tr()}${shortcutTooltips( + '⌘ + E', + 'CTRL + E', + 'CTRL + E', + )}'; + default: + return ''; + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toolbar_item/custom_hightlight_color_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toolbar_item/custom_hightlight_color_toolbar_item.dart new file mode 100644 index 0000000000..46f2c02c5a --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toolbar_item/custom_hightlight_color_toolbar_item.dart @@ -0,0 +1,227 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_page.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/color_picker.dart'; +import 'package:appflowy/plugins/document/presentation/editor_style.dart'; +import 'package:appflowy_editor/appflowy_editor.dart' hide ColorPicker; +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; + +import 'toolbar_id_enum.dart'; + +String? _customHighlightColorHex; + +final customHighlightColorItem = ToolbarItem( + id: ToolbarId.highlightColor.id, + group: 1, + isActive: showInAnyTextType, + builder: (context, editorState, highlightColor, iconColor, tooltipBuilder) => + HighlightColorPickerWidget( + editorState: editorState, + tooltipBuilder: tooltipBuilder, + highlightColor: highlightColor, + ), +); + +class HighlightColorPickerWidget extends StatefulWidget { + const HighlightColorPickerWidget({ + super.key, + required this.editorState, + this.tooltipBuilder, + required this.highlightColor, + }); + + final EditorState editorState; + final ToolbarTooltipBuilder? tooltipBuilder; + final Color highlightColor; + + @override + State createState() => + _HighlightColorPickerWidgetState(); +} + +class _HighlightColorPickerWidgetState + extends State { + final popoverController = PopoverController(); + + bool isSelected = false; + + EditorState get editorState => widget.editorState; + + Color get highlightColor => widget.highlightColor; + + @override + void dispose() { + super.dispose(); + popoverController.close(); + } + + @override + Widget build(BuildContext context) { + if (editorState.selection == null) { + return const SizedBox.shrink(); + } + final selectionRectList = editorState.selectionRects(); + final top = + selectionRectList.isEmpty ? 0.0 : selectionRectList.first.height; + return AppFlowyPopover( + controller: popoverController, + direction: PopoverDirection.bottomWithLeftAligned, + offset: Offset(0, top), + onOpen: () => keepEditorFocusNotifier.increase(), + onClose: () { + setState(() { + isSelected = false; + }); + keepEditorFocusNotifier.decrease(); + }, + margin: EdgeInsets.zero, + popupBuilder: (context) => buildPopoverContent(), + child: buildChild(context), + ); + } + + Widget buildChild(BuildContext context) { + final theme = AppFlowyTheme.of(context), + iconColor = theme.iconColorScheme.primary; + + final child = FlowyIconButton( + width: 36, + height: 32, + hoverColor: EditorStyleCustomizer.toolbarHoverColor(context), + icon: SizedBox( + width: 20, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + FlowySvg( + FlowySvgs.toolbar_text_highlight_m, + size: Size(20, 16), + color: iconColor, + ), + buildColorfulDivider(iconColor), + ], + ), + ), + onPressed: () { + setState(() { + isSelected = true; + }); + showPopover(); + }, + ); + + return widget.tooltipBuilder?.call( + context, + ToolbarId.highlightColor.id, + AppFlowyEditorL10n.current.highlightColor, + child, + ) ?? + child; + } + + Widget buildColorfulDivider(Color? iconColor) { + final List colors = []; + final selection = editorState.selection!; + final nodes = editorState.getNodesInSelection(selection); + final isHighLight = nodes.allSatisfyInSelection(selection, (delta) { + if (delta.everyAttributes((attr) => attr.isEmpty)) { + return false; + } + + return delta.everyAttributes((attr) { + final textColorHex = attr[AppFlowyRichTextKeys.backgroundColor]; + if (textColorHex != null) colors.add(textColorHex); + return (textColorHex != null); + }); + }); + + final colorLength = colors.length; + if (colors.isEmpty || !isHighLight) { + return Container( + width: 20, + height: 4, + color: iconColor, + ); + } + return SizedBox( + width: 20, + height: 4, + child: Row( + children: List.generate(colorLength, (index) { + final currentColor = int.tryParse(colors[index]); + return Container( + width: 20 / colorLength, + height: 4, + color: currentColor == null ? iconColor : Color(currentColor), + ); + }), + ), + ); + } + + Widget buildPopoverContent() { + final List colors = []; + + final selection = editorState.selection!; + final nodes = editorState.getNodesInSelection(selection); + final isHighlight = nodes.allSatisfyInSelection(selection, (delta) { + if (delta.everyAttributes((attr) => attr.isEmpty)) { + return false; + } + + return delta.everyAttributes((attributes) { + final highlightColorHex = + attributes[AppFlowyRichTextKeys.backgroundColor]; + if (highlightColorHex != null) colors.add(highlightColorHex); + return highlightColorHex != null; + }); + }); + bool showClearButton = false; + nodes.allSatisfyInSelection(selection, (delta) { + if (!showClearButton) { + showClearButton = delta.whereType().any( + (element) { + return element.attributes?[AppFlowyRichTextKeys.backgroundColor] != + null; + }, + ); + } + return true; + }); + return MouseRegion( + child: ColorPicker( + title: AppFlowyEditorL10n.current.highlightColor, + showClearButton: showClearButton, + selectedColorHex: + (colors.length == 1 && isHighlight) ? colors.first : null, + customColorHex: _customHighlightColorHex, + colorOptions: generateHighlightColorOptions(), + onSubmittedColorHex: (color, isCustomColor) { + if (isCustomColor) { + _customHighlightColorHex = color; + } + formatHighlightColor( + editorState, + editorState.selection, + color, + withUpdateSelection: true, + ); + hidePopover(); + }, + resetText: AppFlowyEditorL10n.current.clearHighlightColor, + resetIconName: 'clear_highlight_color', + ), + ); + } + + void showPopover() { + keepEditorFocusNotifier.increase(); + popoverController.show(); + } + + void hidePopover() { + popoverController.close(); + keepEditorFocusNotifier.decrease(); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toolbar_item/custom_link_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toolbar_item/custom_link_toolbar_item.dart new file mode 100644 index 0000000000..8c9e6b69da --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toolbar_item/custom_link_toolbar_item.dart @@ -0,0 +1,96 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/plugins/document/application/document_bloc.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/base/toolbar_extension.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/desktop_floating_toolbar.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_create_menu.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_hover_menu.dart'; +import 'package:appflowy/plugins/document/presentation/editor_style.dart'; +import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/util/theme_extension.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:flowy_infra_ui/style_widget/icon_button.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'toolbar_id_enum.dart'; + +const kIsPageLink = 'is_page_link'; + +final customLinkItem = ToolbarItem( + id: ToolbarId.link.id, + group: 4, + isActive: (state) => + !isNarrowWindow(state) && onlyShowInSingleSelectionAndTextType(state), + builder: (context, editorState, highlightColor, iconColor, tooltipBuilder) { + final selection = editorState.selection!; + final nodes = editorState.getNodesInSelection(selection); + final isHref = nodes.allSatisfyInSelection(selection, (delta) { + return delta.everyAttributes( + (attributes) => attributes[AppFlowyRichTextKeys.href] != null, + ); + }); + + final isDark = !Theme.of(context).isLightMode; + final hoverColor = isHref + ? highlightColor + : EditorStyleCustomizer.toolbarHoverColor(context); + final theme = AppFlowyTheme.of(context); + final child = FlowyIconButton( + width: 36, + height: 32, + hoverColor: hoverColor, + isSelected: isHref, + icon: FlowySvg( + FlowySvgs.toolbar_link_m, + size: Size.square(20.0), + color: (isDark && isHref) + ? Color(0xFF282E3A) + : theme.iconColorScheme.primary, + ), + onPressed: () { + getIt().hideToolbar(); + if (!isHref) { + final viewId = context.read()?.documentId ?? ''; + showLinkCreateMenu(context, editorState, selection, viewId); + } else { + WidgetsBinding.instance.addPostFrameCallback((_) { + getIt() + .call(HoverTriggerKey(nodes.first.id, selection)); + }); + } + }, + ); + + if (tooltipBuilder != null) { + return tooltipBuilder( + context, + ToolbarId.highlightColor.id, + AppFlowyEditorL10n.current.link, + child, + ); + } + + return child; + }, +); + +extension AttributeExtension on Attributes { + bool get isPage { + if (this[kIsPageLink] is bool) { + return this[kIsPageLink]; + } + return false; + } +} + +enum LinkMenuAlignment { + topLeft, + topRight, + bottomLeft, + bottomRight, +} + +extension LinkMenuAlignmentExtension on LinkMenuAlignment { + bool get isTop => + this == LinkMenuAlignment.topLeft || this == LinkMenuAlignment.topRight; +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toolbar_item/custom_placeholder_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toolbar_item/custom_placeholder_toolbar_item.dart new file mode 100644 index 0000000000..e087731c82 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toolbar_item/custom_placeholder_toolbar_item.dart @@ -0,0 +1,49 @@ +import 'package:appflowy/plugins/document/presentation/editor_page.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/ai/ai_writer_toolbar_item.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/base/toolbar_extension.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; + +import 'toolbar_id_enum.dart'; + +final ToolbarItem customPlaceholderItem = ToolbarItem( + id: ToolbarId.placeholder.id, + group: -1, + isActive: (editorState) => true, + builder: (context, __, ___, ____, _____) { + final isDark = Theme.of(context).brightness == Brightness.dark; + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 5), + child: Container( + width: 1, + color: Color(0xffE8ECF3).withAlpha(isDark ? 40 : 255), + ), + ); + }, +); + +ToolbarItem buildPaddingPlaceholderItem( + int group, { + bool Function(EditorState editorState)? isActive, +}) => + ToolbarItem( + id: ToolbarId.paddingPlaceHolder.id, + group: group, + isActive: isActive, + builder: (context, __, ___, ____, _____) => HSpace(4), + ); + +ToolbarItem group0PaddingItem = buildPaddingPlaceholderItem( + 0, + isActive: onlyShowInTextTypeAndExcludeTable, +); + +ToolbarItem group1PaddingItem = + buildPaddingPlaceholderItem(1, isActive: showInAnyTextType); + +ToolbarItem group4PaddingItem = buildPaddingPlaceholderItem( + 4, + isActive: (state) => + !isNarrowWindow(state) && onlyShowInSingleSelectionAndTextType(state), +); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toolbar_item/custom_text_align_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toolbar_item/custom_text_align_toolbar_item.dart new file mode 100644 index 0000000000..efaff532f4 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toolbar_item/custom_text_align_toolbar_item.dart @@ -0,0 +1,215 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/base/toolbar_extension.dart'; +import 'package:appflowy/plugins/document/presentation/editor_style.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; + +import 'toolbar_id_enum.dart'; + +final ToolbarItem customTextAlignItem = ToolbarItem( + id: ToolbarId.textAlign.id, + group: 4, + isActive: (state) => + !isNarrowWindow(state) && onlyShowInSingleSelectionAndTextType(state), + builder: ( + context, + editorState, + highlightColor, + iconColor, + tooltipBuilder, + ) { + return TextAlignActionList( + editorState: editorState, + tooltipBuilder: tooltipBuilder, + highlightColor: highlightColor, + ); + }, +); + +class TextAlignActionList extends StatefulWidget { + const TextAlignActionList({ + super.key, + required this.editorState, + required this.highlightColor, + this.tooltipBuilder, + this.child, + this.onSelect, + this.popoverController, + this.popoverDirection = PopoverDirection.bottomWithLeftAligned, + this.showOffset = const Offset(0, 2), + }); + + final EditorState editorState; + final ToolbarTooltipBuilder? tooltipBuilder; + final Color highlightColor; + final Widget? child; + final VoidCallback? onSelect; + final PopoverController? popoverController; + final PopoverDirection popoverDirection; + final Offset showOffset; + + @override + State createState() => _TextAlignActionListState(); +} + +class _TextAlignActionListState extends State { + late PopoverController popoverController = + widget.popoverController ?? PopoverController(); + + bool isSelected = false; + + EditorState get editorState => widget.editorState; + + Color get highlightColor => widget.highlightColor; + + @override + void dispose() { + super.dispose(); + popoverController.close(); + } + + @override + Widget build(BuildContext context) { + return AppFlowyPopover( + controller: popoverController, + direction: widget.popoverDirection, + offset: widget.showOffset, + onOpen: () => keepEditorFocusNotifier.increase(), + onClose: () { + setState(() { + isSelected = false; + }); + keepEditorFocusNotifier.decrease(); + }, + popupBuilder: (context) => buildPopoverContent(), + child: widget.child ?? buildChild(context), + ); + } + + void showPopover() { + keepEditorFocusNotifier.increase(); + popoverController.show(); + } + + Widget buildChild(BuildContext context) { + final theme = AppFlowyTheme.of(context), + iconColor = theme.iconColorScheme.primary; + final child = FlowyIconButton( + width: 48, + height: 32, + isSelected: isSelected, + hoverColor: EditorStyleCustomizer.toolbarHoverColor(context), + icon: Row( + mainAxisSize: MainAxisSize.min, + children: [ + FlowySvg( + FlowySvgs.toolbar_alignment_m, + size: Size.square(20), + color: iconColor, + ), + HSpace(4), + FlowySvg( + FlowySvgs.toolbar_arrow_down_m, + size: Size(12, 20), + color: iconColor, + ), + ], + ), + onPressed: () { + setState(() { + isSelected = true; + }); + showPopover(); + }, + ); + + return widget.tooltipBuilder?.call( + context, + ToolbarId.textAlign.id, + LocaleKeys.document_toolbar_textAlign.tr(), + child, + ) ?? + child; + } + + Widget buildPopoverContent() { + return MouseRegion( + child: SeparatedColumn( + mainAxisSize: MainAxisSize.min, + separatorBuilder: () => const VSpace(4.0), + children: List.generate(TextAlignCommand.values.length, (index) { + final command = TextAlignCommand.values[index]; + final selection = editorState.selection!; + final nodes = editorState.getNodesInSelection(selection); + final isHighlight = nodes.every( + (n) => n.attributes[blockComponentAlign] == command.name, + ); + + return SizedBox( + height: 36, + child: FlowyButton( + leftIconSize: const Size.square(20), + leftIcon: FlowySvg(command.svg), + iconPadding: 12, + text: FlowyText( + command.title, + fontWeight: FontWeight.w400, + figmaLineHeight: 20, + ), + rightIcon: + isHighlight ? FlowySvg(FlowySvgs.toolbar_check_m) : null, + onTap: () { + command.onAlignChanged(editorState); + widget.onSelect?.call(); + popoverController.close(); + }, + ), + ); + }), + ), + ); + } +} + +enum TextAlignCommand { + left(FlowySvgs.toolbar_text_align_left_m), + center(FlowySvgs.toolbar_text_align_center_m), + right(FlowySvgs.toolbar_text_align_right_m); + + const TextAlignCommand(this.svg); + + final FlowySvgData svg; + + String get title { + switch (this) { + case left: + return LocaleKeys.document_toolbar_alignLeft.tr(); + case center: + return LocaleKeys.document_toolbar_alignCenter.tr(); + case right: + return LocaleKeys.document_toolbar_alignRight.tr(); + } + } + + Future onAlignChanged(EditorState editorState) async { + final selection = editorState.selection!; + + await editorState.updateNode( + selection, + (node) => node.copyWith( + attributes: { + ...node.attributes, + blockComponentAlign: name, + }, + ), + selectionExtraInfo: { + selectionExtraInfoDoNotAttachTextService: true, + selectionExtraInfoDisableFloatingToolbar: true, + }, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toolbar_item/custom_text_color_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toolbar_item/custom_text_color_toolbar_item.dart new file mode 100644 index 0000000000..9f5a917b89 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toolbar_item/custom_text_color_toolbar_item.dart @@ -0,0 +1,226 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_page.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/color_picker.dart'; +import 'package:appflowy/plugins/document/presentation/editor_style.dart'; +import 'package:appflowy_editor/appflowy_editor.dart' hide ColorPicker; +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'toolbar_id_enum.dart'; + +String? _customColorHex; + +final customTextColorItem = ToolbarItem( + id: ToolbarId.textColor.id, + group: 1, + isActive: showInAnyTextType, + builder: (context, editorState, highlightColor, iconColor, tooltipBuilder) => + TextColorPickerWidget( + editorState: editorState, + tooltipBuilder: tooltipBuilder, + highlightColor: highlightColor, + ), +); + +class TextColorPickerWidget extends StatefulWidget { + const TextColorPickerWidget({ + super.key, + required this.editorState, + this.tooltipBuilder, + required this.highlightColor, + }); + + final EditorState editorState; + final ToolbarTooltipBuilder? tooltipBuilder; + final Color highlightColor; + + @override + State createState() => _TextColorPickerWidgetState(); +} + +class _TextColorPickerWidgetState extends State { + final popoverController = PopoverController(); + + bool isSelected = false; + + EditorState get editorState => widget.editorState; + + Color get highlightColor => widget.highlightColor; + + @override + void dispose() { + super.dispose(); + popoverController.close(); + } + + @override + Widget build(BuildContext context) { + if (editorState.selection == null) { + return const SizedBox.shrink(); + } + final selectionRectList = editorState.selectionRects(); + final top = + selectionRectList.isEmpty ? 0.0 : selectionRectList.first.height; + return AppFlowyPopover( + controller: popoverController, + direction: PopoverDirection.bottomWithLeftAligned, + offset: Offset(0, top), + onOpen: () => keepEditorFocusNotifier.increase(), + onClose: () { + setState(() { + isSelected = false; + }); + keepEditorFocusNotifier.decrease(); + }, + margin: EdgeInsets.zero, + popupBuilder: (context) => buildPopoverContent(), + child: buildChild(context), + ); + } + + Widget buildChild(BuildContext context) { + final theme = AppFlowyTheme.of(context), + iconColor = theme.iconColorScheme.primary; + final child = FlowyIconButton( + width: 36, + height: 32, + hoverColor: EditorStyleCustomizer.toolbarHoverColor(context), + icon: SizedBox( + width: 20, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + FlowySvg( + FlowySvgs.toolbar_text_color_m, + size: Size(20, 16), + color: iconColor, + ), + buildColorfulDivider(iconColor), + ], + ), + ), + onPressed: () { + setState(() { + isSelected = true; + }); + showPopover(); + }, + ); + + return widget.tooltipBuilder?.call( + context, + ToolbarId.textColor.id, + LocaleKeys.document_toolbar_textColor.tr(), + child, + ) ?? + child; + } + + Widget buildColorfulDivider(Color? iconColor) { + final List colors = []; + final selection = editorState.selection!; + final nodes = editorState.getNodesInSelection(selection); + final isHighLight = nodes.allSatisfyInSelection(selection, (delta) { + if (delta.everyAttributes((attr) => attr.isEmpty)) { + return false; + } + + return delta.everyAttributes((attr) { + final textColorHex = attr[AppFlowyRichTextKeys.textColor]; + if (textColorHex != null) colors.add(textColorHex); + return (textColorHex != null); + }); + }); + + final colorLength = colors.length; + if (colors.isEmpty || !isHighLight) { + return Container( + width: 20, + height: 4, + color: iconColor, + ); + } + return SizedBox( + width: 20, + height: 4, + child: Row( + children: List.generate(colorLength, (index) { + final currentColor = int.tryParse(colors[index]); + return Container( + width: 20 / colorLength, + height: 4, + color: currentColor == null ? iconColor : Color(currentColor), + ); + }), + ), + ); + } + + Widget buildPopoverContent() { + bool showClearButton = false; + final List colors = []; + final selection = editorState.selection!; + final nodes = editorState.getNodesInSelection(selection); + final isHighLight = nodes.allSatisfyInSelection(selection, (delta) { + if (delta.everyAttributes((attr) => attr.isEmpty)) { + return false; + } + + return delta.everyAttributes((attr) { + final textColorHex = attr[AppFlowyRichTextKeys.textColor]; + if (textColorHex != null) colors.add(textColorHex); + return (textColorHex != null); + }); + }); + nodes.allSatisfyInSelection( + selection, + (delta) { + if (!showClearButton) { + showClearButton = delta.whereType().any( + (element) { + return element.attributes?[AppFlowyRichTextKeys.textColor] != + null; + }, + ); + } + return true; + }, + ); + return MouseRegion( + child: ColorPicker( + title: LocaleKeys.document_toolbar_textColor.tr(), + showClearButton: showClearButton, + selectedColorHex: + (colors.length == 1 && isHighLight) ? colors.first : null, + customColorHex: _customColorHex, + colorOptions: generateTextColorOptions(), + onSubmittedColorHex: (color, isCustomColor) { + if (isCustomColor) { + _customColorHex = color; + } + formatFontColor( + editorState, + editorState.selection, + color, + withUpdateSelection: true, + ); + hidePopover(); + }, + resetText: AppFlowyEditorL10n.current.resetToDefaultColor, + resetIconName: 'reset_text_color', + ), + ); + } + + void showPopover() { + keepEditorFocusNotifier.increase(); + popoverController.show(); + } + + void hidePopover() { + popoverController.close(); + keepEditorFocusNotifier.decrease(); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toolbar_item/more_option_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toolbar_item/more_option_toolbar_item.dart new file mode 100644 index 0000000000..46b707a8d3 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toolbar_item/more_option_toolbar_item.dart @@ -0,0 +1,455 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/application/document_bloc.dart'; +import 'package:appflowy/plugins/document/presentation/editor_page.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/desktop_floating_toolbar.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_create_menu.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_hover_menu.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; +import 'package:appflowy/plugins/document/presentation/editor_style.dart'; +import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +// ignore: implementation_imports +import 'package:appflowy_editor/src/editor/toolbar/desktop/items/utils/tooltip_util.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'custom_text_align_toolbar_item.dart'; +import 'text_suggestions_toolbar_item.dart'; + +const _kMoreOptionItemId = 'editor.more_option'; +const kFontToolbarItemId = 'editor.font'; + +@visibleForTesting +const kFontFamilyToolbarItemKey = ValueKey('FontFamilyToolbarItem'); + +final ToolbarItem moreOptionItem = ToolbarItem( + id: _kMoreOptionItemId, + group: 5, + isActive: showInAnyTextType, + builder: ( + context, + editorState, + highlightColor, + iconColor, + tooltipBuilder, + ) { + return MoreOptionActionList( + editorState: editorState, + tooltipBuilder: tooltipBuilder, + highlightColor: highlightColor, + ); + }, +); + +class MoreOptionActionList extends StatefulWidget { + const MoreOptionActionList({ + super.key, + required this.editorState, + required this.highlightColor, + this.tooltipBuilder, + }); + + final EditorState editorState; + final ToolbarTooltipBuilder? tooltipBuilder; + final Color highlightColor; + + @override + State createState() => _MoreOptionActionListState(); +} + +class _MoreOptionActionListState extends State { + final popoverController = PopoverController(); + PopoverController fontPopoverController = PopoverController(); + PopoverController suggestionsPopoverController = PopoverController(); + PopoverController textAlignPopoverController = PopoverController(); + + bool isSelected = false; + + EditorState get editorState => widget.editorState; + + Color get highlightColor => widget.highlightColor; + + MoreOptionCommand? tappedCommand; + + @override + void dispose() { + super.dispose(); + popoverController.close(); + fontPopoverController.close(); + suggestionsPopoverController.close(); + textAlignPopoverController.close(); + } + + @override + Widget build(BuildContext context) { + return AppFlowyPopover( + controller: popoverController, + direction: PopoverDirection.bottomWithLeftAligned, + offset: const Offset(0, 2.0), + onOpen: () => keepEditorFocusNotifier.increase(), + onClose: () { + setState(() { + isSelected = false; + }); + keepEditorFocusNotifier.decrease(); + }, + popupBuilder: (context) => buildPopoverContent(), + child: buildChild(context), + ); + } + + void showPopover() { + keepEditorFocusNotifier.increase(); + popoverController.show(); + } + + Widget buildChild(BuildContext context) { + final iconColor = Theme.of(context).iconTheme.color; + final child = FlowyIconButton( + width: 36, + height: 32, + isSelected: isSelected, + hoverColor: EditorStyleCustomizer.toolbarHoverColor(context), + icon: FlowySvg( + FlowySvgs.toolbar_more_m, + size: Size.square(20), + color: iconColor, + ), + onPressed: () { + setState(() { + isSelected = true; + }); + showPopover(); + }, + ); + + return widget.tooltipBuilder?.call( + context, + _kMoreOptionItemId, + LocaleKeys.document_toolbar_moreOptions.tr(), + child, + ) ?? + child; + } + + Color? getFormulaColor() { + if (isFormulaHighlight(editorState)) { + return widget.highlightColor; + } + return null; + } + + Color? getStrikethroughColor() { + final selection = editorState.selection; + if (selection == null || selection.isCollapsed) { + return null; + } + final node = editorState.getNodeAtPath(selection.start.path); + final delta = node?.delta; + if (node == null || delta == null) { + return null; + } + + final nodes = editorState.getNodesInSelection(selection); + final isHighlight = nodes.allSatisfyInSelection( + selection, + (delta) => + delta.isNotEmpty && + delta.everyAttributes( + (attr) => attr[MoreOptionCommand.strikethrough.name] == true, + ), + ); + return isHighlight ? widget.highlightColor : null; + } + + Widget buildPopoverContent() { + final showFormula = onlyShowInSingleSelectionAndTextType(editorState); + const fontColor = Color(0xff99A1A8); + final isNarrow = isNarrowWindow(editorState); + return MouseRegion( + child: SeparatedColumn( + mainAxisSize: MainAxisSize.min, + separatorBuilder: () => const VSpace(4.0), + children: [ + if (isNarrow) ...[ + buildTurnIntoSelector(), + buildCommandItem(MoreOptionCommand.link), + buildTextAlignSelector(), + ], + buildFontSelector(), + buildCommandItem( + MoreOptionCommand.strikethrough, + rightIcon: FlowyText( + shortcutTooltips( + '⌘⇧S', + 'Ctrl⇧S', + 'Ctrl⇧S', + ).trim(), + color: fontColor, + fontSize: 12, + figmaLineHeight: 16, + fontWeight: FontWeight.w400, + ), + ), + if (showFormula) + buildCommandItem( + MoreOptionCommand.formula, + rightIcon: FlowyText( + shortcutTooltips( + '⌘⇧E', + 'Ctrl⇧E', + 'Ctrl⇧E', + ).trim(), + color: fontColor, + fontSize: 12, + figmaLineHeight: 16, + fontWeight: FontWeight.w400, + ), + ), + ], + ), + ); + } + + Widget buildCommandItem( + MoreOptionCommand command, { + Widget? rightIcon, + VoidCallback? onTap, + }) { + final isFontCommand = command == MoreOptionCommand.font; + return SizedBox( + height: 36, + child: FlowyButton( + key: isFontCommand ? kFontFamilyToolbarItemKey : null, + leftIconSize: const Size.square(20), + leftIcon: FlowySvg(command.svg), + rightIcon: rightIcon, + iconPadding: 12, + text: FlowyText( + command.title, + figmaLineHeight: 20, + fontWeight: FontWeight.w400, + ), + onTap: onTap ?? + () { + command.onExecute(editorState, context); + hideOtherPopovers(command); + if (command != MoreOptionCommand.font) { + popoverController.close(); + } + }, + ), + ); + } + + Widget buildFontSelector() { + final selection = editorState.selection!; + final String? currentFontFamily = editorState + .getDeltaAttributeValueInSelection(AppFlowyRichTextKeys.fontFamily); + return FontFamilyDropDown( + currentFontFamily: currentFontFamily ?? '', + offset: const Offset(-240, 0), + popoverController: fontPopoverController, + onOpen: () => keepEditorFocusNotifier.increase(), + onClose: () => keepEditorFocusNotifier.decrease(), + onFontFamilyChanged: (fontFamily) async { + fontPopoverController.close(); + popoverController.close(); + try { + await editorState.formatDelta(selection, { + AppFlowyRichTextKeys.fontFamily: fontFamily, + }); + } catch (e) { + Log.error('Failed to set font family: $e'); + } + }, + onResetFont: () async { + fontPopoverController.close(); + popoverController.close(); + await editorState + .formatDelta(selection, {AppFlowyRichTextKeys.fontFamily: null}); + }, + child: buildCommandItem( + MoreOptionCommand.font, + rightIcon: FlowySvg(FlowySvgs.toolbar_arrow_right_m), + ), + ); + } + + Widget buildTurnIntoSelector() { + final selectionRects = editorState.selectionRects(); + double height = -6; + if (selectionRects.isNotEmpty) height = selectionRects.first.height; + return SuggestionsActionList( + editorState: editorState, + popoverController: suggestionsPopoverController, + popoverDirection: PopoverDirection.leftWithTopAligned, + showOffset: Offset(-8, height), + onSelect: () => getIt().hideToolbar(), + child: buildCommandItem( + MoreOptionCommand.suggestions, + rightIcon: FlowySvg(FlowySvgs.toolbar_arrow_right_m), + onTap: () { + if (tappedCommand == MoreOptionCommand.suggestions) return; + hideOtherPopovers(MoreOptionCommand.suggestions); + keepEditorFocusNotifier.increase(); + suggestionsPopoverController.show(); + }, + ), + ); + } + + Widget buildTextAlignSelector() { + return TextAlignActionList( + editorState: editorState, + popoverController: textAlignPopoverController, + popoverDirection: PopoverDirection.leftWithTopAligned, + showOffset: Offset(-8, 0), + onSelect: () => getIt().hideToolbar(), + highlightColor: highlightColor, + child: buildCommandItem( + MoreOptionCommand.textAlign, + rightIcon: FlowySvg(FlowySvgs.toolbar_arrow_right_m), + onTap: () { + if (tappedCommand == MoreOptionCommand.textAlign) return; + hideOtherPopovers(MoreOptionCommand.textAlign); + keepEditorFocusNotifier.increase(); + textAlignPopoverController.show(); + }, + ), + ); + } + + void hideOtherPopovers(MoreOptionCommand currentCommand) { + if (tappedCommand == currentCommand) return; + if (tappedCommand == MoreOptionCommand.font) { + fontPopoverController.close(); + fontPopoverController = PopoverController(); + } else if (tappedCommand == MoreOptionCommand.suggestions) { + suggestionsPopoverController.close(); + suggestionsPopoverController = PopoverController(); + } else if (tappedCommand == MoreOptionCommand.textAlign) { + textAlignPopoverController.close(); + textAlignPopoverController = PopoverController(); + } + tappedCommand = currentCommand; + } +} + +enum MoreOptionCommand { + suggestions(FlowySvgs.turninto_s), + link(FlowySvgs.toolbar_link_m), + textAlign( + FlowySvgs.toolbar_alignment_m, + ), + font(FlowySvgs.type_font_m), + strikethrough(FlowySvgs.type_strikethrough_m), + formula(FlowySvgs.type_formula_m); + + const MoreOptionCommand(this.svg); + + final FlowySvgData svg; + + String get title { + switch (this) { + case suggestions: + return LocaleKeys.document_toolbar_turnInto.tr(); + case link: + return LocaleKeys.document_toolbar_link.tr(); + case textAlign: + return LocaleKeys.button_align.tr(); + case font: + return LocaleKeys.document_toolbar_font.tr(); + case strikethrough: + return LocaleKeys.editor_strikethrough.tr(); + case formula: + return LocaleKeys.document_toolbar_equation.tr(); + } + } + + Future onExecute(EditorState editorState, BuildContext context) async { + final selection = editorState.selection!; + if (this == link) { + final nodes = editorState.getNodesInSelection(selection); + final isHref = nodes.allSatisfyInSelection(selection, (delta) { + return delta.everyAttributes( + (attributes) => attributes[AppFlowyRichTextKeys.href] != null, + ); + }); + getIt().hideToolbar(); + if (isHref) { + getIt().call( + HoverTriggerKey(nodes.first.id, selection), + ); + } else { + final viewId = context.read()?.documentId ?? ''; + showLinkCreateMenu(context, editorState, selection, viewId); + } + } else if (this == strikethrough) { + await editorState.toggleAttribute(name); + } else if (this == formula) { + final node = editorState.getNodeAtPath(selection.start.path); + final delta = node?.delta; + if (node == null || delta == null) { + return; + } + + final transaction = editorState.transaction; + final isHighlight = isFormulaHighlight(editorState); + if (isHighlight) { + final formula = delta + .slice(selection.startIndex, selection.endIndex) + .whereType() + .firstOrNull + ?.attributes?[InlineMathEquationKeys.formula]; + assert(formula != null); + if (formula == null) { + return; + } + // clear the format + transaction.replaceText( + node, + selection.startIndex, + selection.length, + formula, + attributes: {}, + ); + } else { + final text = editorState.getTextInSelection(selection).join(); + transaction.replaceText( + node, + selection.startIndex, + selection.length, + MentionBlockKeys.mentionChar, + attributes: { + InlineMathEquationKeys.formula: text, + }, + ); + } + await editorState.apply(transaction); + } + } +} + +bool isFormulaHighlight(EditorState editorState) { + final selection = editorState.selection; + if (selection == null || selection.isCollapsed) { + return false; + } + final node = editorState.getNodeAtPath(selection.start.path); + final delta = node?.delta; + if (node == null || delta == null) { + return false; + } + + final nodes = editorState.getNodesInSelection(selection); + return nodes.allSatisfyInSelection(selection, (delta) { + return delta.everyAttributes( + (attributes) => attributes[InlineMathEquationKeys.formula] != null, + ); + }); +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toolbar_item/text_heading_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toolbar_item/text_heading_toolbar_item.dart new file mode 100644 index 0000000000..5778b6b8a4 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toolbar_item/text_heading_toolbar_item.dart @@ -0,0 +1,247 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_option_cubit.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; +import 'package:appflowy/plugins/document/presentation/editor_style.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; + +import 'toolbar_id_enum.dart'; + +final ToolbarItem customTextHeadingItem = ToolbarItem( + id: ToolbarId.textHeading.id, + group: 1, + isActive: onlyShowInSingleTextTypeSelectionAndExcludeTable, + builder: ( + context, + editorState, + highlightColor, + iconColor, + tooltipBuilder, + ) { + return TextHeadingActionList( + editorState: editorState, + tooltipBuilder: tooltipBuilder, + ); + }, +); + +class TextHeadingActionList extends StatefulWidget { + const TextHeadingActionList({ + super.key, + required this.editorState, + this.tooltipBuilder, + }); + + final EditorState editorState; + final ToolbarTooltipBuilder? tooltipBuilder; + + @override + State createState() => _TextHeadingActionListState(); +} + +class _TextHeadingActionListState extends State { + final popoverController = PopoverController(); + + bool isSelected = false; + + @override + void dispose() { + super.dispose(); + popoverController.close(); + } + + @override + Widget build(BuildContext context) { + return AppFlowyPopover( + controller: popoverController, + direction: PopoverDirection.bottomWithLeftAligned, + offset: const Offset(0, 2.0), + onOpen: () => keepEditorFocusNotifier.increase(), + onClose: () { + setState(() { + isSelected = false; + }); + keepEditorFocusNotifier.decrease(); + }, + popupBuilder: (context) => buildPopoverContent(), + child: buildChild(context), + ); + } + + void showPopover() { + keepEditorFocusNotifier.increase(); + popoverController.show(); + } + + Widget buildChild(BuildContext context) { + final theme = AppFlowyTheme.of(context), + iconColor = theme.iconColorScheme.primary; + final child = FlowyIconButton( + width: 48, + height: 32, + isSelected: isSelected, + hoverColor: EditorStyleCustomizer.toolbarHoverColor(context), + icon: Row( + mainAxisSize: MainAxisSize.min, + children: [ + FlowySvg( + FlowySvgs.toolbar_text_format_m, + size: Size.square(20), + color: iconColor, + ), + HSpace(4), + FlowySvg( + FlowySvgs.toolbar_arrow_down_m, + size: Size(12, 20), + color: iconColor, + ), + ], + ), + onPressed: () { + setState(() { + isSelected = true; + }); + showPopover(); + }, + ); + + return widget.tooltipBuilder?.call( + context, + ToolbarId.textHeading.id, + LocaleKeys.document_toolbar_textSize.tr(), + child, + ) ?? + child; + } + + Widget buildPopoverContent() { + final selectingCommand = getSelectingCommand(); + return MouseRegion( + child: SeparatedColumn( + mainAxisSize: MainAxisSize.min, + separatorBuilder: () => const VSpace(4.0), + children: List.generate(TextHeadingCommand.values.length, (index) { + final command = TextHeadingCommand.values[index]; + return SizedBox( + height: 36, + child: FlowyButton( + leftIconSize: const Size.square(20), + leftIcon: FlowySvg(command.svg), + iconPadding: 12, + text: FlowyText( + command.title, + fontWeight: FontWeight.w400, + figmaLineHeight: 20, + ), + rightIcon: selectingCommand == command + ? FlowySvg(FlowySvgs.toolbar_check_m) + : null, + onTap: () { + if (command == selectingCommand) return; + command.onExecute(widget.editorState); + popoverController.close(); + }, + ), + ); + }), + ), + ); + } + + TextHeadingCommand? getSelectingCommand() { + final editorState = widget.editorState; + final selection = editorState.selection; + if (selection == null || !selection.isSingle) { + return null; + } + final node = editorState.getNodeAtPath(selection.start.path); + if (node == null || node.delta == null) { + return null; + } + final nodeType = node.type; + if (nodeType == ParagraphBlockKeys.type) return TextHeadingCommand.text; + if (nodeType == HeadingBlockKeys.type) { + final level = node.attributes[HeadingBlockKeys.level] ?? 1; + if (level == 1) return TextHeadingCommand.h1; + if (level == 2) return TextHeadingCommand.h2; + if (level == 3) return TextHeadingCommand.h3; + } + return null; + } +} + +enum TextHeadingCommand { + text(FlowySvgs.type_text_m), + h1(FlowySvgs.type_h1_m), + h2(FlowySvgs.type_h2_m), + h3(FlowySvgs.type_h3_m); + + const TextHeadingCommand(this.svg); + + final FlowySvgData svg; + + String get title { + switch (this) { + case text: + return AppFlowyEditorL10n.current.text; + case h1: + return LocaleKeys.document_toolbar_h1.tr(); + case h2: + return LocaleKeys.document_toolbar_h2.tr(); + case h3: + return LocaleKeys.document_toolbar_h3.tr(); + } + } + + void onExecute(EditorState state) { + switch (this) { + case text: + formatNodeToText(state); + break; + case h1: + _turnInto(state, 1); + break; + case h2: + _turnInto(state, 2); + break; + case h3: + _turnInto(state, 3); + break; + } + } + + Future _turnInto(EditorState state, int level) async { + final selection = state.selection!; + final node = state.getNodeAtPath(selection.start.path)!; + await BlockActionOptionCubit.turnIntoBlock( + HeadingBlockKeys.type, + node, + state, + level: level, + keepSelection: true, + ); + } +} + +void formatNodeToText(EditorState editorState) { + final selection = editorState.selection!; + final node = editorState.getNodeAtPath(selection.start.path)!; + final delta = (node.delta ?? Delta()).toJson(); + editorState.formatNode( + selection, + (node) => node.copyWith( + type: ParagraphBlockKeys.type, + attributes: { + blockComponentDelta: delta, + blockComponentBackgroundColor: + node.attributes[blockComponentBackgroundColor], + blockComponentTextDirection: + node.attributes[blockComponentTextDirection], + }, + ), + ); +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toolbar_item/text_suggestions_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toolbar_item/text_suggestions_toolbar_item.dart new file mode 100644 index 0000000000..48f5d3f403 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toolbar_item/text_suggestions_toolbar_item.dart @@ -0,0 +1,536 @@ +import 'dart:collection'; +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_option_cubit.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; +import 'package:appflowy/plugins/document/presentation/editor_style.dart'; +import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart'; +import 'package:appflowy_editor/appflowy_editor.dart' + hide QuoteBlockComponentBuilder, quoteNode, QuoteBlockKeys; +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/size.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flowy_infra_ui/style_widget/hover.dart'; +import 'package:flutter/material.dart'; + +import 'text_heading_toolbar_item.dart'; +import 'toolbar_id_enum.dart'; + +@visibleForTesting +const kSuggestionsItemKey = ValueKey('SuggestionsItem'); + +@visibleForTesting +const kSuggestionsItemListKey = ValueKey('SuggestionsItemList'); + +final ToolbarItem suggestionsItem = ToolbarItem( + id: ToolbarId.suggestions.id, + group: 3, + isActive: enableSuggestions, + builder: ( + context, + editorState, + highlightColor, + iconColor, + tooltipBuilder, + ) { + return SuggestionsActionList( + editorState: editorState, + tooltipBuilder: tooltipBuilder, + ); + }, +); + +class SuggestionsActionList extends StatefulWidget { + const SuggestionsActionList({ + super.key, + required this.editorState, + this.tooltipBuilder, + this.child, + this.onSelect, + this.popoverController, + this.popoverDirection = PopoverDirection.bottomWithLeftAligned, + this.showOffset = const Offset(0, 2), + }); + + final EditorState editorState; + final ToolbarTooltipBuilder? tooltipBuilder; + final Widget? child; + final VoidCallback? onSelect; + final PopoverController? popoverController; + final PopoverDirection popoverDirection; + final Offset showOffset; + + @override + State createState() => _SuggestionsActionListState(); +} + +class _SuggestionsActionListState extends State { + late PopoverController popoverController = + widget.popoverController ?? PopoverController(); + + bool isSelected = false; + + final List suggestionItems = suggestions.sublist(0, 4); + final List turnIntoItems = + suggestions.sublist(4, suggestions.length); + + EditorState get editorState => widget.editorState; + + SuggestionItem currentSuggestionItem = textSuggestionItem; + + @override + void initState() { + super.initState(); + refreshSuggestions(); + editorState.selectionNotifier.addListener(refreshSuggestions); + } + + @override + void dispose() { + editorState.selectionNotifier.removeListener(refreshSuggestions); + popoverController.close(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AppFlowyPopover( + controller: popoverController, + direction: widget.popoverDirection, + offset: widget.showOffset, + onOpen: () => keepEditorFocusNotifier.increase(), + onClose: () { + setState(() { + isSelected = false; + }); + keepEditorFocusNotifier.decrease(); + }, + constraints: const BoxConstraints(maxWidth: 240, maxHeight: 400), + popupBuilder: (context) => buildPopoverContent(context), + child: widget.child ?? buildChild(context), + ); + } + + void showPopover() { + keepEditorFocusNotifier.increase(); + popoverController.show(); + } + + Widget buildChild(BuildContext context) { + final theme = AppFlowyTheme.of(context), + iconColor = theme.iconColorScheme.primary; + final child = FlowyHover( + isSelected: () => isSelected, + style: HoverStyle( + hoverColor: EditorStyleCustomizer.toolbarHoverColor(context), + foregroundColorOnHover: Theme.of(context).iconTheme.color, + ), + resetHoverOnRebuild: false, + child: FlowyTooltip( + preferBelow: true, + child: RawMaterialButton( + key: kSuggestionsItemKey, + constraints: BoxConstraints(maxHeight: 32, minWidth: 60), + clipBehavior: Clip.antiAlias, + hoverElevation: 0, + highlightElevation: 0, + shape: RoundedRectangleBorder(borderRadius: Corners.s6Border), + fillColor: Colors.transparent, + hoverColor: Colors.transparent, + focusColor: Colors.transparent, + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + elevation: 0, + onPressed: () { + setState(() { + isSelected = true; + }); + showPopover(); + }, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + FlowyText( + currentSuggestionItem.title, + fontWeight: FontWeight.w400, + figmaLineHeight: 20, + ), + HSpace(4), + FlowySvg( + FlowySvgs.toolbar_arrow_down_m, + size: Size(12, 20), + color: iconColor, + ), + ], + ), + ), + ), + ), + ); + + return widget.tooltipBuilder?.call( + context, + ToolbarId.suggestions.id, + currentSuggestionItem.title, + child, + ) ?? + child; + } + + Widget buildPopoverContent(BuildContext context) { + final textColor = Color(0xff99A1A8); + return MouseRegion( + child: SingleChildScrollView( + key: kSuggestionsItemListKey, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildSubTitle( + LocaleKeys.document_toolbar_suggestions.tr(), + textColor, + ), + ...List.generate(suggestionItems.length, (index) { + return buildItem(suggestionItems[index]); + }), + buildSubTitle(LocaleKeys.document_toolbar_turnInto.tr(), textColor), + ...List.generate(turnIntoItems.length, (index) { + return buildItem(turnIntoItems[index]); + }), + ], + ), + ), + ); + } + + Widget buildItem(SuggestionItem item) { + final isSelected = item.type == currentSuggestionItem.type; + return SizedBox( + height: 36, + child: FlowyButton( + leftIconSize: const Size.square(20), + leftIcon: FlowySvg(item.svg), + iconPadding: 12, + text: FlowyText( + item.title, + fontWeight: FontWeight.w400, + figmaLineHeight: 20, + ), + rightIcon: isSelected ? FlowySvg(FlowySvgs.toolbar_check_m) : null, + onTap: () { + item.onTap(widget.editorState, true); + widget.onSelect?.call(); + popoverController.close(); + }, + ), + ); + } + + Widget buildSubTitle(String text, Color color) { + return Container( + height: 32, + margin: EdgeInsets.symmetric(horizontal: 8), + child: Align( + alignment: Alignment.centerLeft, + child: FlowyText.semibold( + text, + color: color, + figmaLineHeight: 16, + ), + ), + ); + } + + void refreshSuggestions() { + final selection = editorState.selection; + if (selection == null || !selection.isSingle) { + return; + } + final node = editorState.getNodeAtPath(selection.start.path); + if (node == null || node.delta == null) { + return; + } + final nodeType = node.type; + SuggestionType? suggestionType; + if (nodeType == HeadingBlockKeys.type) { + final level = node.attributes[HeadingBlockKeys.level] ?? 1; + if (level == 1) { + suggestionType = SuggestionType.h1; + } else if (level == 2) { + suggestionType = SuggestionType.h2; + } else if (level == 3) { + suggestionType = SuggestionType.h3; + } + } else if (nodeType == ToggleListBlockKeys.type) { + final level = node.attributes[ToggleListBlockKeys.level]; + if (level == null) { + suggestionType = SuggestionType.toggle; + } else if (level == 1) { + suggestionType = SuggestionType.toggleH1; + } else if (level == 2) { + suggestionType = SuggestionType.toggleH2; + } else if (level == 3) { + suggestionType = SuggestionType.toggleH3; + } + } else { + suggestionType = nodeType2SuggestionType[nodeType]; + } + if (suggestionType == null) return; + suggestionItems.clear(); + turnIntoItems.clear(); + for (final item in suggestions) { + if (item.type.group == suggestionType.group && + item.type != suggestionType) { + suggestionItems.add(item); + } else { + turnIntoItems.add(item); + } + } + currentSuggestionItem = + suggestions.where((item) => item.type == suggestionType).first; + if (mounted) setState(() {}); + } +} + +class SuggestionItem { + SuggestionItem({ + required this.type, + required this.title, + required this.svg, + required this.onTap, + }); + + final SuggestionType type; + final String title; + final FlowySvgData svg; + final Function(EditorState state, bool keepSelection) onTap; +} + +enum SuggestionGroup { textHeading, list, toggle, quote, page } + +enum SuggestionType { + text(SuggestionGroup.textHeading), + h1(SuggestionGroup.textHeading), + h2(SuggestionGroup.textHeading), + h3(SuggestionGroup.textHeading), + checkbox(SuggestionGroup.list), + bulleted(SuggestionGroup.list), + numbered(SuggestionGroup.list), + toggle(SuggestionGroup.toggle), + toggleH1(SuggestionGroup.toggle), + toggleH2(SuggestionGroup.toggle), + toggleH3(SuggestionGroup.toggle), + callOut(SuggestionGroup.quote), + quote(SuggestionGroup.quote), + page(SuggestionGroup.page); + + const SuggestionType(this.group); + + final SuggestionGroup group; +} + +final textSuggestionItem = SuggestionItem( + type: SuggestionType.text, + title: AppFlowyEditorL10n.current.text, + svg: FlowySvgs.type_text_m, + onTap: (state, _) => formatNodeToText(state), +); + +final h1SuggestionItem = SuggestionItem( + type: SuggestionType.h1, + title: LocaleKeys.document_toolbar_h1.tr(), + svg: FlowySvgs.type_h1_m, + onTap: (state, keepSelection) => _turnInto( + state, + HeadingBlockKeys.type, + level: 1, + keepSelection: keepSelection, + ), +); + +final h2SuggestionItem = SuggestionItem( + type: SuggestionType.h2, + title: LocaleKeys.document_toolbar_h2.tr(), + svg: FlowySvgs.type_h2_m, + onTap: (state, keepSelection) => _turnInto( + state, + HeadingBlockKeys.type, + level: 2, + keepSelection: keepSelection, + ), +); + +final h3SuggestionItem = SuggestionItem( + type: SuggestionType.h3, + title: LocaleKeys.document_toolbar_h3.tr(), + svg: FlowySvgs.type_h3_m, + onTap: (state, keepSelection) => _turnInto( + state, + HeadingBlockKeys.type, + level: 3, + keepSelection: keepSelection, + ), +); + +final checkboxSuggestionItem = SuggestionItem( + type: SuggestionType.checkbox, + title: LocaleKeys.editor_checkbox.tr(), + svg: FlowySvgs.type_todo_m, + onTap: (state, keepSelection) => _turnInto( + state, + TodoListBlockKeys.type, + keepSelection: keepSelection, + ), +); + +final bulletedSuggestionItem = SuggestionItem( + type: SuggestionType.bulleted, + title: LocaleKeys.editor_bulletedListShortForm.tr(), + svg: FlowySvgs.type_bulleted_list_m, + onTap: (state, keepSelection) => _turnInto( + state, + BulletedListBlockKeys.type, + keepSelection: keepSelection, + ), +); + +final numberedSuggestionItem = SuggestionItem( + type: SuggestionType.numbered, + title: LocaleKeys.editor_numberedListShortForm.tr(), + svg: FlowySvgs.type_numbered_list_m, + onTap: (state, keepSelection) => _turnInto( + state, + NumberedListBlockKeys.type, + keepSelection: keepSelection, + ), +); + +final toggleSuggestionItem = SuggestionItem( + type: SuggestionType.toggle, + title: LocaleKeys.editor_toggleListShortForm.tr(), + svg: FlowySvgs.type_toggle_list_m, + onTap: (state, keepSelection) => _turnInto( + state, + ToggleListBlockKeys.type, + keepSelection: keepSelection, + ), +); + +final toggleH1SuggestionItem = SuggestionItem( + type: SuggestionType.toggleH1, + title: LocaleKeys.editor_toggleHeading1ShortForm.tr(), + svg: FlowySvgs.type_toggle_h1_m, + onTap: (state, keepSelection) => _turnInto( + state, + ToggleListBlockKeys.type, + level: 1, + keepSelection: keepSelection, + ), +); + +final toggleH2SuggestionItem = SuggestionItem( + type: SuggestionType.toggleH2, + title: LocaleKeys.editor_toggleHeading2ShortForm.tr(), + svg: FlowySvgs.type_toggle_h2_m, + onTap: (state, keepSelection) => _turnInto( + state, + ToggleListBlockKeys.type, + level: 2, + keepSelection: keepSelection, + ), +); + +final toggleH3SuggestionItem = SuggestionItem( + type: SuggestionType.toggleH3, + title: LocaleKeys.editor_toggleHeading3ShortForm.tr(), + svg: FlowySvgs.type_toggle_h3_m, + onTap: (state, keepSelection) => _turnInto( + state, + ToggleListBlockKeys.type, + level: 3, + keepSelection: keepSelection, + ), +); + +final callOutSuggestionItem = SuggestionItem( + type: SuggestionType.callOut, + title: LocaleKeys.document_plugins_callout.tr(), + svg: FlowySvgs.type_callout_m, + onTap: (state, keepSelection) => _turnInto( + state, + CalloutBlockKeys.type, + keepSelection: keepSelection, + ), +); + +final quoteSuggestionItem = SuggestionItem( + type: SuggestionType.quote, + title: LocaleKeys.editor_quote.tr(), + svg: FlowySvgs.type_quote_m, + onTap: (state, keepSelection) => _turnInto( + state, + QuoteBlockKeys.type, + keepSelection: keepSelection, + ), +); + +final pateItem = SuggestionItem( + type: SuggestionType.page, + title: LocaleKeys.editor_page.tr(), + svg: FlowySvgs.icon_document_s, + onTap: (state, keepSelection) => _turnInto( + state, + SubPageBlockKeys.type, + viewId: getIt().latestOpenView?.id, + keepSelection: keepSelection, + ), +); + +Future _turnInto( + EditorState state, + String type, { + int? level, + String? viewId, + bool keepSelection = true, +}) async { + final selection = state.selection!; + final node = state.getNodeAtPath(selection.start.path)!; + await BlockActionOptionCubit.turnIntoBlock( + type, + node, + state, + level: level, + currentViewId: viewId, + keepSelection: keepSelection, + ); +} + +final suggestions = UnmodifiableListView([ + textSuggestionItem, + h1SuggestionItem, + h2SuggestionItem, + h3SuggestionItem, + checkboxSuggestionItem, + bulletedSuggestionItem, + numberedSuggestionItem, + toggleSuggestionItem, + toggleH1SuggestionItem, + toggleH2SuggestionItem, + toggleH3SuggestionItem, + callOutSuggestionItem, + quoteSuggestionItem, + pateItem, +]); + +final nodeType2SuggestionType = UnmodifiableMapView({ + ParagraphBlockKeys.type: SuggestionType.text, + NumberedListBlockKeys.type: SuggestionType.numbered, + BulletedListBlockKeys.type: SuggestionType.bulleted, + QuoteBlockKeys.type: SuggestionType.quote, + TodoListBlockKeys.type: SuggestionType.checkbox, + CalloutBlockKeys.type: SuggestionType.callOut, +}); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toolbar_item/toolbar_id_enum.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toolbar_item/toolbar_id_enum.dart new file mode 100644 index 0000000000..8a97bb6648 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toolbar_item/toolbar_id_enum.dart @@ -0,0 +1,19 @@ +enum ToolbarId { + bold, + underline, + italic, + code, + highlightColor, + textColor, + link, + placeholder, + paddingPlaceHolder, + textAlign, + moreOption, + textHeading, + suggestions, +} + +extension ToolbarIdExtension on ToolbarId { + String get id => 'editor.$name'; +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/undo_redo/custom_undo_redo_commands.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/undo_redo/custom_undo_redo_commands.dart index 9ea6477969..36ea3d2704 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/undo_redo/custom_undo_redo_commands.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/undo_redo/custom_undo_redo_commands.dart @@ -1,6 +1,8 @@ import 'package:appflowy/plugins/document/presentation/editor_notification.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/shared_context/shared_context.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; /// Undo /// @@ -14,10 +16,15 @@ final CommandShortcutEvent customUndoCommand = CommandShortcutEvent( command: 'ctrl+z', macOSCommand: 'cmd+z', handler: (editorState) { - // if the selection is null, it means the keyboard service is disabled - if (editorState.selection == null) { + final context = editorState.document.root.context; + if (context == null) { return KeyEventResult.ignored; } + final editorContext = context.read(); + if (editorContext.coverTitleFocusNode.hasFocus) { + return KeyEventResult.ignored; + } + EditorNotification.undo().post(); return KeyEventResult.handled; }, @@ -35,9 +42,15 @@ final CommandShortcutEvent customRedoCommand = CommandShortcutEvent( command: 'ctrl+y,ctrl+shift+z', macOSCommand: 'cmd+shift+z', handler: (editorState) { - if (editorState.selection == null) { + final context = editorState.document.root.context; + if (context == null) { return KeyEventResult.ignored; } + final editorContext = context.read(); + if (editorContext.coverTitleFocusNode.hasFocus) { + return KeyEventResult.ignored; + } + EditorNotification.redo().post(); return KeyEventResult.handled; }, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/video/video_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/video/video_block_component.dart new file mode 100644 index 0000000000..f41d4526ea --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/video/video_block_component.dart @@ -0,0 +1,6 @@ +class VideoBlockKeys { + const VideoBlockKeys._(); + + static const String type = 'video'; + static const String url = 'url'; +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart index de4d431a08..cd9d7bb5e8 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart @@ -10,6 +10,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.da import 'package:appflowy/plugins/inline_actions/inline_actions_menu.dart'; import 'package:appflowy/shared/google_fonts_extension.dart'; import 'package:appflowy/util/font_family_extension.dart'; +import 'package:appflowy/util/theme_extension.dart'; import 'package:appflowy/workspace/application/appearance_defaults.dart'; import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart'; @@ -27,16 +28,21 @@ import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:universal_platform/universal_platform.dart'; +import 'editor_plugins/desktop_toolbar/link/link_hover_menu.dart'; +import 'editor_plugins/toolbar_item/more_option_toolbar_item.dart'; + class EditorStyleCustomizer { EditorStyleCustomizer({ required this.context, required this.padding, this.width, + this.editorState, }); final BuildContext context; final EdgeInsets padding; final double? width; + final EditorState? editorState; static const double maxDocumentWidth = 480 * 4; static const double minDocumentWidth = 480; @@ -56,6 +62,12 @@ class EditorStyleCustomizer { static double get optionMenuWidth => UniversalPlatform.isMobile ? 0 : 44; + static Color? toolbarHoverColor(BuildContext context) { + return Theme.of(context).brightness == Brightness.dark + ? Theme.of(context).colorScheme.secondary + : AFThemeExtension.of(context).toolbarHoverColor; + } + EditorStyle style() { if (UniversalPlatform.isDesktopOrWeb) { return desktop(); @@ -76,11 +88,15 @@ class EditorStyleCustomizer { fontFamily = appearanceFont; } + final cursorColor = (editorState?.editable ?? true) + ? (appearance.cursorColor ?? + DefaultAppearanceSettings.getDefaultCursorColor(context)) + : Colors.transparent; + return EditorStyle.desktop( padding: padding, maxWidth: width, - cursorColor: appearance.cursorColor ?? - DefaultAppearanceSettings.getDefaultCursorColor(context), + cursorColor: cursorColor, selectionColor: appearance.selectionColor ?? DefaultAppearanceSettings.getDefaultSelectionColor(context), defaultTextDirection: appearance.defaultTextDirection, @@ -111,13 +127,15 @@ class EditorStyleCustomizer { fontSize: fontSize, fontWeight: FontWeight.normal, color: Colors.red, - backgroundColor: theme.colorScheme.inverseSurface.withOpacity(0.8), + backgroundColor: + theme.colorScheme.inverseSurface.withValues(alpha: 0.8), ), ), ), textSpanDecorator: customizeAttributeDecorator, textScaleFactor: context.watch().state.textScaleFactor, + textSpanOverlayBuilder: _buildTextSpanOverlay, ); } @@ -159,16 +177,17 @@ class EditorStyleCustomizer { fontSize: fontSize, fontWeight: FontWeight.normal, color: Colors.red, - backgroundColor: Colors.grey.withOpacity(0.3), + backgroundColor: Colors.grey.withValues(alpha: 0.3), ), ), applyHeightToFirstAscent: true, applyHeightToLastDescent: true, ), textSpanDecorator: customizeAttributeDecorator, - mobileDragHandleBallSize: const Size.square(12.0), magnifierSize: const Size(144, 96), textScaleFactor: textScaleFactor, + mobileDragHandleLeftExtend: 12.0, + mobileDragHandleWidthExtend: 24.0, ); } @@ -240,7 +259,7 @@ class EditorStyleCustomizer { fontFamily: defaultFontFamily, fontSize: fontSize, height: 1.5, - color: AFThemeExtension.of(context).onBackground.withOpacity(0.6), + color: AFThemeExtension.of(context).onBackground.withValues(alpha: 0.6), ); } @@ -272,6 +291,15 @@ class EditorStyleCustomizer { selectionMenuItemSelectedIconColor: theme.colorScheme.onSurface, selectionMenuItemSelectedTextColor: theme.colorScheme.onSurface, selectionMenuItemSelectedColor: afThemeExtension.greyHover, + selectionMenuUnselectedLabelColor: afThemeExtension.onBackground, + selectionMenuDividerColor: afThemeExtension.greyHover, + selectionMenuLinkBorderColor: afThemeExtension.greyHover, + selectionMenuInvalidLinkColor: afThemeExtension.onBackground, + selectionMenuButtonColor: afThemeExtension.greyHover, + selectionMenuButtonTextColor: afThemeExtension.onBackground, + selectionMenuButtonIconColor: afThemeExtension.onBackground, + selectionMenuButtonBorderColor: afThemeExtension.greyHover, + selectionMenuTabIndicatorColor: afThemeExtension.greyHover, ); } @@ -280,21 +308,20 @@ class EditorStyleCustomizer { final afThemeExtension = AFThemeExtension.of(context); return InlineActionsMenuStyle( backgroundColor: theme.cardColor, - groupTextColor: afThemeExtension.onBackground.withOpacity(.8), + groupTextColor: afThemeExtension.onBackground.withValues(alpha: .8), menuItemTextColor: afThemeExtension.onBackground, menuItemSelectedColor: theme.colorScheme.secondary, menuItemSelectedTextColor: theme.colorScheme.onSurface, ); } - FloatingToolbarStyle floatingToolbarStyleBuilder() => FloatingToolbarStyle( - backgroundColor: Theme.of(context).colorScheme.onTertiary, - ); - TextStyle baseTextStyle(String? fontFamily, {FontWeight? fontWeight}) { - if (fontFamily == null || fontFamily == defaultFontFamily) { + if (fontFamily == null) { return TextStyle(fontWeight: fontWeight); + } else if (fontFamily == defaultFontFamily) { + return TextStyle(fontFamily: fontFamily, fontWeight: fontWeight); } + try { return getGoogleFontSafely(fontFamily, fontWeight: fontWeight); } on Exception { @@ -319,6 +346,11 @@ class EditorStyleCustomizer { return before; } + final suggestion = attributes[AiWriterBlockKeys.suggestion] as String?; + final newStyle = suggestion == null + ? after.style + : _styleSuggestion(after.style, suggestion); + if (attributes.backgroundColor != null) { final color = EditorFontColors.fromBuiltInColors( context, @@ -327,7 +359,7 @@ class EditorStyleCustomizer { if (color != null) { return TextSpan( text: before.text, - style: after.style?.merge( + style: newStyle?.merge( TextStyle(backgroundColor: color), ), ); @@ -342,7 +374,7 @@ class EditorStyleCustomizer { } else { return TextSpan( text: before.text, - style: after.style?.merge( + style: newStyle?.merge( getGoogleFontSafely(attributes.fontFamily!), ), ); @@ -359,7 +391,7 @@ class EditorStyleCustomizer { final type = mention[MentionBlockKeys.type]; return WidgetSpan( alignment: PlaceholderAlignment.middle, - style: after.style, + style: newStyle, child: MentionBlock( key: ValueKey( switch (type) { @@ -371,7 +403,7 @@ class EditorStyleCustomizer { node: node, index: index, mention: mention, - textStyle: after.style, + textStyle: newStyle, ), ); } @@ -434,14 +466,22 @@ class EditorStyleCustomizer { ); } - return defaultTextSpanDecoratorForAttribute( - context, - node, - index, - text, - before, - after, - ); + if (suggestion != null) { + return TextSpan( + text: before.text, + style: newStyle, + ); + } + + if (href != null) { + return TextSpan( + style: before.style, + text: text.text, + mouseCursor: SystemMouseCursors.click, + ); + } else { + return before; + } } Widget buildToolbarItemTooltip( @@ -454,7 +494,7 @@ class EditorStyleCustomizer { child = FlowyTooltip( richMessage: tooltipMessage, preferBelow: false, - verticalOffset: 20, + verticalOffset: 24, child: child, ); @@ -466,10 +506,10 @@ class EditorStyleCustomizer { if (!toolbarItemsWithoutHover.contains(id)) { child = Padding( - padding: const EdgeInsets.symmetric(vertical: 4.0), + padding: const EdgeInsets.symmetric(vertical: 6), child: FlowyHover( style: HoverStyle( - hoverColor: Colors.grey.withOpacity(0.3), + hoverColor: Colors.grey.withValues(alpha: 0.3), ), child: child, ), @@ -486,6 +526,10 @@ class EditorStyleCustomizer { 'italic': (LocaleKeys.toolbar_italic.tr(), 'I'), 'strikethrough': (LocaleKeys.toolbar_strike.tr(), 'Shift+S'), 'code': (LocaleKeys.toolbar_inlineCode.tr(), 'E'), + 'editor.inline_math_equation': ( + LocaleKeys.document_plugins_createInlineMathEquation.tr(), + 'Shift+E' + ), }; final markdownItemIds = markdownItemTooltips.keys.toSet(); @@ -512,14 +556,85 @@ class EditorStyleCustomizer { style: context.tooltipTextStyle(), ), TextSpan( - text: (Platform.isMacOS ? '⌘+' : 'Ctrl+\\') + tooltip.$2, - style: context - .tooltipTextStyle() - ?.copyWith(color: Theme.of(context).hintColor), + text: (Platform.isMacOS ? '⌘+' : 'Ctrl+') + tooltip.$2, + style: context.tooltipTextStyle()?.copyWith( + color: Theme.of(context).hintColor, + ), ), ], ); return textSpan; } + + TextStyle? _styleSuggestion(TextStyle? style, String suggestion) { + if (style == null) { + return null; + } + final isLight = Theme.of(context).isLightMode; + final textColor = isLight ? Color(0xFF007296) : Color(0xFF49CFF4); + final underlineColor = isLight ? Color(0x33005A7A) : Color(0x3349CFF4); + return switch (suggestion) { + AiWriterBlockKeys.suggestionOriginal => style.copyWith( + color: Theme.of(context).disabledColor, + decoration: TextDecoration.lineThrough, + ), + AiWriterBlockKeys.suggestionReplacement => style.copyWith( + color: textColor, + decoration: TextDecoration.underline, + decorationColor: underlineColor, + decorationThickness: 1.0, + ), + _ => style, + }; + } + + List _buildTextSpanOverlay( + BuildContext context, + Node node, + SelectableMixin delegate, + ) { + if (UniversalPlatform.isMobile) return []; + final delta = node.delta; + if (delta == null) return []; + final widgets = []; + final textInserts = delta.whereType(); + int index = 0; + final editorState = context.read(); + for (final textInsert in textInserts) { + if (textInsert.attributes?.href != null) { + final nodeSelection = Selection( + start: Position(path: node.path, offset: index), + end: Position( + path: node.path, + offset: index + textInsert.length, + ), + ); + final rectList = delegate.getRectsInSelection(nodeSelection); + if (rectList.isNotEmpty) { + for (final rect in rectList) { + widgets.add( + Positioned( + left: rect.left, + top: rect.top, + child: SizedBox( + width: rect.width, + height: rect.height, + child: LinkHoverTrigger( + editorState: editorState, + selection: nodeSelection, + attribute: textInsert.attributes!, + node: node, + size: rect.size, + ), + ), + ), + ); + } + } + } + index += textInsert.length; + } + return widgets; + } } diff --git a/frontend/appflowy_flutter/lib/plugins/emoji/emoji_actions_command.dart b/frontend/appflowy_flutter/lib/plugins/emoji/emoji_actions_command.dart new file mode 100644 index 0000000000..c116680c2e --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/emoji/emoji_actions_command.dart @@ -0,0 +1,66 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:universal_platform/universal_platform.dart'; + +import 'emoji_menu.dart'; + +const _emojiCharacter = ':'; +final _letterRegExp = RegExp(r'^[a-zA-Z]$'); + +CharacterShortcutEvent emojiCommand(BuildContext context) => + CharacterShortcutEvent( + key: 'Opens Emoji Menu', + character: '', + regExp: _letterRegExp, + handler: (editorState) async { + return false; + }, + handlerWithCharacter: (editorState, character) { + emojiMenuService = EmojiMenu( + overlay: Overlay.of(context), + editorState: editorState, + ); + return emojiCommandHandler(editorState, context, character); + }, + ); + +EmojiMenuService? emojiMenuService; + +Future emojiCommandHandler( + EditorState editorState, + BuildContext context, + String character, +) async { + final selection = editorState.selection; + + if (UniversalPlatform.isMobile || selection == null) { + return false; + } + + final node = editorState.getNodeAtPath(selection.end.path); + final delta = node?.delta; + if (node == null || delta == null || node.type == CodeBlockKeys.type) { + return false; + } + + if (selection.end.offset > 0) { + final plain = delta.toPlainText(); + + final previousCharacter = plain[selection.end.offset - 1]; + if (previousCharacter != _emojiCharacter) return false; + if (!context.mounted) return false; + + if (!selection.isCollapsed) return false; + + await editorState.insertTextAtPosition( + character, + position: selection.start, + ); + + emojiMenuService?.show(character); + return true; + } + + return false; +} diff --git a/frontend/appflowy_flutter/lib/plugins/emoji/emoji_handler.dart b/frontend/appflowy_flutter/lib/plugins/emoji/emoji_handler.dart new file mode 100644 index 0000000000..b1b1e7cdbb --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/emoji/emoji_handler.dart @@ -0,0 +1,413 @@ +import 'dart:math'; + +import 'package:appflowy/plugins/base/emoji/emoji_picker.dart'; +import 'package:appflowy/shared/icon_emoji_picker/emoji_skin_tone.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flowy_infra/size.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/flowy_tooltip.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_emoji_mart/flutter_emoji_mart.dart'; + +import 'emoji_menu.dart'; + +class EmojiHandler extends StatefulWidget { + const EmojiHandler({ + super.key, + required this.editorState, + required this.menuService, + required this.onDismiss, + required this.onSelectionUpdate, + required this.onEmojiSelect, + this.cancelBySpaceHandler, + this.initialSearchText = '', + }); + + final EditorState editorState; + final EmojiMenuService menuService; + final VoidCallback onDismiss; + final VoidCallback onSelectionUpdate; + final SelectEmojiItemHandler onEmojiSelect; + final String initialSearchText; + final bool Function()? cancelBySpaceHandler; + + @override + State createState() => _EmojiHandlerState(); +} + +class _EmojiHandlerState extends State { + final focusNode = FocusNode(debugLabel: 'emoji_menu_handler'); + final scrollController = ScrollController(); + late EmojiData emojiData; + final List searchedEmojis = []; + bool loaded = false; + int invalidCounter = 0; + late int startOffset; + late String _search = widget.initialSearchText; + double emojiHeight = 36.0; + final configuration = EmojiPickerConfiguration( + defaultSkinTone: lastSelectedEmojiSkinTone ?? EmojiSkinTone.none, + ); + + int get startCharAmount => widget.initialSearchText.length; + + set search(String search) { + _search = search; + _doSearch(); + } + + final ValueNotifier selectedIndexNotifier = ValueNotifier(0); + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback( + (_) => focusNode.requestFocus(), + ); + + startOffset = + (widget.editorState.selection?.endIndex ?? 0) - startCharAmount; + + if (kCachedEmojiData != null) { + loadEmojis(kCachedEmojiData!); + } else { + EmojiData.builtIn().then( + (value) { + kCachedEmojiData = value; + loadEmojis(value); + }, + ); + } + } + + @override + void dispose() { + focusNode.dispose(); + selectedIndexNotifier.dispose(); + scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final noEmojis = searchedEmojis.isEmpty; + return Focus( + focusNode: focusNode, + onKeyEvent: onKeyEvent, + child: Container( + constraints: const BoxConstraints(maxHeight: 392, maxWidth: 360), + padding: const EdgeInsets.symmetric(vertical: 16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6.0), + color: Theme.of(context).cardColor, + boxShadow: [ + BoxShadow( + blurRadius: 5, + spreadRadius: 1, + color: Colors.black.withAlpha(25), + ), + ], + ), + child: noEmojis ? buildLoading() : buildEmojis(), + ), + ); + } + + Widget buildLoading() { + return SizedBox( + width: 400, + height: 40, + child: Center( + child: SizedBox.square( + dimension: 20, + child: CircularProgressIndicator(), + ), + ), + ); + } + + Widget buildEmojis() { + return SizedBox( + height: + (searchedEmojis.length / configuration.perLine).ceil() * emojiHeight, + child: GridView.builder( + controller: scrollController, + itemCount: searchedEmojis.length, + padding: const EdgeInsets.symmetric(horizontal: 16), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: configuration.perLine, + ), + itemBuilder: (context, index) { + final currentEmoji = searchedEmojis[index]; + final emojiId = currentEmoji.id; + final emoji = emojiData.getEmojiById( + emojiId, + skinTone: configuration.defaultSkinTone, + ); + return ValueListenableBuilder( + valueListenable: selectedIndexNotifier, + builder: (context, value, child) { + final isSelected = value == index; + return SizedBox.square( + dimension: emojiHeight, + child: FlowyButton( + isSelected: isSelected, + margin: EdgeInsets.zero, + radius: Corners.s8Border, + text: ManualTooltip( + key: ValueKey('$emojiId-$isSelected'), + message: currentEmoji.name, + showAutomaticlly: isSelected, + preferBelow: false, + child: FlowyText.emoji( + emoji, + fontSize: configuration.emojiSize, + ), + ), + onTap: () => onSelect(index), + ), + ); + }, + ); + }, + ), + ); + } + + void changeSelectedIndex(int index) => selectedIndexNotifier.value = index; + + void loadEmojis(EmojiData data) { + emojiData = data; + searchedEmojis.clear(); + searchedEmojis.addAll(emojiData.emojis.values); + if (mounted) { + setState(() { + loaded = true; + }); + } + WidgetsBinding.instance.addPostFrameCallback((_) { + _doSearch(); + }); + } + + void _doSearch() { + if (!loaded || !mounted) return; + final enableEmptySearch = widget.initialSearchText.isEmpty; + if ((_search.startsWith(' ') || _search.isEmpty) && !enableEmptySearch) { + widget.onDismiss.call(); + return; + } + final searchEmojiData = emojiData.filterByKeyword(_search); + setState(() { + searchedEmojis.clear(); + searchedEmojis.addAll(searchEmojiData.emojis.values); + changeSelectedIndex(0); + _scrollToItem(); + }); + if (searchedEmojis.isEmpty) { + widget.onDismiss.call(); + } + } + + KeyEventResult onKeyEvent(focus, KeyEvent event) { + if (event is! KeyDownEvent && event is! KeyRepeatEvent) { + return KeyEventResult.ignored; + } + + const moveKeys = [ + LogicalKeyboardKey.arrowUp, + LogicalKeyboardKey.arrowDown, + LogicalKeyboardKey.arrowLeft, + LogicalKeyboardKey.arrowRight, + ]; + + if (event.logicalKey == LogicalKeyboardKey.enter) { + onSelect(selectedIndexNotifier.value); + return KeyEventResult.handled; + } else if (event.logicalKey == LogicalKeyboardKey.escape) { + // Workaround to bring focus back to editor + widget.editorState + .updateSelectionWithReason(widget.editorState.selection); + widget.onDismiss.call(); + } else if (event.logicalKey == LogicalKeyboardKey.backspace) { + if (_search.isEmpty) { + if (widget.initialSearchText.isEmpty) { + widget.onDismiss.call(); + return KeyEventResult.handled; + } + if (_canDeleteLastCharacter()) { + widget.editorState.deleteBackward(); + } else { + // Workaround for editor regaining focus + widget.editorState.apply( + widget.editorState.transaction + ..afterSelection = widget.editorState.selection, + ); + } + widget.onDismiss.call(); + } else { + widget.onSelectionUpdate(); + widget.editorState.deleteBackward(); + _deleteCharacterAtSelection(); + } + + return KeyEventResult.handled; + } else if (event.character != null && + !moveKeys.contains(event.logicalKey)) { + /// Prevents dismissal of context menu by notifying the parent + /// that the selection change occurred from the handler. + widget.onSelectionUpdate(); + + if (event.logicalKey == LogicalKeyboardKey.space) { + final cancelBySpaceHandler = widget.cancelBySpaceHandler; + if (cancelBySpaceHandler != null && cancelBySpaceHandler()) { + return KeyEventResult.handled; + } + } + + // Interpolation to avoid having a getter for private variable + _insertCharacter(event.character!); + return KeyEventResult.handled; + } else if (moveKeys.contains(event.logicalKey)) { + _moveSelection(event.logicalKey); + return KeyEventResult.handled; + } + + return KeyEventResult.handled; + } + + void onSelect(int index) { + widget.onEmojiSelect.call( + context, + (startOffset - startCharAmount, startOffset + _search.length), + emojiData.getEmojiById(searchedEmojis[index].id), + ); + widget.onDismiss.call(); + } + + void _insertCharacter(String character) { + widget.editorState.insertTextAtCurrentSelection(character); + + final selection = widget.editorState.selection; + if (selection == null || !selection.isCollapsed) { + return; + } + + final delta = widget.editorState.getNodeAtPath(selection.end.path)?.delta; + if (delta == null) { + return; + } + + search = widget.editorState + .getTextInSelection( + selection.copyWith( + start: selection.start.copyWith(offset: startOffset), + end: selection.start + .copyWith(offset: startOffset + _search.length + 1), + ), + ) + .join(); + } + + void _moveSelection(LogicalKeyboardKey key) { + final index = selectedIndexNotifier.value, + perLine = configuration.perLine, + remainder = index % perLine, + length = searchedEmojis.length, + currentLine = index ~/ perLine, + maxLine = (length / perLine).ceil(); + + final heightBefore = currentLine * emojiHeight; + if (key == LogicalKeyboardKey.arrowUp) { + if (currentLine == 0) { + final exceptLine = max(0, maxLine - 1); + changeSelectedIndex(min(exceptLine * perLine + remainder, length - 1)); + } else if (currentLine > 0) { + changeSelectedIndex(index - perLine); + } + } else if (key == LogicalKeyboardKey.arrowDown) { + if (currentLine == maxLine - 1) { + changeSelectedIndex(remainder); + } else if (currentLine < maxLine - 1) { + changeSelectedIndex(min(index + perLine, length - 1)); + } + } else if (key == LogicalKeyboardKey.arrowLeft) { + if (index == 0) { + changeSelectedIndex(length - 1); + } else if (index > 0) { + changeSelectedIndex(index - 1); + } + } else if (key == LogicalKeyboardKey.arrowRight) { + if (index == length - 1) { + changeSelectedIndex(0); + } else if (index < length - 1) { + changeSelectedIndex(index + 1); + } + } + final heightAfter = + (selectedIndexNotifier.value ~/ configuration.perLine) * emojiHeight; + + if (mounted && (heightAfter != heightBefore)) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _scrollToItem(); + }); + } + } + + void _scrollToItem() { + final noEmojis = searchedEmojis.isEmpty; + if (noEmojis || !mounted) return; + final currentItem = selectedIndexNotifier.value; + final exceptHeight = (currentItem ~/ configuration.perLine) * emojiHeight; + final maxExtent = scrollController.position.maxScrollExtent; + final jumpTo = (exceptHeight - maxExtent > 10 * emojiHeight) + ? exceptHeight + : min(exceptHeight, maxExtent); + scrollController.animateTo( + jumpTo, + duration: Duration(milliseconds: 300), + curve: Curves.linear, + ); + } + + void _deleteCharacterAtSelection() { + final selection = widget.editorState.selection; + if (selection == null || !selection.isCollapsed) { + return; + } + + final node = widget.editorState.getNodeAtPath(selection.end.path); + final delta = node?.delta; + if (node == null || delta == null) { + return; + } + + search = delta.toPlainText().substring( + startOffset, + startOffset + _search.length - 1, + ); + } + + bool _canDeleteLastCharacter() { + final selection = widget.editorState.selection; + if (selection == null || !selection.isCollapsed) { + return false; + } + + final delta = widget.editorState.getNodeAtPath(selection.start.path)?.delta; + if (delta == null) { + return false; + } + + return delta.isNotEmpty; + } +} + +typedef SelectEmojiItemHandler = void Function( + BuildContext context, + (int start, int end) replacement, + String emoji, +); diff --git a/frontend/appflowy_flutter/lib/plugins/emoji/emoji_menu.dart b/frontend/appflowy_flutter/lib/plugins/emoji/emoji_menu.dart new file mode 100644 index 0000000000..29f130d77d --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/emoji/emoji_menu.dart @@ -0,0 +1,231 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter/material.dart'; + +import 'emoji_actions_command.dart'; +import 'emoji_handler.dart'; + +abstract class EmojiMenuService { + void show(String character); + + void dismiss(); +} + +class EmojiMenu extends EmojiMenuService { + EmojiMenu({ + required this.overlay, + required this.editorState, + this.cancelBySpaceHandler, + this.menuHeight = 400, + this.menuWidth = 300, + }); + + final EditorState editorState; + final double menuHeight; + final double menuWidth; + final OverlayState overlay; + final bool Function()? cancelBySpaceHandler; + + Offset _offset = Offset.zero; + Alignment _alignment = Alignment.topLeft; + OverlayEntry? _menuEntry; + bool selectionChangedByMenu = false; + String initialCharacter = ''; + + @override + void dismiss() { + if (_menuEntry != null) { + editorState.service.keyboardService?.enable(); + editorState.service.scrollService?.enable(); + keepEditorFocusNotifier.decrease(); + } + + _menuEntry?.remove(); + _menuEntry = null; + + // workaround: SelectionService has been released after hot reload. + final isSelectionDisposed = + editorState.service.selectionServiceKey.currentState == null; + if (!isSelectionDisposed) { + final selectionService = editorState.service.selectionService; + selectionService.currentSelection.removeListener(_onSelectionChange); + } + emojiMenuService = null; + } + + void _onSelectionUpdate() => selectionChangedByMenu = true; + + @override + void show(String character) { + initialCharacter = character; + WidgetsBinding.instance.addPostFrameCallback((_) => _show()); + } + + void _show() { + final selectionService = editorState.service.selectionService; + final selectionRects = selectionService.selectionRects; + if (selectionRects.isEmpty) { + return; + } + + final Size editorSize = editorState.renderBox!.size; + + calculateSelectionMenuOffset(selectionRects.first); + + final (left, top, right, bottom) = _getPosition(); + + _menuEntry = OverlayEntry( + builder: (context) => SizedBox( + height: editorSize.height, + width: editorSize.width, + + // GestureDetector handles clicks outside of the context menu, + // to dismiss the context menu. + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: dismiss, + child: Stack( + children: [ + Positioned( + top: top, + bottom: bottom, + left: left, + right: right, + child: EmojiHandler( + editorState: editorState, + menuService: this, + onDismiss: dismiss, + onSelectionUpdate: _onSelectionUpdate, + cancelBySpaceHandler: cancelBySpaceHandler, + initialSearchText: initialCharacter, + onEmojiSelect: ( + BuildContext context, + (int, int) replacement, + String emoji, + ) async { + final selection = editorState.selection; + + if (selection == null) return; + final node = + editorState.document.nodeAtPath(selection.end.path); + if (node == null) return; + final transaction = editorState.transaction + ..deleteText( + node, + replacement.$1, + replacement.$2 - replacement.$1, + ) + ..insertText( + node, + replacement.$1, + emoji, + ); + await editorState.apply(transaction); + }, + ), + ), + ], + ), + ), + ), + ); + + overlay.insert(_menuEntry!); + + keepEditorFocusNotifier.increase(); + editorState.service.keyboardService?.disable(showCursor: true); + editorState.service.scrollService?.disable(); + selectionService.currentSelection.addListener(_onSelectionChange); + } + + void _onSelectionChange() { + // workaround: SelectionService has been released after hot reload. + final isSelectionDisposed = + editorState.service.selectionServiceKey.currentState == null; + if (!isSelectionDisposed) { + final selectionService = editorState.service.selectionService; + if (selectionService.currentSelection.value == null) { + return; + } + } + + if (!selectionChangedByMenu) { + return dismiss(); + } + + selectionChangedByMenu = false; + } + + (double? left, double? top, double? right, double? bottom) _getPosition() { + double? left, top, right, bottom; + switch (_alignment) { + case Alignment.topLeft: + left = _offset.dx; + top = _offset.dy; + break; + case Alignment.bottomLeft: + left = _offset.dx; + bottom = _offset.dy; + break; + case Alignment.topRight: + right = _offset.dx; + top = _offset.dy; + break; + case Alignment.bottomRight: + right = _offset.dx; + bottom = _offset.dy; + break; + } + + return (left, top, right, bottom); + } + + void calculateSelectionMenuOffset(Rect rect) { + // Workaround: We can customize the padding through the [EditorStyle], + // but the coordinates of overlay are not properly converted currently. + // Just subtract the padding here as a result. + const menuOffset = Offset(0, 10); + final editorOffset = + editorState.renderBox?.localToGlobal(Offset.zero) ?? Offset.zero; + final editorHeight = editorState.renderBox!.size.height; + final editorWidth = editorState.renderBox!.size.width; + + // show below default + _alignment = Alignment.topLeft; + final bottomRight = rect.bottomRight; + final topRight = rect.topRight; + var offset = bottomRight + menuOffset; + _offset = Offset( + offset.dx, + offset.dy, + ); + + // show above + if (offset.dy + menuHeight >= editorOffset.dy + editorHeight) { + offset = topRight - menuOffset; + _alignment = Alignment.bottomLeft; + + _offset = Offset( + offset.dx, + editorHeight + editorOffset.dy - offset.dy, + ); + } + + // show on right + if (_offset.dx + menuWidth < editorOffset.dx + editorWidth) { + _offset = Offset( + _offset.dx, + _offset.dy, + ); + } else if (offset.dx - editorOffset.dx > menuWidth) { + // show on left + _alignment = _alignment == Alignment.topLeft + ? Alignment.topRight + : Alignment.bottomRight; + + _offset = Offset( + editorWidth - _offset.dx + editorOffset.dx, + _offset.dy, + ); + } + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/inline_actions/handlers/child_page.dart b/frontend/appflowy_flutter/lib/plugins/inline_actions/handlers/child_page.dart index d29a1f86bf..6dbd38affb 100644 --- a/frontend/appflowy_flutter/lib/plugins/inline_actions/handlers/child_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/inline_actions/handlers/child_page.dart @@ -24,7 +24,7 @@ class InlineChildPageService extends InlineActionsDelegate { results.add( InlineActionsMenuItem( label: LocaleKeys.inlineActions_createPage.tr(args: [search]), - icon: (_) => const FlowySvg(FlowySvgs.add_s), + iconBuilder: (_) => const FlowySvg(FlowySvgs.add_s), onSelected: (context, editorState, service, replacement) => _onSelected(context, editorState, service, replacement, search), ), @@ -71,12 +71,11 @@ class InlineChildPageService extends InlineActionsDelegate { replacement.$1, replacement.$2, MentionBlockKeys.mentionChar, - attributes: { - MentionBlockKeys.mention: { - MentionBlockKeys.type: MentionType.childPage.name, - MentionBlockKeys.pageId: view.id, - }, - }, + attributes: MentionBlockKeys.buildMentionPageAttributes( + mentionType: MentionType.childPage, + pageId: view.id, + blockId: null, + ), ); await editorState.apply(transaction); diff --git a/frontend/appflowy_flutter/lib/plugins/inline_actions/handlers/date_reference.dart b/frontend/appflowy_flutter/lib/plugins/inline_actions/handlers/date_reference.dart index 904e10d362..747c8667f8 100644 --- a/frontend/appflowy_flutter/lib/plugins/inline_actions/handlers/date_reference.dart +++ b/frontend/appflowy_flutter/lib/plugins/inline_actions/handlers/date_reference.dart @@ -122,12 +122,12 @@ class DateReferenceService extends InlineActionsDelegate { start, end, MentionBlockKeys.mentionChar, - attributes: { - MentionBlockKeys.mention: { - MentionBlockKeys.type: MentionType.date.name, - MentionBlockKeys.date: date.toIso8601String(), - }, - }, + attributes: MentionBlockKeys.buildMentionDateAttributes( + date: date.toIso8601String(), + includeTime: false, + reminderId: null, + reminderOption: null, + ), ); await editorState.apply(transaction); @@ -138,22 +138,36 @@ class DateReferenceService extends InlineActionsDelegate { final tomorrow = today.add(const Duration(days: 1)); final yesterday = today.subtract(const Duration(days: 1)); - _allOptions = [ - _itemFromDate( + late InlineActionsMenuItem todayItem; + late InlineActionsMenuItem tomorrowItem; + late InlineActionsMenuItem yesterdayItem; + + try { + todayItem = _itemFromDate( today, LocaleKeys.relativeDates_today.tr(), [DateFormat.yMd(_locale).format(today)], - ), - _itemFromDate( + ); + tomorrowItem = _itemFromDate( tomorrow, LocaleKeys.relativeDates_tomorrow.tr(), [DateFormat.yMd(_locale).format(tomorrow)], - ), - _itemFromDate( + ); + yesterdayItem = _itemFromDate( yesterday, LocaleKeys.relativeDates_yesterday.tr(), [DateFormat.yMd(_locale).format(yesterday)], - ), + ); + } catch (e) { + todayItem = _itemFromDate(today); + tomorrowItem = _itemFromDate(tomorrow); + yesterdayItem = _itemFromDate(yesterday); + } + + _allOptions = [ + todayItem, + tomorrowItem, + yesterdayItem, ]; } @@ -173,7 +187,17 @@ class DateReferenceService extends InlineActionsDelegate { String? label, List? keywords, ]) { - final labelStr = label ?? DateFormat.yMd(_locale).format(date); + late String labelStr; + if (label != null) { + labelStr = label; + } else { + try { + labelStr = DateFormat.yMd(_locale).format(date); + } catch (e) { + // fallback to en-US + labelStr = DateFormat.yMd('en-US').format(date); + } + } return InlineActionsMenuItem( label: labelStr.capitalize(), diff --git a/frontend/appflowy_flutter/lib/plugins/inline_actions/handlers/inline_page_reference.dart b/frontend/appflowy_flutter/lib/plugins/inline_actions/handlers/inline_page_reference.dart index ff9e751e3f..9853d6757c 100644 --- a/frontend/appflowy_flutter/lib/plugins/inline_actions/handlers/inline_page_reference.dart +++ b/frontend/appflowy_flutter/lib/plugins/inline_actions/handlers/inline_page_reference.dart @@ -221,12 +221,11 @@ class InlinePageReferenceService extends InlineActionsDelegate { replace.$1, replace.$2, MentionBlockKeys.mentionChar, - attributes: { - MentionBlockKeys.mention: { - MentionBlockKeys.type: MentionType.page.name, - MentionBlockKeys.pageId: view.id, - }, - }, + attributes: MentionBlockKeys.buildMentionPageAttributes( + mentionType: MentionType.page, + pageId: view.id, + blockId: null, + ), ); await editorState.apply(transaction); @@ -235,12 +234,19 @@ class InlinePageReferenceService extends InlineActionsDelegate { InlineActionsMenuItem _fromView(ViewPB view) => InlineActionsMenuItem( keywords: [view.nameOrDefault.toLowerCase()], label: view.nameOrDefault, - icon: (onSelected) => view.icon.value.isNotEmpty - ? EmojiIconWidget( - emoji: view.icon.toEmojiIconData(), - emojiSize: 14, - ) - : view.defaultIcon(), + iconBuilder: (onSelected) { + final child = view.icon.value.isNotEmpty + ? RawEmojiIconWidget( + emoji: view.icon.toEmojiIconData(), + emojiSize: 16.0, + lineHeight: 18.0 / 16.0, + ) + : view.defaultIcon(size: const Size(16, 16)); + return SizedBox( + width: 16, + child: child, + ); + }, onSelected: (context, editorState, menu, replace) => insertPage ? _onInsertPageRef(view, context, editorState, replace) : _onInsertLinkRef(view, context, editorState, menu, replace), diff --git a/frontend/appflowy_flutter/lib/plugins/inline_actions/handlers/reminder_reference.dart b/frontend/appflowy_flutter/lib/plugins/inline_actions/handlers/reminder_reference.dart index 372cc78698..471f1c9211 100644 --- a/frontend/appflowy_flutter/lib/plugins/inline_actions/handlers/reminder_reference.dart +++ b/frontend/appflowy_flutter/lib/plugins/inline_actions/handlers/reminder_reference.dart @@ -148,14 +148,12 @@ class ReminderReferenceService extends InlineActionsDelegate { start, end, MentionBlockKeys.mentionChar, - attributes: { - MentionBlockKeys.mention: { - MentionBlockKeys.type: MentionType.date.name, - MentionBlockKeys.date: date.toIso8601String(), - MentionBlockKeys.reminderId: reminder.id, - MentionBlockKeys.reminderOption: ReminderOption.atTimeOfEvent.name, - }, - }, + attributes: MentionBlockKeys.buildMentionDateAttributes( + date: date.toIso8601String(), + reminderId: reminder.id, + reminderOption: ReminderOption.atTimeOfEvent.name, + includeTime: false, + ), ); await editorState.apply(transaction); @@ -170,17 +168,32 @@ class ReminderReferenceService extends InlineActionsDelegate { final tomorrow = today.add(const Duration(days: 1)); final oneWeek = today.add(const Duration(days: 7)); - _allOptions = [ - _itemFromDate( + late InlineActionsMenuItem todayItem; + late InlineActionsMenuItem oneWeekItem; + + try { + todayItem = _itemFromDate( tomorrow, LocaleKeys.relativeDates_tomorrow.tr(), [DateFormat.yMd(_locale).format(tomorrow)], - ), - _itemFromDate( + ); + } catch (e) { + todayItem = _itemFromDate(today); + } + + try { + oneWeekItem = _itemFromDate( oneWeek, LocaleKeys.relativeDates_oneWeek.tr(), [DateFormat.yMd(_locale).format(oneWeek)], - ), + ); + } catch (e) { + oneWeekItem = _itemFromDate(oneWeek); + } + + _allOptions = [ + todayItem, + oneWeekItem, ]; } @@ -200,7 +213,17 @@ class ReminderReferenceService extends InlineActionsDelegate { String? label, List? keywords, ]) { - final labelStr = label ?? DateFormat.yMd(_locale).format(date); + late String labelStr; + if (label != null) { + labelStr = label; + } else { + try { + labelStr = DateFormat.yMd(_locale).format(date); + } catch (e) { + // fallback to en-US + labelStr = DateFormat.yMd('en-US').format(date); + } + } return InlineActionsMenuItem( label: labelStr.capitalize(), diff --git a/frontend/appflowy_flutter/lib/plugins/inline_actions/inline_actions_command.dart b/frontend/appflowy_flutter/lib/plugins/inline_actions/inline_actions_command.dart index 0ef4ac7c09..e0e03e7dec 100644 --- a/frontend/appflowy_flutter/lib/plugins/inline_actions/inline_actions_command.dart +++ b/frontend/appflowy_flutter/lib/plugins/inline_actions/inline_actions_command.dart @@ -1,3 +1,4 @@ +import 'package:appflowy/mobile/presentation/inline_actions/mobile_inline_actions_menu.dart'; import 'package:appflowy/plugins/inline_actions/inline_actions_menu.dart'; import 'package:appflowy/plugins/inline_actions/inline_actions_result.dart'; import 'package:appflowy/plugins/inline_actions/inline_actions_service.dart'; @@ -21,13 +22,14 @@ CharacterShortcutEvent inlineActionsCommand( ); InlineActionsMenuService? selectionMenuService; + Future inlineActionsCommandHandler( EditorState editorState, InlineActionsService service, InlineActionsMenuStyle style, ) async { final selection = editorState.selection; - if (UniversalPlatform.isMobile || selection == null) { + if (selection == null) { return false; } @@ -50,15 +52,31 @@ Future inlineActionsCommandHandler( } if (service.context != null) { - selectionMenuService = InlineActionsMenu( - context: service.context!, - editorState: editorState, - service: service, - initialResults: initialResults, - style: style, - ); + keepEditorFocusNotifier.increase(); + selectionMenuService?.dismiss(); + selectionMenuService = UniversalPlatform.isMobile + ? MobileInlineActionsMenu( + context: service.context!, + editorState: editorState, + service: service, + initialResults: initialResults, + style: style, + ) + : InlineActionsMenu( + context: service.context!, + editorState: editorState, + service: service, + initialResults: initialResults, + style: style, + ); - selectionMenuService?.show(); + // disable the keyboard service + editorState.service.keyboardService?.disable(); + + await selectionMenuService?.show(); + + // enable the keyboard service + editorState.service.keyboardService?.enable(); } return true; diff --git a/frontend/appflowy_flutter/lib/plugins/inline_actions/inline_actions_menu.dart b/frontend/appflowy_flutter/lib/plugins/inline_actions/inline_actions_menu.dart index dadc4ebf6f..651e739abc 100644 --- a/frontend/appflowy_flutter/lib/plugins/inline_actions/inline_actions_menu.dart +++ b/frontend/appflowy_flutter/lib/plugins/inline_actions/inline_actions_menu.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:appflowy/plugins/inline_actions/inline_actions_result.dart'; import 'package:appflowy/plugins/inline_actions/inline_actions_service.dart'; import 'package:appflowy/plugins/inline_actions/widgets/inline_actions_handler.dart'; @@ -7,7 +9,8 @@ import 'package:flutter/material.dart'; abstract class InlineActionsMenuService { InlineActionsMenuStyle get style; - void show(); + Future show(); + void dismiss(); } @@ -59,8 +62,13 @@ class InlineActionsMenu extends InlineActionsMenuService { void _onSelectionUpdate() => selectionChangedByMenu = true; @override - void show() { - WidgetsBinding.instance.addPostFrameCallback((_) => _show()); + Future show() { + final completer = Completer(); + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + _show(); + completer.complete(); + }); + return completer.future; } void _show() { diff --git a/frontend/appflowy_flutter/lib/plugins/inline_actions/inline_actions_result.dart b/frontend/appflowy_flutter/lib/plugins/inline_actions/inline_actions_result.dart index 8da9647084..1fe2703870 100644 --- a/frontend/appflowy_flutter/lib/plugins/inline_actions/inline_actions_result.dart +++ b/frontend/appflowy_flutter/lib/plugins/inline_actions/inline_actions_result.dart @@ -12,13 +12,13 @@ typedef SelectItemHandler = void Function( class InlineActionsMenuItem { InlineActionsMenuItem({ required this.label, - this.icon, + this.iconBuilder, this.keywords, this.onSelected, }); final String label; - final Widget Function(bool onSelected)? icon; + final Widget Function(bool onSelected)? iconBuilder; final List? keywords; final SelectItemHandler? onSelected; } diff --git a/frontend/appflowy_flutter/lib/plugins/inline_actions/widgets/inline_actions_handler.dart b/frontend/appflowy_flutter/lib/plugins/inline_actions/widgets/inline_actions_handler.dart index be9a6c2f5f..63ccb04839 100644 --- a/frontend/appflowy_flutter/lib/plugins/inline_actions/widgets/inline_actions_handler.dart +++ b/frontend/appflowy_flutter/lib/plugins/inline_actions/widgets/inline_actions_handler.dart @@ -165,7 +165,7 @@ class _InlineActionsHandlerState extends State { BoxShadow( blurRadius: 5, spreadRadius: 1, - color: Colors.black.withOpacity(0.1), + color: Colors.black.withValues(alpha: 0.1), ), ], ), diff --git a/frontend/appflowy_flutter/lib/plugins/inline_actions/widgets/inline_actions_menu_group.dart b/frontend/appflowy_flutter/lib/plugins/inline_actions/widgets/inline_actions_menu_group.dart index 6179d3d18b..123cfc1177 100644 --- a/frontend/appflowy_flutter/lib/plugins/inline_actions/widgets/inline_actions_menu_group.dart +++ b/frontend/appflowy_flutter/lib/plugins/inline_actions/widgets/inline_actions_menu_group.dart @@ -92,6 +92,8 @@ class InlineActionsWidget extends StatefulWidget { class _InlineActionsWidgetState extends State { @override Widget build(BuildContext context) { + final iconBuilder = widget.item.iconBuilder; + final hasIcon = iconBuilder != null; return Padding( padding: const EdgeInsets.symmetric(vertical: 2), child: SizedBox( @@ -99,11 +101,20 @@ class _InlineActionsWidgetState extends State { child: FlowyButton( expand: true, isSelected: widget.isSelected, - leftIcon: widget.item.icon?.call(widget.isSelected), - text: FlowyText.regular( - widget.item.label, - figmaLineHeight: 18, - overflow: TextOverflow.ellipsis, + text: Row( + children: [ + if (hasIcon) ...[ + iconBuilder.call(widget.isSelected), + SizedBox(width: 12), + ], + Flexible( + child: FlowyText.regular( + widget.item.label, + figmaLineHeight: 18, + overflow: TextOverflow.ellipsis, + ), + ), + ], ), onTap: _onPressed, ), diff --git a/frontend/appflowy_flutter/lib/plugins/shared/share/constants.dart b/frontend/appflowy_flutter/lib/plugins/shared/share/constants.dart index 2be63c5879..649d7c0883 100644 --- a/frontend/appflowy_flutter/lib/plugins/shared/share/constants.dart +++ b/frontend/appflowy_flutter/lib/plugins/shared/share/constants.dart @@ -1,19 +1,30 @@ +import 'package:appflowy/env/cloud_env.dart'; +import 'package:appflowy/startup/startup.dart'; + class ShareConstants { - static const String publishBaseUrl = 'appflowy.com'; - static const String shareBaseUrl = 'appflowy.com/app'; + static const String testBaseWebDomain = 'test.appflowy.com'; + static const String defaultBaseWebDomain = 'https://appflowy.com'; static String buildPublishUrl({ required String nameSpace, required String publishName, }) { - return 'https://$publishBaseUrl/$nameSpace/$publishName'; + final baseShareDomain = + getIt().appflowyCloudConfig.base_web_domain; + final url = '$baseShareDomain/$nameSpace/$publishName'.addSchemaIfNeeded(); + return url; } static String buildNamespaceUrl({ required String nameSpace, bool withHttps = false, }) { - final url = withHttps ? 'https://$publishBaseUrl' : publishBaseUrl; + final baseShareDomain = + getIt().appflowyCloudConfig.base_web_domain; + String url = baseShareDomain.addSchemaIfNeeded(); + if (!withHttps) { + url = url.replaceFirst('https://', ''); + } return '$url/$nameSpace'; } @@ -22,10 +33,23 @@ class ShareConstants { required String viewId, String? blockId, }) { - final url = 'https://$shareBaseUrl/$workspaceId/$viewId'; + final baseShareDomain = + getIt().appflowyCloudConfig.base_web_domain; + final url = '$baseShareDomain/app/$workspaceId/$viewId'.addSchemaIfNeeded(); if (blockId == null || blockId.isEmpty) { return url; } return '$url?blockId=$blockId'; } } + +extension on String { + String addSchemaIfNeeded() { + final schema = Uri.parse(this).scheme; + // if the schema is empty, add https schema by default + if (schema.isEmpty) { + return 'https://$this'; + } + return this; + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/shared/share/export_tab.dart b/frontend/appflowy_flutter/lib/plugins/shared/share/export_tab.dart index ff95fe6acc..9d6adee7df 100644 --- a/frontend/appflowy_flutter/lib/plugins/shared/share/export_tab.dart +++ b/frontend/appflowy_flutter/lib/plugins/shared/share/export_tab.dart @@ -70,7 +70,7 @@ class ExportTab extends StatelessWidget { const VSpace(10), _ExportButton( title: LocaleKeys.shareAction_csv.tr(), - svg: FlowySvgs.database_layout_m, + svg: FlowySvgs.database_layout_s, onTap: () => _exportCSV(context), ), if (kDebugMode) ...[ @@ -105,7 +105,7 @@ class ExportTab extends StatelessWidget { final viewName = context.read().state.viewName; final exportPath = await getIt().saveFile( dialogTitle: '', - fileName: '${viewName.toFileName()}.md', + fileName: '${viewName.toFileName()}.zip', ); if (context.mounted && exportPath != null) { context.read().add( @@ -174,11 +174,10 @@ class ExportTab extends StatelessWidget { ClipboardServiceData(plainText: markdown), ); showToastNotification( - context, - message: LocaleKeys.grid_url_copiedNotification.tr(), + message: LocaleKeys.message_copy_success.tr(), ); }, - (error) => showToastNotification(context, message: error.msg), + (error) => showToastNotification(message: error.msg), ); } } @@ -198,7 +197,7 @@ class _ExportButton extends StatelessWidget { Widget build(BuildContext context) { final color = Theme.of(context).isLightMode ? const Color(0x1E14171B) - : Colors.white.withOpacity(0.1); + : Colors.white.withValues(alpha: 0.1); final radius = BorderRadius.circular(10.0); return FlowyButton( margin: const EdgeInsets.symmetric(vertical: 10, horizontal: 14), diff --git a/frontend/appflowy_flutter/lib/plugins/shared/share/publish_color_extension.dart b/frontend/appflowy_flutter/lib/plugins/shared/share/publish_color_extension.dart index 960f59b07d..1c957016e4 100644 --- a/frontend/appflowy_flutter/lib/plugins/shared/share/publish_color_extension.dart +++ b/frontend/appflowy_flutter/lib/plugins/shared/share/publish_color_extension.dart @@ -5,7 +5,7 @@ class ShareMenuColors { static Color borderColor(BuildContext context) { final borderColor = Theme.of(context).isLightMode ? const Color(0x1E14171B) - : Colors.white.withOpacity(0.1); + : Colors.white.withValues(alpha: 0.1); return borderColor; } } diff --git a/frontend/appflowy_flutter/lib/plugins/shared/share/publish_tab.dart b/frontend/appflowy_flutter/lib/plugins/shared/share/publish_tab.dart index 68d1918c7f..244ded0bf6 100644 --- a/frontend/appflowy_flutter/lib/plugins/shared/share/publish_tab.dart +++ b/frontend/appflowy_flutter/lib/plugins/shared/share/publish_tab.dart @@ -85,11 +85,9 @@ class PublishTab extends StatelessWidget { if (state.publishResult != null) { state.publishResult!.fold( (value) => showToastNotification( - context, message: LocaleKeys.publish_publishSuccessfully.tr(), ), (error) => showToastNotification( - context, message: '${LocaleKeys.publish_publishFailed.tr()}: ${error.code}', type: ToastificationType.error, ), @@ -97,11 +95,9 @@ class PublishTab extends StatelessWidget { } else if (state.unpublishResult != null) { state.unpublishResult!.fold( (value) => showToastNotification( - context, message: LocaleKeys.publish_unpublishSuccessfully.tr(), ), (error) => showToastNotification( - context, message: LocaleKeys.publish_unpublishFailed.tr(), description: error.msg, type: ToastificationType.error, @@ -110,14 +106,12 @@ class PublishTab extends StatelessWidget { } else if (state.updatePathNameResult != null) { state.updatePathNameResult!.fold( (value) => showToastNotification( - context, message: LocaleKeys.settings_sites_success_updatePathNameSuccess.tr(), ), (error) { Log.error('update path name failed: $error'); showToastNotification( - context, message: LocaleKeys.settings_sites_error_updatePathNameFailed.tr(), type: ToastificationType.error, description: error.code.publishErrorMessage, @@ -182,8 +176,7 @@ class _PublishedWidgetState extends State<_PublishedWidget> { ); showToastNotification( - context, - message: LocaleKeys.grid_url_copy.tr(), + message: LocaleKeys.message_copy_success.tr(), ); }, onSubmitted: (pathName) { @@ -217,7 +210,7 @@ class _PublishedWidgetState extends State<_PublishedWidget> { title: LocaleKeys.shareAction_visitSite.tr(), borderRadius: const BorderRadius.all(Radius.circular(10)), fillColor: Theme.of(context).colorScheme.primary, - hoverColor: Theme.of(context).colorScheme.primary.withOpacity(0.9), + hoverColor: Theme.of(context).colorScheme.primary.withValues(alpha: 0.9), textColor: Theme.of(context).colorScheme.onPrimary, ); } @@ -292,7 +285,6 @@ class _PublishWidgetState extends State<_PublishWidget> { // check if any database is selected if (_selectedViews.isEmpty) { showToastNotification( - context, message: LocaleKeys.publish_noDatabaseSelected.tr(), ); return; @@ -508,7 +500,7 @@ class _PublishDatabaseSelector extends StatefulWidget { class _PublishDatabaseSelectorState extends State<_PublishDatabaseSelector> { final PropertyValueNotifier> _databaseStatus = PropertyValueNotifier>([]); - late final _borderColor = Theme.of(context).hintColor.withOpacity(0.3); + late final _borderColor = Theme.of(context).hintColor.withValues(alpha: 0.3); @override void initState() { @@ -611,7 +603,6 @@ class _PublishDatabaseSelectorState extends State<_PublishDatabaseSelector> { // unable to deselect the primary database if (isPrimaryDatabase) { showToastNotification( - context, message: LocaleKeys.publish_unableToDeselectPrimaryDatabase.tr(), ); diff --git a/frontend/appflowy_flutter/lib/plugins/shared/share/share_bloc.dart b/frontend/appflowy_flutter/lib/plugins/shared/share/share_bloc.dart index c42bbda5a0..e683518526 100644 --- a/frontend/appflowy_flutter/lib/plugins/shared/share/share_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/shared/share/share_bloc.dart @@ -193,7 +193,7 @@ class ShareBloc extends Bloc { Future _updatePublishStatus(Emitter emit) async { final publishInfo = await ViewBackendService.getPublishInfo(view); final enablePublish = await UserBackendService.getCurrentUserProfile().fold( - (v) => v.authenticator == AuthenticatorPB.AppFlowyCloud, + (v) => v.workspaceAuthType == AuthTypePB.Server, (p) => false, ); @@ -324,19 +324,21 @@ class ShareBloc extends Bloc { (f) => FlowyResult.failure(f), ); } else { - result = await documentExporter.export(type.documentExportType); + result = + await documentExporter.export(type.documentExportType, path: path); } return result.fold( (s) { if (path != null) { switch (type) { - case ShareType.markdown: case ShareType.html: case ShareType.csv: case ShareType.json: case ShareType.rawDatabaseData: File(path).writeAsStringSync(s); return FlowyResult.success(type); + case ShareType.markdown: + return FlowyResult.success(type); default: break; } @@ -387,22 +389,30 @@ enum ShareType { @freezed class ShareEvent with _$ShareEvent { const factory ShareEvent.initial() = _Initial; + const factory ShareEvent.share( ShareType type, String? path, ) = _Share; + const factory ShareEvent.publish( String nameSpace, String pageId, List selectedViewIds, ) = _Publish; + const factory ShareEvent.unPublish() = _UnPublish; + const factory ShareEvent.updateViewName(String name, String viewId) = _UpdateViewName; + const factory ShareEvent.updatePublishStatus() = _UpdatePublishStatus; + const factory ShareEvent.setPublishStatus(bool isPublished) = _SetPublishStatus; + const factory ShareEvent.updatePathName(String pathName) = _UpdatePathName; + const factory ShareEvent.clearPathNameResult() = _ClearPathNameResult; } diff --git a/frontend/appflowy_flutter/lib/plugins/shared/share/share_button.dart b/frontend/appflowy_flutter/lib/plugins/shared/share/share_button.dart index 7f3eff9d34..9020441b4e 100644 --- a/frontend/appflowy_flutter/lib/plugins/shared/share/share_button.dart +++ b/frontend/appflowy_flutter/lib/plugins/shared/share/share_button.dart @@ -30,8 +30,11 @@ class ShareButton extends StatelessWidget { ), if (view.layout.isDatabaseView) BlocProvider( - create: (context) => DatabaseTabBarBloc(view: view) - ..add(const DatabaseTabBarEvent.initial()), + create: (context) => DatabaseTabBarBloc( + view: view, + compactModeId: view.id, + enableCompactMode: false, + )..add(const DatabaseTabBarEvent.initial()), ), ], child: BlocListener( @@ -67,7 +70,6 @@ class ShareButton extends StatelessWidget { case ShareType.html: case ShareType.csv: showToastNotification( - context, message: LocaleKeys.settings_files_exportFileSuccess.tr(), ); break; @@ -78,7 +80,6 @@ class ShareButton extends StatelessWidget { void _handleExportError(BuildContext context, FlowyError error) { showToastNotification( - context, message: '${LocaleKeys.settings_files_exportFileFail.tr()}: ${error.code}', ); diff --git a/frontend/appflowy_flutter/lib/plugins/shared/share/share_tab.dart b/frontend/appflowy_flutter/lib/plugins/shared/share/share_tab.dart index 6980edce46..190fe9ddd8 100644 --- a/frontend/appflowy_flutter/lib/plugins/shared/share/share_tab.dart +++ b/frontend/appflowy_flutter/lib/plugins/shared/share/share_tab.dart @@ -117,8 +117,7 @@ class _ShareTabContent extends StatelessWidget { ); showToastNotification( - context, - message: LocaleKeys.grid_url_copy.tr(), + message: LocaleKeys.message_copy_success.tr(), ); } } diff --git a/frontend/appflowy_flutter/lib/shared/af_user_profile_extension.dart b/frontend/appflowy_flutter/lib/shared/af_user_profile_extension.dart new file mode 100644 index 0000000000..2632c22d49 --- /dev/null +++ b/frontend/appflowy_flutter/lib/shared/af_user_profile_extension.dart @@ -0,0 +1,16 @@ +import 'dart:convert'; + +import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart'; + +extension UserProfilePBExtension on UserProfilePB { + String? get authToken { + try { + final map = jsonDecode(token) as Map; + return map['access_token'] as String?; + } catch (e) { + Log.error('Failed to decode auth token: $e'); + return null; + } + } +} diff --git a/frontend/appflowy_flutter/lib/shared/appflowy_network_image.dart b/frontend/appflowy_flutter/lib/shared/appflowy_network_image.dart index d37b2e0838..090db27ddc 100644 --- a/frontend/appflowy_flutter/lib/shared/appflowy_network_image.dart +++ b/frontend/appflowy_flutter/lib/shared/appflowy_network_image.dart @@ -1,17 +1,21 @@ import 'dart:convert'; +import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/shared/custom_image_cache_manager.dart'; import 'package:appflowy/util/string_extension.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; import 'package:cached_network_image/cached_network_image.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/uuid.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:string_validator/string_validator.dart'; /// This widget handles the downloading and caching of either internal or network images. -/// /// It will append the access token to the URL if the URL is internal. -class FlowyNetworkImage extends StatelessWidget { +class FlowyNetworkImage extends StatefulWidget { const FlowyNetworkImage({ super.key, this.userProfilePB, @@ -21,57 +25,252 @@ class FlowyNetworkImage extends StatelessWidget { this.progressIndicatorBuilder, this.errorWidgetBuilder, required this.url, + this.maxRetries = 5, + this.retryDuration = const Duration(seconds: 6), + this.retryErrorCodes = const {404}, + this.onImageLoaded, }); - final UserProfilePB? userProfilePB; + /// The URL of the image. final String url; + + /// The width of the image. final double? width; + + /// The height of the image. final double? height; + + /// The fit of the image. final BoxFit fit; + + /// The user profile. + /// + /// If the userProfilePB is not null, the image will be downloaded with the access token. + final UserProfilePB? userProfilePB; + + /// The progress indicator builder. final ProgressIndicatorBuilder? progressIndicatorBuilder; + + /// The error widget builder. final LoadingErrorWidgetBuilder? errorWidgetBuilder; + /// Retry loading the image if it fails. + final int maxRetries; + + /// Retry duration + final Duration retryDuration; + + /// Retry error codes. + final Set retryErrorCodes; + + final void Function(bool isImageInCache)? onImageLoaded; + + @override + FlowyNetworkImageState createState() => FlowyNetworkImageState(); +} + +class FlowyNetworkImageState extends State { + final manager = CustomImageCacheManager(); + final retryCounter = FlowyNetworkRetryCounter(); + + // This is used to clear the retry count when the widget is disposed in case of the url is the same. + String? retryTag; + + @override + void initState() { + super.initState(); + + assert(isURL(widget.url)); + + if (widget.url.isAppFlowyCloudUrl) { + assert( + widget.userProfilePB != null && widget.userProfilePB!.token.isNotEmpty, + ); + } + + retryTag = retryCounter.add(widget.url); + + manager.getFileFromCache(widget.url).then((file) { + widget.onImageLoaded?.call( + file != null && + file.file.path.isNotEmpty && + file.originalUrl == widget.url, + ); + }); + } + + @override + void reassemble() { + super.reassemble(); + + if (retryTag != null) { + retryCounter.clear( + tag: retryTag!, + url: widget.url, + maxRetries: widget.maxRetries, + ); + } + } + + @override + void dispose() { + if (retryTag != null) { + retryCounter.clear( + tag: retryTag!, + url: widget.url, + maxRetries: widget.maxRetries, + ); + } + + super.dispose(); + } + @override Widget build(BuildContext context) { - assert(isURL(url)); + return ListenableBuilder( + listenable: retryCounter, + builder: (context, child) { + final retryCount = retryCounter.getRetryCount(widget.url); + return CachedNetworkImage( + key: ValueKey('${widget.url}_$retryCount'), + cacheManager: manager, + httpHeaders: _buildRequestHeader(), + imageUrl: widget.url, + fit: widget.fit, + width: widget.width, + height: widget.height, + progressIndicatorBuilder: widget.progressIndicatorBuilder, + errorWidget: _errorWidgetBuilder, + errorListener: (value) async { + Log.error( + 'Unable to load image: ${value.toString()} - retryCount: $retryCount', + ); - if (url.isAppFlowyCloudUrl) { - assert(userProfilePB != null && userProfilePB!.token.isNotEmpty); - } - - final manager = CustomImageCacheManager(); - - return CachedNetworkImage( - cacheManager: manager, - httpHeaders: _header(), - imageUrl: url, - fit: fit, - width: width, - height: height, - progressIndicatorBuilder: progressIndicatorBuilder, - errorWidget: (context, url, error) => - errorWidgetBuilder?.call(context, url, error) ?? - const SizedBox.shrink(), - errorListener: (value) { - // try to clear the image cache. - manager.removeFile(url); - - Log.error(value.toString()); + // clear the cache and retry + await manager.removeFile(widget.url); + _retryLoadImage(); + }, + ); }, ); } - Map _header() { + /// if the error is 404 and the retry count is less than the max retries, it return a loading indicator. + Widget _errorWidgetBuilder(BuildContext context, String url, Object error) { + final retryCount = retryCounter.getRetryCount(url); + if (error is HttpExceptionWithStatus) { + if (widget.retryErrorCodes.contains(error.statusCode) && + retryCount < widget.maxRetries) { + final fakeDownloadProgress = DownloadProgress(url, null, 0); + return widget.progressIndicatorBuilder?.call( + context, + url, + fakeDownloadProgress, + ) ?? + const Center( + child: _SensitiveContent(), + ); + } + + if (error.statusCode == 422) { + // Unprocessable Entity: Used when the server understands the request but cannot process it due to + //semantic issues (e.g., sensitive keywords). + return const _SensitiveContent(); + } + } + + return widget.errorWidgetBuilder?.call(context, url, error) ?? + const SizedBox.shrink(); + } + + Map _buildRequestHeader() { final header = {}; - final token = userProfilePB?.token; + final token = widget.userProfilePB?.token; if (token != null) { try { final decodedToken = jsonDecode(token); header['Authorization'] = 'Bearer ${decodedToken['access_token']}'; } catch (e) { - Log.error('unable to decode token: $e'); + Log.error('Unable to decode token: $e'); } } return header; } + + void _retryLoadImage() { + final retryCount = retryCounter.getRetryCount(widget.url); + if (retryCount < widget.maxRetries) { + Future.delayed(widget.retryDuration, () { + Log.debug( + 'Retry load image: ${widget.url}, retry count: $retryCount', + ); + // Increment the retry count for the URL to trigger the image rebuild. + retryCounter.increment(widget.url); + }); + } + } +} + +/// This class is used to count the number of retries for a given URL. +@visibleForTesting +class FlowyNetworkRetryCounter with ChangeNotifier { + FlowyNetworkRetryCounter._(); + + factory FlowyNetworkRetryCounter() => _instance; + static final _instance = FlowyNetworkRetryCounter._(); + + final Map _values = {}; + Map get values => {..._values}; + + /// Get the retry count for a given URL. + int getRetryCount(String url) => _values[url] ?? 0; + + /// Add a new URL to the retry counter. Don't call notifyListeners() here. + /// + /// This function will return a tag, use it to clear the retry count. + /// Because the url may be the same, we need to add a unique tag to the url. + String add(String url) { + _values.putIfAbsent(url, () => 0); + return url + uuid(); + } + + /// Increment the retry count for a given URL. + void increment(String url) { + final count = _values[url]; + if (count == null) { + _values[url] = 1; + } else { + _values[url] = count + 1; + } + notifyListeners(); + } + + /// Clear the retry count for a given tag. + void clear({ + required String tag, + required String url, + int? maxRetries, + }) { + _values.remove(tag); + + final retryCount = _values[url]; + if (maxRetries == null || + (retryCount != null && retryCount >= maxRetries)) { + _values.remove(url); + } + } + + /// Reset the retry counter. + void reset() { + _values.clear(); + } +} + +class _SensitiveContent extends StatelessWidget { + const _SensitiveContent(); + + @override + Widget build(BuildContext context) { + return FlowyText(LocaleKeys.ai_contentPolicyViolation.tr()); + } } diff --git a/frontend/appflowy_flutter/lib/shared/appflowy_network_svg.dart b/frontend/appflowy_flutter/lib/shared/appflowy_network_svg.dart new file mode 100644 index 0000000000..33c3bb2c0a --- /dev/null +++ b/frontend/appflowy_flutter/lib/shared/appflowy_network_svg.dart @@ -0,0 +1,197 @@ +import 'dart:developer'; +import 'dart:io'; + +import 'package:flowy_svg/flowy_svg.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_cache_manager/flutter_cache_manager.dart'; + +import 'custom_image_cache_manager.dart'; + +class FlowyNetworkSvg extends StatefulWidget { + FlowyNetworkSvg( + this.url, { + Key? key, + this.cacheKey, + this.placeholder, + this.errorWidget, + this.width, + this.height, + this.headers, + this.fit = BoxFit.contain, + this.alignment = Alignment.center, + this.matchTextDirection = false, + this.allowDrawingOutsideViewBox = false, + this.semanticsLabel, + this.excludeFromSemantics = false, + this.theme = const SvgTheme(), + this.fadeDuration = Duration.zero, + this.colorFilter, + this.placeholderBuilder, + BaseCacheManager? cacheManager, + }) : cacheManager = cacheManager ?? CustomImageCacheManager(), + super(key: key ?? ValueKey(url)); + + final String url; + final String? cacheKey; + final Widget? placeholder; + final Widget? errorWidget; + final double? width; + final double? height; + final ColorFilter? colorFilter; + final Map? headers; + final BoxFit fit; + final AlignmentGeometry alignment; + final bool matchTextDirection; + final bool allowDrawingOutsideViewBox; + final String? semanticsLabel; + final bool excludeFromSemantics; + final SvgTheme theme; + final Duration fadeDuration; + final WidgetBuilder? placeholderBuilder; + final BaseCacheManager cacheManager; + + @override + State createState() => _FlowyNetworkSvgState(); + + static Future preCache( + String imageUrl, { + String? cacheKey, + BaseCacheManager? cacheManager, + }) { + final key = cacheKey ?? _generateKeyFromUrl(imageUrl); + cacheManager ??= DefaultCacheManager(); + return cacheManager.downloadFile(key); + } + + static Future clearCacheForUrl( + String imageUrl, { + String? cacheKey, + BaseCacheManager? cacheManager, + }) { + final key = cacheKey ?? _generateKeyFromUrl(imageUrl); + cacheManager ??= DefaultCacheManager(); + return cacheManager.removeFile(key); + } + + static Future clearCache({BaseCacheManager? cacheManager}) { + cacheManager ??= DefaultCacheManager(); + return cacheManager.emptyCache(); + } + + static String _generateKeyFromUrl(String url) => url.split('?').first; +} + +class _FlowyNetworkSvgState extends State + with SingleTickerProviderStateMixin { + bool _isLoading = false; + bool _isError = false; + File? _imageFile; + late String _cacheKey; + + late final AnimationController _controller; + late final Animation _animation; + + @override + void initState() { + super.initState(); + _cacheKey = + widget.cacheKey ?? FlowyNetworkSvg._generateKeyFromUrl(widget.url); + _controller = AnimationController( + vsync: this, + duration: widget.fadeDuration, + ); + _animation = Tween(begin: 0.0, end: 1.0).animate(_controller); + _loadImage(); + } + + Future _loadImage() async { + try { + _setToLoadingAfter15MsIfNeeded(); + + var file = (await widget.cacheManager.getFileFromMemory(_cacheKey))?.file; + + file ??= await widget.cacheManager.getSingleFile( + widget.url, + key: _cacheKey, + headers: widget.headers ?? {}, + ); + + _imageFile = file; + _isLoading = false; + + _setState(); + + await _controller.forward(); + } catch (e) { + log('CachedNetworkSVGImage: $e'); + + _isError = true; + _isLoading = false; + + _setState(); + } + } + + void _setToLoadingAfter15MsIfNeeded() => Future.delayed( + const Duration(milliseconds: 15), + () { + if (!_isLoading && _imageFile == null && !_isError) { + _isLoading = true; + _setState(); + } + }, + ); + + void _setState() => mounted ? setState(() {}) : null; + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SizedBox( + width: widget.width, + height: widget.height, + child: _buildImage(), + ); + } + + Widget _buildImage() { + if (_isLoading) return _buildPlaceholderWidget(); + + if (_isError) return _buildErrorWidget(); + + return FadeTransition( + opacity: _animation, + child: _buildSVGImage(), + ); + } + + Widget _buildPlaceholderWidget() => + Center(child: widget.placeholder ?? const SizedBox()); + + Widget _buildErrorWidget() => + Center(child: widget.errorWidget ?? const SizedBox()); + + Widget _buildSVGImage() { + if (_imageFile == null) return const SizedBox(); + + return SvgPicture.file( + _imageFile!, + fit: widget.fit, + width: widget.width, + height: widget.height, + alignment: widget.alignment, + matchTextDirection: widget.matchTextDirection, + allowDrawingOutsideViewBox: widget.allowDrawingOutsideViewBox, + colorFilter: widget.colorFilter, + semanticsLabel: widget.semanticsLabel, + excludeFromSemantics: widget.excludeFromSemantics, + placeholderBuilder: widget.placeholderBuilder, + theme: widget.theme, + ); + } +} diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/error_page.dart b/frontend/appflowy_flutter/lib/shared/error_page/error_page.dart similarity index 88% rename from frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/error_page.dart rename to frontend/appflowy_flutter/lib/shared/error_page/error_page.dart index d395873bd7..9661fd822a 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/error_page.dart +++ b/frontend/appflowy_flutter/lib/shared/error_page/error_page.dart @@ -1,14 +1,18 @@ import 'dart:io'; +import 'package:appflowy/core/helpers/url_launcher.dart'; +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart'; +import 'package:appflowy/startup/startup.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/theme_extension.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_infra_ui/widget/flowy_tooltip.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - -import 'package:flowy_infra/theme_extension.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/style_widget/hover.dart'; -import 'package:flowy_svg/flowy_svg.dart'; -import 'package:url_launcher/url_launcher.dart'; class FlowyErrorPage extends StatelessWidget { factory FlowyErrorPage.error( @@ -86,7 +90,9 @@ class FlowyErrorPage extends StatelessWidget { Listener( behavior: HitTestBehavior.translucent, onPointerDown: (_) async { - await Clipboard.setData(ClipboardData(text: message)); + await getIt().setData( + ClipboardServiceData(plainText: message), + ); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( @@ -188,8 +194,8 @@ class StackTracePreview extends StatelessWidget { "Copy", ), useIntrinsicWidth: true, - onTap: () => Clipboard.setData( - ClipboardData(text: stackTrace), + onTap: () => getIt().setData( + ClipboardServiceData(plainText: stackTrace), ), ), ), @@ -252,18 +258,14 @@ class GitHubRedirectButton extends StatelessWidget { Widget build(BuildContext context) { return FlowyButton( leftIconSize: const Size.square(_height), - text: const FlowyText( - "AppFlowy", - ), + text: FlowyText(LocaleKeys.appName.tr()), useIntrinsicWidth: true, leftIcon: const Padding( padding: EdgeInsets.all(4.0), child: FlowySvg(FlowySvgData('login/github-mark')), ), onTap: () async { - if (await canLaunchUrl(_gitHubNewBugUri)) { - await launchUrl(_gitHubNewBugUri); - } + await afLaunchUri(_gitHubNewBugUri); }, ); } diff --git a/frontend/appflowy_flutter/lib/shared/flowy_error_page.dart b/frontend/appflowy_flutter/lib/shared/flowy_error_page.dart index da9f679f56..5942271206 100644 --- a/frontend/appflowy_flutter/lib/shared/flowy_error_page.dart +++ b/frontend/appflowy_flutter/lib/shared/flowy_error_page.dart @@ -45,7 +45,6 @@ class _MobileSyncErrorPage extends StatelessWidget { onTapUp: () { getIt().setPlainText(error.toString()); showToastNotification( - context, message: LocaleKeys.message_copy_success.tr(), bottomPadding: 0, ); @@ -101,7 +100,6 @@ class _DesktopSyncErrorPage extends StatelessWidget { onTapUp: () { getIt().setPlainText(error.toString()); showToastNotification( - context, message: LocaleKeys.message_copy_success.tr(), bottomPadding: 0, ); diff --git a/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/colors.dart b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/colors.dart index 8728c3be4a..40b9c1d6fa 100644 --- a/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/colors.dart +++ b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/colors.dart @@ -5,7 +5,7 @@ extension PickerColors on BuildContext { Color get pickerTextColor { return Theme.of(this).isLightMode ? const Color(0x80171717) - : Colors.white.withOpacity(0.5); + : Colors.white.withValues(alpha: 0.5); } Color get pickerIconColor { @@ -15,12 +15,12 @@ extension PickerColors on BuildContext { Color get pickerSearchBarBorderColor { return Theme.of(this).isLightMode ? const Color(0x1E171717) - : Colors.white.withOpacity(0.12); + : Colors.white.withValues(alpha: 0.12); } Color get pickerButtonBoarderColor { return Theme.of(this).isLightMode ? const Color(0x1E171717) - : Colors.white.withOpacity(0.12); + : Colors.white.withValues(alpha: 0.12); } } diff --git a/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/emoji_search_bar.dart b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/emoji_search_bar.dart index 5604da1b33..4520a2b118 100644 --- a/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/emoji_search_bar.dart +++ b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/emoji_search_bar.dart @@ -34,6 +34,7 @@ class FlowyEmojiSearchBar extends StatefulWidget { class _FlowyEmojiSearchBarState extends State { final TextEditingController controller = TextEditingController(); + EmojiSkinTone skinTone = lastSelectedEmojiSkinTone ?? EmojiSkinTone.none; @override void dispose() { @@ -58,12 +59,18 @@ class _FlowyEmojiSearchBarState extends State { ), const HSpace(8.0), _RandomEmojiButton( + skinTone: skinTone, emojiData: widget.emojiData, onRandomEmojiSelected: widget.onRandomEmojiSelected, ), const HSpace(8.0), FlowyEmojiSkinToneSelector( - onEmojiSkinToneChanged: widget.onSkinToneChanged, + onEmojiSkinToneChanged: (v) { + setState(() { + skinTone = v; + }); + widget.onSkinToneChanged.call(v); + }, ), ], ), @@ -73,10 +80,12 @@ class _FlowyEmojiSearchBarState extends State { class _RandomEmojiButton extends StatelessWidget { const _RandomEmojiButton({ + required this.skinTone, required this.emojiData, required this.onRandomEmojiSelected, }); + final EmojiSkinTone skinTone; final EmojiData emojiData; final EmojiSelectedCallback onRandomEmojiSelected; @@ -100,9 +109,14 @@ class _RandomEmojiButton extends StatelessWidget { ), onTap: () { final random = emojiData.random; + final emojiId = random.$1; + final emoji = emojiData.getEmojiById( + emojiId, + skinTone: skinTone, + ); onRandomEmojiSelected( - random.$1, - random.$2, + emojiId, + emoji, ); }, ), @@ -131,6 +145,9 @@ class _SearchTextFieldState extends State<_SearchTextField> { @override void initState() { super.initState(); + + /// Sometimes focus is lost due to the [SelectionGestureInterceptor] in [KeyboardServiceWidgetState] + /// this is to ensure that focus can be regained within a short period of time if (widget.ensureFocus) { Future.delayed(const Duration(milliseconds: 200), () { if (!mounted || focusNode.hasFocus) return; diff --git a/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart index 727d0b8fba..b04b38a45a 100644 --- a/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart +++ b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/base/emoji/emoji_picker.dart'; import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart'; @@ -6,8 +8,11 @@ import 'package:appflowy_backend/protobuf/flowy-folder/icon.pb.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart' hide Icon; +import 'package:flutter/services.dart'; import 'package:universal_platform/universal_platform.dart'; +import 'icon_uploader.dart'; + extension ToProto on FlowyIconType { ViewIconTypePB toProto() { switch (this) { @@ -46,6 +51,10 @@ enum FlowyIconType { custom; } +extension FlowyIconTypeToPickerTabType on FlowyIconType { + PickerTabType? toPickerTabType() => name.toPickerTabType(); +} + class EmojiIconData { factory EmojiIconData.none() => const EmojiIconData(FlowyIconType.icon, ''); @@ -55,6 +64,9 @@ class EmojiIconData { factory EmojiIconData.icon(IconsData icon) => EmojiIconData(FlowyIconType.icon, icon.iconString); + factory EmojiIconData.custom(String url) => + EmojiIconData(FlowyIconType.custom, url); + const EmojiIconData( this.type, this.emoji, @@ -78,17 +90,40 @@ class EmojiIconData { bool get isNotEmpty => emoji.isNotEmpty; } +class SelectedEmojiIconResult { + SelectedEmojiIconResult(this.data, this.keepOpen); + + final EmojiIconData data; + final bool keepOpen; + + FlowyIconType get type => data.type; + + String get emoji => data.emoji; +} + +extension EmojiIconDataToSelectedResultExtension on EmojiIconData { + SelectedEmojiIconResult toSelectedResult({bool keepOpen = false}) => + SelectedEmojiIconResult(this, keepOpen); +} + class FlowyIconEmojiPicker extends StatefulWidget { const FlowyIconEmojiPicker({ super.key, this.onSelectedEmoji, + this.initialType, + this.documentId, this.enableBackgroundColorSelection = true, - this.tabs = const [PickerTabType.emoji, PickerTabType.icon], + this.tabs = const [ + PickerTabType.emoji, + PickerTabType.icon, + ], }); - final ValueChanged? onSelectedEmoji; + final ValueChanged? onSelectedEmoji; final bool enableBackgroundColorSelection; final List tabs; + final PickerTabType? initialType; + final String? documentId; @override State createState() => _FlowyIconEmojiPickerState(); @@ -96,12 +131,29 @@ class FlowyIconEmojiPicker extends StatefulWidget { class _FlowyIconEmojiPickerState extends State with SingleTickerProviderStateMixin { - late final controller = TabController( - length: widget.tabs.length, - vsync: this, - ); + late TabController controller; int currentIndex = 0; + @override + void initState() { + super.initState(); + final initialType = widget.initialType; + if (initialType != null) { + currentIndex = max(widget.tabs.indexOf(initialType), 0); + } + controller = TabController( + initialIndex: currentIndex, + length: widget.tabs.length, + vsync: this, + ); + controller.addListener(() { + final currentType = widget.tabs[currentIndex]; + if (currentType == PickerTabType.custom) { + SystemChannels.textInput.invokeMethod('TextInput.hide'); + } + }); + } + @override void dispose() { controller.dispose(); @@ -127,7 +179,8 @@ class _FlowyIconEmojiPickerState extends State ), _RemoveIconButton( onTap: () { - widget.onSelectedEmoji?.call(EmojiIconData.none()); + widget.onSelectedEmoji + ?.call(EmojiIconData.none().toSelectedResult()); }, ), ], @@ -143,6 +196,8 @@ class _FlowyIconEmojiPickerState extends State return _buildEmojiPicker(); case PickerTabType.icon: return _buildIconPicker(); + case PickerTabType.custom: + return _buildIconUploader(); } }).toList(), ), @@ -155,9 +210,12 @@ class _FlowyIconEmojiPickerState extends State return FlowyEmojiPicker( ensureFocus: true, emojiPerLine: _getEmojiPerLine(context), - onEmojiSelected: (_, emoji) => widget.onSelectedEmoji?.call( - EmojiIconData.emoji(emoji), - ), + onEmojiSelected: (r) { + widget.onSelectedEmoji?.call( + EmojiIconData.emoji(r.emoji).toSelectedResult(keepOpen: r.isRandom), + ); + SystemChannels.textInput.invokeMethod('TextInput.hide'); + }, ); } @@ -171,9 +229,24 @@ class _FlowyIconEmojiPickerState extends State Widget _buildIconPicker() { return FlowyIconPicker( + ensureFocus: true, enableBackgroundColorSelection: widget.enableBackgroundColorSelection, - onSelectedIcon: (result) { - widget.onSelectedEmoji?.call(result.toEmojiIconData()); + onSelectedIcon: (r) { + widget.onSelectedEmoji?.call( + r.data.toEmojiIconData().toSelectedResult(keepOpen: r.isRandom), + ); + SystemChannels.textInput.invokeMethod('TextInput.hide'); + }, + ); + } + + Widget _buildIconUploader() { + return IconUploader( + documentId: widget.documentId ?? '', + ensureFocus: true, + onUrl: (url) { + widget.onSelectedEmoji + ?.call(SelectedEmojiIconResult(EmojiIconData.custom(url), false)); }, ); } diff --git a/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon.dart b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon.dart index 7764d73838..a053595bbd 100644 --- a/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon.dart +++ b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon.dart @@ -39,8 +39,9 @@ class IconGroup { final filteredIcons = icons .where( (icon) => - icon.keywords.any((k) => k.contains(lowercaseKey)) || - icon.name.contains(lowercaseKey), + icon.keywords + .any((k) => k.toLowerCase().contains(lowercaseKey)) || + icon.name.toLowerCase().contains(lowercaseKey), ) .toList(); return IconGroup(name: name, icons: filteredIcons); @@ -84,3 +85,23 @@ class Icon { return '${iconGroup!.name}/$name'; } } + +class RecentIcon { + factory RecentIcon.fromJson(Map json) => + RecentIcon(_$IconFromJson(json), json['groupName'] ?? ''); + + RecentIcon(this.icon, this.groupName); + + final Icon icon; + final String groupName; + + String get name => icon.name; + + List get keywords => icon.keywords; + + String get content => icon.content; + + Map toJson() => _$IconToJson( + Icon(name: name, keywords: keywords, content: content), + )..addAll({'groupName': groupName}); +} diff --git a/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon_picker.dart b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon_picker.dart index 75f5633ee5..0d57d12d3c 100644 --- a/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon_picker.dart +++ b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon_picker.dart @@ -75,17 +75,31 @@ Future> loadIconGroups() async { } } +class IconPickerResult { + IconPickerResult(this.data, this.isRandom); + + final IconsData data; + final bool isRandom; +} + +extension IconsDataToIconPickerResultExtension on IconsData { + IconPickerResult toResult({bool isRandom = false}) => + IconPickerResult(this, isRandom); +} + class FlowyIconPicker extends StatefulWidget { const FlowyIconPicker({ super.key, required this.onSelectedIcon, required this.enableBackgroundColorSelection, this.iconPerLine = 9, + this.ensureFocus = false, }); final bool enableBackgroundColorSelection; - final ValueChanged onSelectedIcon; + final ValueChanged onSelectedIcon; final int iconPerLine; + final bool ensureFocus; @override State createState() => _FlowyIconPickerState(); @@ -101,15 +115,22 @@ class _FlowyIconPickerState extends State { final localIcons = await loadIconGroups(); final recentIcons = await RecentIcons.getIcons(); if (recentIcons.isNotEmpty) { - iconGroups.add( - IconGroup( - name: _kRecentIconGroupName, - icons: recentIcons.sublist( + final filterRecentIcons = recentIcons + .sublist( 0, min(recentIcons.length, widget.iconPerLine), + ) + .skipWhile((e) => e.groupName.isEmpty) + .map((e) => e.icon) + .toList(); + if (filterRecentIcons.isNotEmpty) { + iconGroups.add( + IconGroup( + name: _kRecentIconGroupName, + icons: filterRecentIcons, ), - ), - ); + ); + } } iconGroups.addAll(localIcons); if (mounted) { @@ -142,20 +163,23 @@ class _FlowyIconPickerState extends State { Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: IconSearchBar( + ensureFocus: widget.ensureFocus, onRandomTap: () { final value = kIconGroups?.randomIcon(); if (value == null) { return; } - final color = generateRandomSpaceColor(); + final color = widget.enableBackgroundColorSelection + ? generateRandomSpaceColor() + : null; widget.onSelectedIcon( IconsData( value.$1.name, - value.$2.content, value.$2.name, color, - ), + ).toResult(isRandom: true), ); + RecentIcons.putIcon(RecentIcon(value.$2, value.$1.name)); }, onKeywordChanged: (keyword) => { debounce.call(() { @@ -193,14 +217,14 @@ class _FlowyIconPickerState extends State { iconGroups: filteredIconGroups, enableBackgroundColorSelection: widget.enableBackgroundColorSelection, - onSelectedIcon: widget.onSelectedIcon, + onSelectedIcon: (r) => widget.onSelectedIcon.call(r.toResult()), iconPerLine: widget.iconPerLine, ); } return IconPicker( iconGroups: iconGroups, enableBackgroundColorSelection: widget.enableBackgroundColorSelection, - onSelectedIcon: widget.onSelectedIcon, + onSelectedIcon: (r) => widget.onSelectedIcon.call(r.toResult()), iconPerLine: widget.iconPerLine, ); }, @@ -209,30 +233,35 @@ class _FlowyIconPickerState extends State { } class IconsData { - IconsData(this.groupName, this.iconContent, this.iconName, this.color); + IconsData(this.groupName, this.iconName, this.color); final String groupName; - final String iconContent; final String iconName; final String? color; String get iconString => jsonEncode({ 'groupName': groupName, - 'iconContent': iconContent, 'iconName': iconName, if (color != null) 'color': color, }); EmojiIconData toEmojiIconData() => EmojiIconData.icon(this); + IconsData noColor() => IconsData(groupName, iconName, null); + static IconsData fromJson(dynamic json) { return IconsData( json['groupName'], - json['iconContent'], json['iconName'], json['color'], ); } + + String? get svgString => kIconGroups + ?.firstWhereOrNull((group) => group.name == groupName) + ?.icons + .firstWhereOrNull((icon) => icon.name == iconName) + ?.content; } class IconPicker extends StatefulWidget { @@ -255,84 +284,130 @@ class IconPicker extends StatefulWidget { class _IconPickerState extends State { final mutex = PopoverMutex(); + PopoverController? childPopoverController; + + @override + void dispose() { + super.dispose(); + childPopoverController = null; + } @override Widget build(BuildContext context) { - return ListView.builder( - itemCount: widget.iconGroups.length, - padding: const EdgeInsets.symmetric(horizontal: 16.0), - itemBuilder: (context, index) { - final iconGroup = widget.iconGroups[index]; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - FlowyText( - iconGroup.displayName.capitalize(), - fontSize: 12, - figmaLineHeight: 18.0, - color: context.pickerTextColor, - ), - const VSpace(4.0), - GridView.builder( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: widget.iconPerLine, - ), - itemCount: iconGroup.icons.length, - shrinkWrap: true, - itemBuilder: (context, index) { - final icon = iconGroup.icons[index]; - return widget.enableBackgroundColorSelection - ? _Icon( - icon: icon, - mutex: mutex, - onSelectedColor: (context, color) { - widget.onSelectedIcon( - IconsData( - iconGroup.name, - icon.content, - icon.name, - color, - ), + return GestureDetector( + onTap: hideColorSelector, + child: NotificationListener( + onNotification: (notificationInfo) { + if (notificationInfo is ScrollStartNotification) { + hideColorSelector(); + } + return true; + }, + child: ListView.builder( + itemCount: widget.iconGroups.length, + padding: const EdgeInsets.symmetric(horizontal: 16.0), + itemBuilder: (context, index) { + final iconGroup = widget.iconGroups[index]; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FlowyText( + iconGroup.displayName.capitalize(), + fontSize: 12, + figmaLineHeight: 18.0, + color: context.pickerTextColor, + ), + const VSpace(4.0), + GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: widget.iconPerLine, + ), + itemCount: iconGroup.icons.length, + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemBuilder: (context, index) { + final icon = iconGroup.icons[index]; + return widget.enableBackgroundColorSelection + ? _Icon( + icon: icon, + mutex: mutex, + onOpen: (childPopoverController) { + this.childPopoverController = + childPopoverController; + }, + onSelectedColor: (context, color) { + String groupName = iconGroup.name; + if (groupName == _kRecentIconGroupName) { + groupName = getGroupName(index); + } + widget.onSelectedIcon( + IconsData( + groupName, + icon.name, + color, + ), + ); + RecentIcons.putIcon(RecentIcon(icon, groupName)); + PopoverContainer.of(context).close(); + }, + ) + : _IconNoBackground( + icon: icon, + onSelectedIcon: () { + String groupName = iconGroup.name; + if (groupName == _kRecentIconGroupName) { + groupName = getGroupName(index); + } + widget.onSelectedIcon( + IconsData( + groupName, + icon.name, + null, + ), + ); + RecentIcons.putIcon(RecentIcon(icon, groupName)); + }, ); - RecentIcons.putIcon(icon); - PopoverContainer.of(context).close(); - }, - ) - : _IconNoBackground( - icon: icon, - onSelectedIcon: () { - widget.onSelectedIcon( - IconsData( - iconGroup.name, - icon.content, - icon.name, - null, - ), - ); - RecentIcons.putIcon(icon); - }, - ); - }, - ), - const VSpace(12.0), - if (index == widget.iconGroups.length - 1) ...[ - const StreamlinePermit(), - const VSpace(12.0), - ], - ], - ); - }, + }, + ), + const VSpace(12.0), + if (index == widget.iconGroups.length - 1) ...[ + const StreamlinePermit(), + const VSpace(12.0), + ], + ], + ); + }, + ), + ), ); } + + void hideColorSelector() { + childPopoverController?.close(); + childPopoverController = null; + } + + String getGroupName(int index) { + final recentIcons = RecentIcons.getIconsSync(); + try { + return recentIcons[index].groupName; + } catch (e) { + Log.error('getGroupName with index: $index error', e); + return ''; + } + } } class _IconNoBackground extends StatelessWidget { const _IconNoBackground({ required this.icon, required this.onSelectedIcon, + this.isSelected = false, }); final Icon icon; + final bool isSelected; final VoidCallback onSelectedIcon; @override @@ -341,6 +416,7 @@ class _IconNoBackground extends StatelessWidget { message: icon.displayName, preferBelow: false, child: FlowyButton( + isSelected: isSelected, useIntrinsicWidth: true, onTap: () => onSelectedIcon(), margin: const EdgeInsets.all(8.0), @@ -362,11 +438,13 @@ class _Icon extends StatefulWidget { required this.icon, required this.mutex, required this.onSelectedColor, + this.onOpen, }); final Icon icon; final PopoverMutex mutex; final void Function(BuildContext context, String color) onSelectedColor; + final ValueChanged? onOpen; @override State<_Icon> createState() => _IconState(); @@ -374,16 +452,33 @@ class _Icon extends StatefulWidget { class _IconState extends State<_Icon> { final PopoverController _popoverController = PopoverController(); + bool isSelected = false; + + @override + void dispose() { + super.dispose(); + _popoverController.close(); + } @override Widget build(BuildContext context) { return AppFlowyPopover( direction: PopoverDirection.bottomWithCenterAligned, + controller: _popoverController, offset: const Offset(0, 6), mutex: widget.mutex, + onClose: () { + updateIsSelected(false); + }, + clickHandler: PopoverClickHandler.gestureDetector, child: _IconNoBackground( icon: widget.icon, - onSelectedIcon: () => _popoverController.show(), + isSelected: isSelected, + onSelectedIcon: () { + updateIsSelected(true); + _popoverController.show(); + widget.onOpen?.call(_popoverController); + }, ), popupBuilder: (context) { return Container( @@ -395,6 +490,12 @@ class _IconState extends State<_Icon> { }, ); } + + void updateIsSelected(bool isSelected) { + setState(() { + this.isSelected = isSelected; + }); + } } class StreamlinePermit extends StatelessWidget { diff --git a/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon_search_bar.dart b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon_search_bar.dart index dc079bbc4e..a12be47684 100644 --- a/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon_search_bar.dart +++ b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon_search_bar.dart @@ -16,9 +16,11 @@ class IconSearchBar extends StatefulWidget { super.key, required this.onRandomTap, required this.onKeywordChanged, + this.ensureFocus = false, }); final VoidCallback onRandomTap; + final bool ensureFocus; final IconKeywordChangedCallback onKeywordChanged; @override @@ -46,6 +48,7 @@ class _IconSearchBarState extends State { Expanded( child: _SearchTextField( onKeywordChanged: widget.onKeywordChanged, + ensureFocus: widget.ensureFocus, ), ), const HSpace(8.0), @@ -93,9 +96,11 @@ class _RandomIconButton extends StatelessWidget { class _SearchTextField extends StatefulWidget { const _SearchTextField({ required this.onKeywordChanged, + this.ensureFocus = false, }); final IconKeywordChangedCallback onKeywordChanged; + final bool ensureFocus; @override State<_SearchTextField> createState() => _SearchTextFieldState(); @@ -105,6 +110,20 @@ class _SearchTextFieldState extends State<_SearchTextField> { final TextEditingController controller = TextEditingController(); final FocusNode focusNode = FocusNode(); + @override + void initState() { + super.initState(); + + /// Sometimes focus is lost due to the [SelectionGestureInterceptor] in [KeyboardServiceWidgetState] + /// this is to ensure that focus can be regained within a short period of time + if (widget.ensureFocus) { + Future.delayed(const Duration(milliseconds: 200), () { + if (!mounted || focusNode.hasFocus) return; + focusNode.requestFocus(); + }); + } + } + @override void dispose() { controller.dispose(); diff --git a/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon_uploader.dart b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon_uploader.dart new file mode 100644 index 0000000000..c303160ffe --- /dev/null +++ b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon_uploader.dart @@ -0,0 +1,466 @@ +import 'dart:io'; + +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_drop_manager.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_util.dart'; +import 'package:appflowy/shared/appflowy_network_image.dart'; +import 'package:appflowy/shared/appflowy_network_svg.dart'; +import 'package:appflowy/shared/custom_image_cache_manager.dart'; +import 'package:appflowy/shared/patterns/file_type_patterns.dart'; +import 'package:appflowy/shared/permission/permission_checker.dart'; +import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/user/application/user_service.dart'; +import 'package:appflowy/util/default_extensions.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_backend/protobuf/flowy-user/workspace.pb.dart'; +import 'package:desktop_drop/desktop_drop.dart'; +import 'package:dotted_border/dotted_border.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/file_picker/file_picker_service.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flowy_svg/flowy_svg.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:http/http.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:string_validator/string_validator.dart'; +import 'package:universal_platform/universal_platform.dart'; + +@visibleForTesting +class IconUploader extends StatefulWidget { + const IconUploader({ + super.key, + required this.onUrl, + required this.documentId, + this.ensureFocus = false, + }); + + final ValueChanged onUrl; + final String documentId; + final bool ensureFocus; + + @override + State createState() => _IconUploaderState(); +} + +class _IconUploaderState extends State { + bool isActive = false; + bool isHovering = false; + bool isUploading = false; + + final List<_Image> pickedImages = []; + final FocusNode focusNode = FocusNode(); + + @override + void initState() { + super.initState(); + + /// Sometimes focus is lost due to the [SelectionGestureInterceptor] in [KeyboardServiceWidgetState] + /// this is to ensure that focus can be regained within a short period of time + if (widget.ensureFocus) { + Future.delayed(const Duration(milliseconds: 200), () { + if (!mounted || focusNode.hasFocus) return; + focusNode.requestFocus(); + }); + } + WidgetsBinding.instance.addPostFrameCallback((_) { + enableDocumentDragNotifier.value = false; + }); + } + + @override + void dispose() { + super.dispose(); + WidgetsBinding.instance.addPostFrameCallback((_) { + enableDocumentDragNotifier.value = true; + }); + focusNode.dispose(); + } + + @override + Widget build(BuildContext context) { + return Shortcuts( + shortcuts: { + LogicalKeySet( + Platform.isMacOS + ? LogicalKeyboardKey.meta + : LogicalKeyboardKey.control, + LogicalKeyboardKey.keyV, + ): _PasteIntent(), + }, + child: Actions( + actions: { + _PasteIntent: CallbackAction<_PasteIntent>( + onInvoke: (intent) => pasteAsAnImage(), + ), + }, + child: Focus( + autofocus: true, + focusNode: focusNode, + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + Expanded( + child: DropTarget( + onDragEntered: (_) => setState(() => isActive = true), + onDragExited: (_) => setState(() => isActive = false), + onDragDone: (details) => loadImage(details.files), + child: MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: (_) => setState(() => isHovering = true), + onExit: (_) => setState(() => isHovering = false), + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: pickImage, + child: DottedBorder( + dashPattern: const [3, 3], + radius: const Radius.circular(8), + borderType: BorderType.RRect, + color: isActive + ? Theme.of(context).colorScheme.primary + : Theme.of(context).hintColor, + child: Container( + alignment: Alignment.center, + decoration: isHovering + ? BoxDecoration( + color: Color(0x0F1F2329), + borderRadius: BorderRadius.circular(8), + ) + : null, + child: pickedImages.isEmpty + ? (isActive + ? hoveringWidget() + : dragHint(context)) + : previewImage(), + ), + ), + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.only(top: 16), + child: Row( + children: [ + Spacer(), + if (pickedImages.isNotEmpty) + Padding( + padding: EdgeInsets.only(right: 8), + child: _ChangeIconButton( + onTap: pickImage, + ), + ), + _ConfirmButton( + onTap: uploadImage, + enable: pickedImages.isNotEmpty, + ), + ], + ), + ), + ], + ), + ), + ), + ), + ); + } + + Widget hoveringWidget() { + return Container( + color: Color(0xffE0F8FF), + child: Center( + child: FlowyText( + LocaleKeys.emojiIconPicker_iconUploader_dropToUpload.tr(), + ), + ), + ); + } + + Widget dragHint(BuildContext context) { + final style = TextStyle( + fontSize: 14, + color: Color(0xff666D76), + fontWeight: FontWeight.w500, + ); + return Padding( + padding: EdgeInsets.symmetric(horizontal: 32), + child: RichText( + textAlign: TextAlign.center, + text: TextSpan( + children: [ + TextSpan( + text: + LocaleKeys.emojiIconPicker_iconUploader_placeholderLeft.tr(), + ), + TextSpan( + text: LocaleKeys.emojiIconPicker_iconUploader_placeholderUpload + .tr(), + style: style.copyWith(color: Color(0xff00BCF0)), + ), + TextSpan( + text: + LocaleKeys.emojiIconPicker_iconUploader_placeholderRight.tr(), + mouseCursor: SystemMouseCursors.click, + ), + ], + style: style, + ), + ), + ); + } + + Widget previewImage() { + final image = pickedImages.first; + final url = image.url; + if (image is _FileImage) { + if (url.endsWith(_svgSuffix)) { + return SvgPicture.file( + File(url), + width: 200, + height: 200, + ); + } + return Image.file( + File(url), + width: 200, + height: 200, + ); + } else if (image is _NetworkImage) { + if (url.endsWith(_svgSuffix)) { + return FlowyNetworkSvg( + url, + width: 200, + height: 200, + ); + } + return FlowyNetworkImage( + width: 200, + height: 200, + url: url, + ); + } + return const SizedBox.shrink(); + } + + void loadImage(List files) { + final imageFiles = files + .where( + (file) => + file.mimeType?.startsWith('image/') ?? + false || + imgExtensionRegex.hasMatch(file.name) || + file.name.endsWith(_svgSuffix), + ) + .toList(); + if (imageFiles.isEmpty) return; + if (mounted) { + setState(() { + pickedImages.clear(); + pickedImages.add(_FileImage(imageFiles.first.path)); + }); + } + } + + Future pickImage() async { + if (UniversalPlatform.isDesktopOrWeb) { + // on desktop, the users can pick a image file from folder + final result = await getIt().pickFiles( + dialogTitle: '', + type: FileType.custom, + allowedExtensions: List.of(defaultImageExtensions)..add('svg'), + ); + loadImage(result?.files.map((f) => f.xFile).toList() ?? const []); + } else { + final photoPermission = + await PermissionChecker.checkPhotoPermission(context); + if (!photoPermission) { + Log.error('Has no permission to access the photo library'); + return; + } + // on mobile, the users can pick a image file from camera or image library + final result = await ImagePicker().pickMultiImage(); + loadImage(result); + } + } + + Future uploadImage() async { + if (pickedImages.isEmpty || isUploading) return; + isUploading = true; + String? result; + final userProfileResult = await UserBackendService.getCurrentUserProfile(); + final userProfile = userProfileResult.fold( + (userProfile) => userProfile, + (l) => null, + ); + final isLocalMode = (userProfile?.workspaceAuthType ?? AuthTypePB.Local) == + AuthTypePB.Local; + if (isLocalMode) { + result = await pickedImages.first.saveToLocal(); + } else { + result = await pickedImages.first.uploadToCloud(widget.documentId); + } + isUploading = false; + if (result?.isNotEmpty ?? false) { + widget.onUrl.call(result!); + } + } + + Future pasteAsAnImage() async { + final data = await getIt().getData(); + final plainText = data.plainText; + Log.info('pasteAsAnImage plainText:$plainText'); + if (plainText == null) return; + if (isURL(plainText) && (await validateImage(plainText))) { + setState(() { + pickedImages.clear(); + pickedImages.add(_NetworkImage(plainText)); + }); + } + } + + Future validateImage(String imageUrl) async { + Response res; + try { + res = await get(Uri.parse(imageUrl)); + } catch (e) { + return false; + } + if (res.statusCode != 200) return false; + final Map data = res.headers; + return checkIfImage(data['content-type']); + } + + bool checkIfImage(String? param) { + if (param == 'image/jpeg' || + param == 'image/png' || + param == 'image/gif' || + param == 'image/tiff' || + param == 'image/webp' || + param == 'image/svg+xml' || + param == 'image/svg') { + return true; + } + return false; + } +} + +class _ChangeIconButton extends StatelessWidget { + const _ChangeIconButton({required this.onTap}); + + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + final isDark = Theme.of(context).brightness == Brightness.dark; + return SizedBox( + height: 32, + width: 84, + child: FlowyButton( + text: FlowyText( + LocaleKeys.emojiIconPicker_iconUploader_change.tr(), + fontSize: 14.0, + fontWeight: FontWeight.w500, + figmaLineHeight: 20.0, + color: isDark ? Colors.white : Color(0xff1F2329), + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + ), + margin: const EdgeInsets.symmetric(horizontal: 14.0), + backgroundColor: Theme.of(context).colorScheme.surface, + hoverColor: + (isDark ? Colors.white : Color(0xffD1D8E0)).withValues(alpha: 0.9), + decoration: BoxDecoration( + border: Border.all(color: isDark ? Colors.white : Color(0xffD1D8E0)), + borderRadius: BorderRadius.circular(10), + ), + onTap: onTap, + ), + ); + } +} + +class _ConfirmButton extends StatelessWidget { + const _ConfirmButton({required this.onTap, this.enable = true}); + + final VoidCallback onTap; + final bool enable; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 32, + child: Opacity( + opacity: enable ? 1.0 : 0.5, + child: PrimaryRoundedButton( + text: LocaleKeys.button_confirm.tr(), + figmaLineHeight: 20.0, + onTap: enable ? onTap : null, + ), + ), + ); + } +} + +const _svgSuffix = '.svg'; + +class _PasteIntent extends Intent {} + +abstract class _Image { + String get url; + + Future saveToLocal(); + + Future uploadToCloud(String documentId); + + String get pureUrl => url.split('?').first; +} + +class _FileImage extends _Image { + _FileImage(this.url); + + @override + final String url; + + @override + Future saveToLocal() => saveImageToLocalStorage(url); + + @override + Future uploadToCloud(String documentId) async { + final (url, errorMsg) = await saveImageToCloudStorage( + this.url, + documentId, + ); + if (errorMsg?.isNotEmpty ?? false) { + Log.error('upload icon image :${this.url} error :$errorMsg'); + } + return url; + } +} + +class _NetworkImage extends _Image { + _NetworkImage(this.url); + + @override + final String url; + + @override + Future saveToLocal() async { + final file = await CustomImageCacheManager().downloadFile(pureUrl); + return file.file.path; + } + + @override + Future uploadToCloud(String documentId) async { + final file = await CustomImageCacheManager().downloadFile(pureUrl); + final (url, errorMsg) = await saveImageToCloudStorage( + file.file.path, + documentId, + ); + if (errorMsg?.isNotEmpty ?? false) { + Log.error('upload icon image :${this.url} error :$errorMsg'); + } + return url; + } +} diff --git a/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/recent_icons.dart b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/recent_icons.dart index aae3937fac..8c5891bb6e 100644 --- a/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/recent_icons.dart +++ b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/recent_icons.dart @@ -22,13 +22,10 @@ class RecentIcons { await _put(FlowyIconType.emoji, id); } - static Future putIcon(Icon icon) async { + static Future putIcon(RecentIcon icon) async { await _put( FlowyIconType.icon, - jsonEncode( - Icon(name: icon.name, keywords: icon.keywords, content: icon.content) - .toJson(), - ), + jsonEncode(icon.toJson()), ); } @@ -37,19 +34,36 @@ class RecentIcons { return _dataMap[FlowyIconType.emoji.name] ?? []; } - static Future> getIcons() async { + static Future> getIcons() async { await _load(); + return getIconsSync(); + } + + static List getIconsSync() { final iconList = _dataMap[FlowyIconType.icon.name] ?? []; try { - return iconList - .map((e) => Icon.fromJson(jsonDecode(e) as Map)) - .toList(); + final List result = []; + for (final map in iconList) { + final recentIcon = + RecentIcon.fromJson(jsonDecode(map) as Map); + if (recentIcon.groupName.isEmpty) { + continue; + } + result.add(recentIcon); + } + return result; } catch (e) { Log.error('RecentIcons getIcons with :$iconList', e); } return []; } + @visibleForTesting + static void clear() { + _dataMap.clear(); + getIt().remove(KVKeys.recentIcons); + } + static Future _save() async { await getIt().set( KVKeys.recentIcons, diff --git a/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/tab.dart b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/tab.dart index 56a363132c..f28ae0f9a8 100644 --- a/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/tab.dart +++ b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/tab.dart @@ -3,7 +3,8 @@ import 'package:flutter/material.dart'; enum PickerTabType { emoji, - icon; + icon, + custom; String get tr { switch (this) { @@ -11,6 +12,18 @@ enum PickerTabType { return 'Emojis'; case PickerTabType.icon: return 'Icons'; + case PickerTabType.custom: + return 'Upload'; + } + } +} + +extension StringToPickerTabType on String { + PickerTabType? toPickerTabType() { + try { + return PickerTabType.values.byName(this); + } on ArgumentError { + return null; } } } diff --git a/frontend/appflowy_flutter/lib/shared/loading.dart b/frontend/appflowy_flutter/lib/shared/loading.dart new file mode 100644 index 0000000000..f8d1c6fc86 --- /dev/null +++ b/frontend/appflowy_flutter/lib/shared/loading.dart @@ -0,0 +1,49 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +class Loading { + Loading(this.context); + + BuildContext? loadingContext; + final BuildContext context; + + bool hasStopped = false; + + void start() => unawaited( + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + loadingContext = context; + + if (hasStopped) { + WidgetsBinding.instance.addPostFrameCallback((_) { + Navigator.of(loadingContext!).maybePop(); + loadingContext = null; + }); + } + + return const SimpleDialog( + elevation: 0.0, + backgroundColor: + Colors.transparent, // can change this to your preferred color + children: [ + Center( + child: CircularProgressIndicator(), + ), + ], + ); + }, + ), + ); + + void stop() { + if (loadingContext != null) { + Navigator.of(loadingContext!).pop(); + loadingContext = null; + } + + hasStopped = true; + } +} diff --git a/frontend/appflowy_flutter/lib/shared/markdown_to_document.dart b/frontend/appflowy_flutter/lib/shared/markdown_to_document.dart index b3e2034602..912f96bd05 100644 --- a/frontend/appflowy_flutter/lib/shared/markdown_to_document.dart +++ b/frontend/appflowy_flutter/lib/shared/markdown_to_document.dart @@ -1,27 +1,97 @@ -import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; +import 'dart:convert'; +import 'dart:io'; -Document customMarkdownToDocument(String markdown) { +import 'package:appflowy/plugins/document/presentation/editor_plugins/parsers/sub_page_node_parser.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:archive/archive.dart'; +import 'package:flutter/foundation.dart'; +import 'package:path/path.dart' as p; +import 'package:share_plus/share_plus.dart'; + +Document customMarkdownToDocument( + String markdown, { + double? tableWidth, +}) { return markdownToDocument( markdown, markdownParsers: [ const MarkdownCodeBlockParser(), - const MarkdownSimpleTableParser(), + MarkdownSimpleTableParser(tableWidth: tableWidth), ], ); } -String customDocumentToMarkdown(Document document) { - return documentToMarkdown( - document, - customParsers: [ - const MathEquationNodeParser(), - const CalloutNodeParser(), - const ToggleListNodeParser(), - const CustomImageNodeParser(), - const SimpleTableNodeParser(), - const LinkPreviewNodeParser(), - const FileBlockNodeParser(), - ], - ); +Future customDocumentToMarkdown( + Document document, { + String path = '', + AsyncValueSetter? onArchive, + String lineBreak = '', +}) async { + final List> fileFutures = []; + + /// create root Archive and directory + final id = document.root.id, + archive = Archive(), + resourceDir = ArchiveFile('$id/', 0, null)..isFile = false, + fileName = p.basenameWithoutExtension(path), + dirName = resourceDir.name; + + String markdown = ''; + try { + markdown = documentToMarkdown( + document, + lineBreak: lineBreak, + customParsers: [ + const MathEquationNodeParser(), + const CalloutNodeParser(), + const ToggleListNodeParser(), + CustomImageNodeFileParser(fileFutures, dirName), + CustomMultiImageNodeFileParser(fileFutures, dirName), + GridNodeParser(fileFutures, dirName), + BoardNodeParser(fileFutures, dirName), + CalendarNodeParser(fileFutures, dirName), + const CustomParagraphNodeParser(), + const SubPageNodeParser(), + const SimpleTableNodeParser(), + const LinkPreviewNodeParser(), + const FileBlockNodeParser(), + ], + ); + } catch (e) { + Log.error('documentToMarkdown error: $e'); + } + + /// create resource directory + if (fileFutures.isNotEmpty) archive.addFile(resourceDir); + + for (final fileFuture in fileFutures) { + archive.addFile(await fileFuture); + } + + /// add markdown file to Archive + final dataBytes = utf8.encode(markdown); + archive.addFile(ArchiveFile('$fileName-$id.md', dataBytes.length, dataBytes)); + + if (archive.isNotEmpty && path.isNotEmpty) { + if (onArchive == null) { + final zipEncoder = ZipEncoder(); + final zip = zipEncoder.encode(archive); + if (zip != null) { + final zipFile = await File(path).writeAsBytes(zip); + if (Platform.isIOS) { + await Share.shareUri(zipFile.uri); + await zipFile.delete(); + } else if (Platform.isAndroid) { + await Share.shareXFiles([XFile(zipFile.path)]); + await zipFile.delete(); + } + Log.info('documentToMarkdownFiles to $path'); + } + } else { + await onArchive.call(archive); + } + } + return markdown; } diff --git a/frontend/appflowy_flutter/lib/shared/patterns/common_patterns.dart b/frontend/appflowy_flutter/lib/shared/patterns/common_patterns.dart index ebd310a747..862f9c4778 100644 --- a/frontend/appflowy_flutter/lib/shared/patterns/common_patterns.dart +++ b/frontend/appflowy_flutter/lib/shared/patterns/common_patterns.dart @@ -13,6 +13,9 @@ const _imgUrlPattern = r'(https?:\/\/)([^\s(["<,>/]*)(\/)[^\s[",><]*(.png|.jpg|.jpeg|.gif|.webm|.webp|.bmp)(\?[^\s[",><]*)?'; final imgUrlRegex = RegExp(_imgUrlPattern); +const _singleLineMarkdownImagePattern = "^!\\[.*\\]\\(($_hrefPattern)\\)\$"; +final singleLineMarkdownImageRegex = RegExp(_singleLineMarkdownImagePattern); + /// This pattern allows for both HTTP and HTTPS Scheme /// It allows for query parameters /// It only allows the following video extensions: @@ -45,3 +48,10 @@ final numberedListRegex = RegExp(_numberedListPattern); const _localPathPattern = r'^(file:\/\/|\/|\\|[a-zA-Z]:[/\\]|\.{1,2}[/\\])'; final localPathRegex = RegExp(_localPathPattern, caseSensitive: false); + +const _wordPattern = r"\S+"; +final wordRegex = RegExp(_wordPattern); + +const _appleNotesPattern = + r'\s*'; +final appleNotesRegex = RegExp(_appleNotesPattern); diff --git a/frontend/appflowy_flutter/lib/shared/permission/permission_checker.dart b/frontend/appflowy_flutter/lib/shared/permission/permission_checker.dart index df260bad71..4b5ad56ab1 100644 --- a/frontend/appflowy_flutter/lib/shared/permission/permission_checker.dart +++ b/frontend/appflowy_flutter/lib/shared/permission/permission_checker.dart @@ -9,9 +9,9 @@ import 'package:appflowy/mobile/presentation/widgets/show_flowy_mobile_confirm_d import 'package:appflowy/startup/tasks/device_info_task.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:permission_handler/permission_handler.dart'; +import 'package:universal_platform/universal_platform.dart'; class PermissionChecker { static Future checkPhotoPermission(BuildContext context) async { @@ -48,7 +48,7 @@ class PermissionChecker { } else if (status.isDenied) { // https://github.com/Baseflow/flutter-permission-handler/issues/1262#issuecomment-2006340937 Permission permission = Permission.photos; - if (defaultTargetPlatform == TargetPlatform.android && + if (UniversalPlatform.isAndroid && ApplicationInfo.androidSDKVersion <= 32) { permission = Permission.storage; } diff --git a/frontend/appflowy_flutter/lib/shared/popup_menu/appflowy_popup_menu.dart b/frontend/appflowy_flutter/lib/shared/popup_menu/appflowy_popup_menu.dart index 1e9aa1a3c3..786d666060 100644 --- a/frontend/appflowy_flutter/lib/shared/popup_menu/appflowy_popup_menu.dart +++ b/frontend/appflowy_flutter/lib/shared/popup_menu/appflowy_popup_menu.dart @@ -1613,7 +1613,7 @@ class _PopupMenuDefaultsM3 extends PopupMenuThemeData { return WidgetStateProperty.resolveWith((Set states) { final TextStyle style = _textTheme.labelLarge!; if (states.contains(WidgetState.disabled)) { - return style.apply(color: _colors.onSurface.withOpacity(0.38)); + return style.apply(color: _colors.onSurface.withValues(alpha: 0.38)); } return style.apply(color: _colors.onSurface); }); diff --git a/frontend/appflowy_flutter/lib/shared/version_checker/version_checker.dart b/frontend/appflowy_flutter/lib/shared/version_checker/version_checker.dart new file mode 100644 index 0000000000..6e50a922a7 --- /dev/null +++ b/frontend/appflowy_flutter/lib/shared/version_checker/version_checker.dart @@ -0,0 +1,96 @@ +import 'package:appflowy/core/helpers/url_launcher.dart'; +import 'package:appflowy/startup/tasks/device_info_task.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:auto_updater/auto_updater.dart'; +import 'package:collection/collection.dart'; +import 'package:http/http.dart' as http; +import 'package:universal_platform/universal_platform.dart'; +import 'package:xml/xml.dart' as xml; + +final versionChecker = VersionChecker(); + +/// Version checker class to handle update checks using appcast XML feeds +class VersionChecker { + factory VersionChecker() => _instance; + + VersionChecker._internal(); + String? _feedUrl; + + static final VersionChecker _instance = VersionChecker._internal(); + + /// Sets the appcast XML feed URL + void setFeedUrl(String url) { + _feedUrl = url; + + if (UniversalPlatform.isWindows || UniversalPlatform.isMacOS) { + autoUpdater.setFeedURL(url); + // disable the auto update check + autoUpdater.setScheduledCheckInterval(0); + } + } + + /// Checks for updates by fetching and parsing the appcast XML + /// Returns a list of [AppcastItem] or throws an exception if the feed URL is not set + Future checkForUpdateInformation() async { + if (_feedUrl == null) { + Log.error('Feed URL is not set'); + return null; + } + + try { + final response = await http.get(Uri.parse(_feedUrl!)); + if (response.statusCode != 200) { + Log.info('Failed to fetch appcast XML: ${response.statusCode}'); + return null; + } + + // Parse XML content + final document = xml.XmlDocument.parse(response.body); + final items = document.findAllElements('item'); + + // Convert XML items to AppcastItem objects + return items + .map(_parseAppcastItem) + .nonNulls + .firstWhereOrNull((e) => e.os == ApplicationInfo.os); + } catch (e) { + Log.info('Failed to check for updates: $e'); + } + + return null; + } + + /// For Windows and macOS, calling this API will trigger the auto updater to check for updates + /// For Linux, it will open the official website in the browser if there is a new version + + Future checkForUpdate() async { + if (UniversalPlatform.isLinux) { + // open the official website in the browser + await afLaunchUrlString('https://appflowy.com/download'); + } else { + await autoUpdater.checkForUpdates(); + } + } + + AppcastItem? _parseAppcastItem(xml.XmlElement item) { + final enclosure = item.findElements('enclosure').firstOrNull; + return AppcastItem.fromJson({ + 'title': item.findElements('title').firstOrNull?.innerText, + 'versionString': item + .findElements('sparkle:shortVersionString') + .firstOrNull + ?.innerText, + 'displayVersionString': item + .findElements('sparkle:shortVersionString') + .firstOrNull + ?.innerText, + 'releaseNotesUrl': + item.findElements('releaseNotesLink').firstOrNull?.innerText, + 'pubDate': item.findElements('pubDate').firstOrNull?.innerText, + 'fileURL': enclosure?.getAttribute('url') ?? '', + 'os': enclosure?.getAttribute('sparkle:os') ?? '', + 'criticalUpdate': + enclosure?.getAttribute('sparkle:criticalUpdate') ?? false, + }); + } +} diff --git a/frontend/appflowy_flutter/lib/startup/deps_resolver.dart b/frontend/appflowy_flutter/lib/startup/deps_resolver.dart index 71f8935ee2..5a8c0fa651 100644 --- a/frontend/appflowy_flutter/lib/startup/deps_resolver.dart +++ b/frontend/appflowy_flutter/lib/startup/deps_resolver.dart @@ -3,19 +3,16 @@ import 'package:appflowy/core/network_monitor.dart'; import 'package:appflowy/env/cloud_env.dart'; import 'package:appflowy/plugins/document/application/prelude.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/ai_client.dart'; import 'package:appflowy/plugins/trash/application/prelude.dart'; import 'package:appflowy/shared/appflowy_cache_manager.dart'; import 'package:appflowy/shared/custom_image_cache_manager.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/tasks/appflowy_cloud_task.dart'; -import 'package:appflowy/user/application/ai_service.dart'; import 'package:appflowy/user/application/auth/af_cloud_auth_service.dart'; import 'package:appflowy/user/application/auth/auth_service.dart'; import 'package:appflowy/user/application/prelude.dart'; import 'package:appflowy/user/application/reminder/reminder_bloc.dart'; import 'package:appflowy/user/application/user_listener.dart'; -import 'package:appflowy/user/application/user_service.dart'; import 'package:appflowy/user/presentation/router.dart'; import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart'; import 'package:appflowy/workspace/application/edit_panel/edit_panel_bloc.dart'; @@ -83,20 +80,6 @@ void _resolveCommonService( () => mode.isTest ? MockApplicationDataStorage() : ApplicationDataStorage(), ); - getIt.registerFactoryAsync( - () async { - final result = await UserBackendService.getCurrentUserProfile(); - return result.fold( - (s) { - return AppFlowyAIService(); - }, - (e) { - throw Exception('Failed to get user profile: ${e.msg}'); - }, - ); - }, - ); - getIt.registerFactory( () => ClipboardService(), ); @@ -119,7 +102,7 @@ void _resolveUserDeps(GetIt getIt, IntegrationMode mode) { case AuthenticatorType.local: getIt.registerFactory( () => BackendAuthService( - AuthenticatorPB.Local, + AuthTypePB.Local, ), ); break; diff --git a/frontend/appflowy_flutter/lib/startup/plugin/plugin.dart b/frontend/appflowy_flutter/lib/startup/plugin/plugin.dart index 920a994927..5bb08e3fdf 100644 --- a/frontend/appflowy_flutter/lib/startup/plugin/plugin.dart +++ b/frontend/appflowy_flutter/lib/startup/plugin/plugin.dart @@ -1,4 +1,4 @@ -library flowy_plugin; +library; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/startup/plugin/plugin.dart'; diff --git a/frontend/appflowy_flutter/lib/startup/startup.dart b/frontend/appflowy_flutter/lib/startup/startup.dart index ac0dfbf88a..7a282b3856 100644 --- a/frontend/appflowy_flutter/lib/startup/startup.dart +++ b/frontend/appflowy_flutter/lib/startup/startup.dart @@ -2,7 +2,9 @@ import 'dart:async'; import 'dart:io'; import 'package:appflowy/env/cloud_env.dart'; -import 'package:appflowy/startup/tasks/feature_flag_task.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/desktop_floating_toolbar.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/desktop_toolbar/link/link_hover_menu.dart'; +import 'package:appflowy/util/expand_views.dart'; import 'package:appflowy/workspace/application/settings/prelude.dart'; import 'package:appflowy_backend/appflowy_backend.dart'; import 'package:appflowy_backend/log.dart'; @@ -119,7 +121,7 @@ class FlowyRunner { // this task should be second task, for handling memory leak. // there's a flag named _enable in memory_leak_detector.dart. If it's false, the task will be ignored. MemoryLeakDetectorTask(), - const DebugTask(), + DebugTask(), const FeatureFlagTask(), // localization @@ -138,6 +140,8 @@ class FlowyRunner { // The DeviceOrApplicationInfoTask should be placed before the AppWidgetTask to fetch the app information. // It is unable to get the device information from the test environment. const ApplicationInfoTask(), + // The auto update task should be placed after the ApplicationInfoTask to fetch the latest version. + if (!mode.isIntegrationTest) AutoUpdateTask(), const HotKeyTask(), if (isAppFlowyCloudEnabled) InitAppFlowyCloudTask(), const InitAppWidgetTask(), @@ -182,6 +186,11 @@ Future initGetIt( }, ); getIt.registerSingleton(PluginSandbox()); + getIt.registerSingleton(ViewExpanderRegistry()); + getIt.registerSingleton(LinkHoverTriggers()); + getIt.registerSingleton( + FloatingToolbarController(), + ); await DependencyResolver.resolve(getIt, mode); } @@ -207,6 +216,7 @@ abstract class LaunchTask { LaunchTaskType get type => LaunchTaskType.dataProcessing; Future initialize(LaunchContext context); + Future dispose(); } @@ -248,7 +258,9 @@ enum IntegrationMode { // test mode bool get isTest => isUnitTest || isIntegrationTest; + bool get isUnitTest => this == IntegrationMode.unitTest; + bool get isIntegrationTest => this == IntegrationMode.integrationTest; // release mode diff --git a/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart b/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart index a398db3061..48e76cecbc 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart @@ -7,19 +7,21 @@ import 'package:appflowy/shared/feature_flags.dart'; import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/user/application/user_settings_service.dart'; +import 'package:appflowy/util/string_extension.dart'; import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart'; import 'package:appflowy/workspace/application/action_navigation/navigation_action.dart'; import 'package:appflowy/workspace/application/command_palette/command_palette_bloc.dart'; import 'package:appflowy/workspace/application/notification/notification_service.dart'; import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; +import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart'; import 'package:appflowy/workspace/application/settings/notifications/notification_settings_cubit.dart'; import 'package:appflowy/workspace/application/sidebar/rename_view/rename_view_bloc.dart'; import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy/workspace/presentation/command_palette/command_palette.dart'; -import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; @@ -64,7 +66,6 @@ class InitAppWidgetTask extends LaunchTask { child: widget, ); - Bloc.observer = ApplicationBlocObserver(); runApp( EasyLocalization( supportedLocales: const [ @@ -100,6 +101,7 @@ class InitAppWidgetTask extends LaunchTask { Locale('zh', 'TW'), Locale('fa'), Locale('hin'), + Locale('mr', 'IN'), ], path: 'assets/translations', fallbackLocale: const Locale('en'), @@ -138,6 +140,8 @@ class _ApplicationWidgetState extends State { final _commandPaletteNotifier = ValueNotifier(false); + final themeBuilder = AppFlowyDefaultTheme(); + @override void initState() { super.initState(); @@ -226,22 +230,6 @@ class _ApplicationWidgetState extends State { } }, child: MaterialApp.router( - builder: (context, child) => MediaQuery( - // use the 1.0 as the textScaleFactor to avoid the text size - // affected by the system setting. - data: MediaQuery.of(context).copyWith( - textScaler: TextScaler.linear(state.textScaleFactor), - ), - child: overlayManagerBuilder( - context, - !UniversalPlatform.isMobile && FeatureFlag.search.isOn - ? CommandPalette( - notifier: _commandPaletteNotifier, - child: child, - ) - : child, - ), - ), debugShowCheckedModeBanner: false, theme: state.lightTheme, darkTheme: state.darkTheme, @@ -250,6 +238,35 @@ class _ApplicationWidgetState extends State { supportedLocales: context.supportedLocales, locale: state.locale, routerConfig: routerConfig, + builder: (context, child) { + final brightness = Theme.of(context).brightness; + final fontFamily = + state.font.orDefault(defaultFontFamily); + + return AppFlowyTheme( + data: brightness == Brightness.light + ? themeBuilder.light(fontFamily: fontFamily) + : themeBuilder.dark(fontFamily: fontFamily), + child: MediaQuery( + // use the 1.0 as the textScaleFactor to avoid the text size + // affected by the system setting. + data: MediaQuery.of(context).copyWith( + textScaler: + TextScaler.linear(state.textScaleFactor), + ), + child: overlayManagerBuilder( + context, + !UniversalPlatform.isMobile && + FeatureFlag.search.isOn + ? CommandPalette( + notifier: _commandPaletteNotifier, + child: child, + ) + : child, + ), + ), + ); + }, ), ), ), @@ -283,14 +300,6 @@ class AppGlobals { static BuildContext get context => rootNavKey.currentContext!; } -class ApplicationBlocObserver extends BlocObserver { - @override - void onError(BlocBase bloc, Object error, StackTrace stackTrace) { - Log.debug(error); - super.onError(bloc, error, stackTrace); - } -} - Future appTheme(String themeName) async { if (themeName.isEmpty) { return AppTheme.fallback; diff --git a/frontend/appflowy_flutter/lib/startup/tasks/app_window_size_manager.dart b/frontend/appflowy_flutter/lib/startup/tasks/app_window_size_manager.dart index 7d41f2dceb..5636ed70cb 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/app_window_size_manager.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/app_window_size_manager.dart @@ -12,6 +12,10 @@ class WindowSizeManager { static const double maxWindowHeight = 8192.0; static const double maxWindowWidth = 8192.0; + // Default windows size + static const double defaultWindowHeight = 960.0; + static const double defaultWindowWidth = 1280.0; + static const double maxScaleFactor = 2.0; static const double minScaleFactor = 0.5; @@ -36,8 +40,8 @@ class WindowSizeManager { Future getSize() async { final defaultWindowSize = jsonEncode( { - WindowSizeManager.height: minWindowHeight, - WindowSizeManager.width: minWindowWidth, + WindowSizeManager.height: defaultWindowHeight, + WindowSizeManager.width: defaultWindowWidth, }, ); final windowSize = await getIt().get(KVKeys.windowSize); diff --git a/frontend/appflowy_flutter/lib/startup/tasks/appflowy_cloud_task.dart b/frontend/appflowy_flutter/lib/startup/tasks/appflowy_cloud_task.dart index 2c22b8a01e..362b27a85a 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/appflowy_cloud_task.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/appflowy_cloud_task.dart @@ -83,6 +83,13 @@ class AppFlowyCloudDeepLink { void unsubscribeDeepLinkLoadingState(VoidCallback listener) => _stateNotifier?.removeListener(listener); + Future passGotrueTokenResponse( + GotrueTokenResponsePB gotrueTokenResponse, + ) async { + final uri = _buildDeepLinkUri(gotrueTokenResponse); + await _handleUri(uri); + } + Future _handleUri( Uri? uri, ) async { @@ -105,7 +112,7 @@ class AppFlowyCloudDeepLink { (_) async { final deviceId = await getDeviceId(); final payload = OauthSignInPB( - authenticator: AuthenticatorPB.AppFlowyCloud, + authenticator: AuthTypePB.Server, map: { AuthServiceMapKeys.signInURL: uri.toString(), AuthServiceMapKeys.deviceId: deviceId, @@ -129,7 +136,6 @@ class AppFlowyCloudDeepLink { final context = AppGlobals.rootNavKey.currentState?.context; if (context != null) { showToastNotification( - context, message: err.msg, ); } @@ -173,6 +179,57 @@ class AppFlowyCloudDeepLink { bool _isPaymentSuccessUri(Uri uri) { return uri.host == 'payment-success'; } + + Uri? _buildDeepLinkUri(GotrueTokenResponsePB gotrueTokenResponse) { + final params = {}; + + if (gotrueTokenResponse.hasAccessToken() && + gotrueTokenResponse.accessToken.isNotEmpty) { + params['access_token'] = gotrueTokenResponse.accessToken; + } + + if (gotrueTokenResponse.hasExpiresAt()) { + params['expires_at'] = gotrueTokenResponse.expiresAt.toString(); + } + + if (gotrueTokenResponse.hasExpiresIn()) { + params['expires_in'] = gotrueTokenResponse.expiresIn.toString(); + } + + if (gotrueTokenResponse.hasProviderRefreshToken() && + gotrueTokenResponse.providerRefreshToken.isNotEmpty) { + params['provider_refresh_token'] = + gotrueTokenResponse.providerRefreshToken; + } + + if (gotrueTokenResponse.hasProviderAccessToken() && + gotrueTokenResponse.providerAccessToken.isNotEmpty) { + params['provider_token'] = gotrueTokenResponse.providerAccessToken; + } + + if (gotrueTokenResponse.hasRefreshToken() && + gotrueTokenResponse.refreshToken.isNotEmpty) { + params['refresh_token'] = gotrueTokenResponse.refreshToken; + } + + if (gotrueTokenResponse.hasTokenType() && + gotrueTokenResponse.tokenType.isNotEmpty) { + params['token_type'] = gotrueTokenResponse.tokenType; + } + + if (params.isEmpty) { + return null; + } + + final fragment = params.entries + .map( + (e) => + '${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value)}', + ) + .join('&'); + + return Uri.parse('appflowy-flutter://login-callback#$fragment'); + } } class InitAppFlowyCloudTask extends LaunchTask { diff --git a/frontend/appflowy_flutter/lib/startup/tasks/auto_update_task.dart b/frontend/appflowy_flutter/lib/startup/tasks/auto_update_task.dart new file mode 100644 index 0000000000..b666392544 --- /dev/null +++ b/frontend/appflowy_flutter/lib/startup/tasks/auto_update_task.dart @@ -0,0 +1,205 @@ +import 'dart:async'; + +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/shared/version_checker/version_checker.dart'; +import 'package:appflowy/startup/tasks/app_widget.dart'; +import 'package:appflowy/startup/tasks/device_info_task.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:auto_updater/auto_updater.dart'; +import 'package:collection/collection.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:universal_platform/universal_platform.dart'; + +import '../startup.dart'; + +class AutoUpdateTask extends LaunchTask { + AutoUpdateTask(); + + static const _feedUrl = + 'https://github.com/AppFlowy-IO/AppFlowy/releases/latest/download/appcast-{os}-{arch}.xml'; + final _listener = _AppFlowyAutoUpdaterListener(); + + @override + Future initialize(LaunchContext context) async { + // the auto updater is not supported on mobile + if (UniversalPlatform.isMobile) { + return; + } + + // don't use await here, because the auto updater is not a blocking operation + unawaited(_setupAutoUpdater()); + + ApplicationInfo.isCriticalUpdateNotifier.addListener( + _showCriticalUpdateDialog, + ); + } + + @override + Future dispose() async { + autoUpdater.removeListener(_listener); + + ApplicationInfo.isCriticalUpdateNotifier.removeListener( + _showCriticalUpdateDialog, + ); + } + + // On macOS and windows, we use auto_updater to check for updates. + // On linux, we use the version checker to check for updates because the auto_updater is not supported. + Future _setupAutoUpdater() async { + Log.info( + '[AutoUpdate] current version: ${ApplicationInfo.applicationVersion}, current cpu architecture: ${ApplicationInfo.architecture}', + ); + + // Since the appcast.xml is not supported the arch, we separate the feed url by os and arch. + final feedUrl = _feedUrl + .replaceAll('{os}', ApplicationInfo.os) + .replaceAll('{arch}', ApplicationInfo.architecture); + + // the auto updater is only supported on macOS and windows, so we don't need to check the platform + if (UniversalPlatform.isMacOS || UniversalPlatform.isWindows) { + autoUpdater.addListener(_listener); + } + + Log.info('[AutoUpdate] feed url: $feedUrl'); + + versionChecker.setFeedUrl(feedUrl); + final item = await versionChecker.checkForUpdateInformation(); + if (item != null) { + ApplicationInfo.latestAppcastItem = item; + ApplicationInfo.latestVersionNotifier.value = + item.displayVersionString ?? ''; + } + } + + void _showCriticalUpdateDialog() { + showCustomConfirmDialog( + context: AppGlobals.rootNavKey.currentContext!, + title: LocaleKeys.autoUpdate_criticalUpdateTitle.tr(), + description: LocaleKeys.autoUpdate_criticalUpdateDescription.tr( + namedArgs: { + 'currentVersion': ApplicationInfo.applicationVersion, + 'newVersion': ApplicationInfo.latestVersion, + }, + ), + builder: (context) => const SizedBox.shrink(), + // if the update is critical, dont allow the user to dismiss the dialog + barrierDismissible: false, + showCloseButton: false, + enableKeyboardListener: false, + closeOnConfirm: false, + confirmLabel: LocaleKeys.autoUpdate_criticalUpdateButton.tr(), + onConfirm: () async { + await versionChecker.checkForUpdate(); + }, + ); + } +} + +class _AppFlowyAutoUpdaterListener extends UpdaterListener { + @override + void onUpdaterBeforeQuitForUpdate(AppcastItem? item) {} + + @override + void onUpdaterCheckingForUpdate(Appcast? appcast) { + // Due to the reason documented in the following link, the update will not be found if the user has skipped the update. + // We have to check the skipped version manually. + // https://sparkle-project.org/documentation/api-reference/Classes/SPUUpdater.html#/c:objc(cs)SPUUpdater(im)checkForUpdateInformation + final items = appcast?.items; + if (items != null) { + final String? currentPlatform; + if (UniversalPlatform.isMacOS) { + currentPlatform = 'macos'; + } else if (UniversalPlatform.isWindows) { + currentPlatform = 'windows'; + } else { + currentPlatform = null; + } + + final matchingItem = items.firstWhereOrNull( + (item) => item.os == currentPlatform, + ); + + if (matchingItem != null) { + _updateVersionNotifier(matchingItem); + + Log.info( + '[AutoUpdate] latest version: ${matchingItem.displayVersionString}', + ); + } + } + } + + @override + void onUpdaterError(UpdaterError? error) { + Log.error('[AutoUpdate] On update error: $error'); + } + + @override + void onUpdaterUpdateNotAvailable(UpdaterError? error) { + Log.info('[AutoUpdate] Update not available $error'); + } + + @override + void onUpdaterUpdateAvailable(AppcastItem? item) { + _updateVersionNotifier(item); + + Log.info('[AutoUpdate] Update available: ${item?.displayVersionString}'); + } + + @override + void onUpdaterUpdateDownloaded(AppcastItem? item) { + Log.info('[AutoUpdate] Update downloaded: ${item?.displayVersionString}'); + } + + @override + void onUpdaterUpdateCancelled(AppcastItem? item) { + _updateVersionNotifier(item); + + Log.info('[AutoUpdate] Update cancelled: ${item?.displayVersionString}'); + } + + @override + void onUpdaterUpdateInstalled(AppcastItem? item) { + _updateVersionNotifier(item); + + Log.info('[AutoUpdate] Update installed: ${item?.displayVersionString}'); + } + + @override + void onUpdaterUpdateSkipped(AppcastItem? item) { + _updateVersionNotifier(item); + + Log.info('[AutoUpdate] Update skipped: ${item?.displayVersionString}'); + } + + void _updateVersionNotifier(AppcastItem? item) { + if (item != null) { + ApplicationInfo.latestAppcastItem = item; + ApplicationInfo.latestVersionNotifier.value = + item.displayVersionString ?? ''; + } + } +} + +class AppFlowyAutoUpdateVersion { + AppFlowyAutoUpdateVersion({ + required this.latestVersion, + required this.currentVersion, + required this.isForceUpdate, + }); + + factory AppFlowyAutoUpdateVersion.initial() => AppFlowyAutoUpdateVersion( + latestVersion: '0.0.0', + currentVersion: '0.0.0', + isForceUpdate: false, + ); + + final String latestVersion; + final String currentVersion; + + final bool isForceUpdate; + + bool get isUpdateAvailable => latestVersion != currentVersion; +} diff --git a/frontend/appflowy_flutter/lib/startup/tasks/debug_task.dart b/frontend/appflowy_flutter/lib/startup/tasks/debug_task.dart index 082e25e250..9a34e84f70 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/debug_task.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/debug_task.dart @@ -1,18 +1,45 @@ +import 'package:appflowy/startup/startup.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:talker/talker.dart'; +import 'package:talker_bloc_logger/talker_bloc_logger.dart'; import 'package:universal_platform/universal_platform.dart'; -import '../startup.dart'; - class DebugTask extends LaunchTask { - const DebugTask(); + DebugTask(); + + final Talker talker = Talker(); @override Future initialize(LaunchContext context) async { - // the hotkey manager is not supported on mobile + // hide the keyboard on mobile if (UniversalPlatform.isMobile && kDebugMode) { await SystemChannels.textInput.invokeMethod('TextInput.hide'); } + + // log the bloc events + if (kDebugMode) { + Bloc.observer = TalkerBlocObserver( + talker: talker, + settings: TalkerBlocLoggerSettings( + // Disabled by default to prevent mixing with AppFlowy logs + // Enable to observe all bloc events + enabled: false, + printEventFullData: false, + printStateFullData: false, + printChanges: true, + printClosings: true, + printCreations: true, + transitionFilter: (_, transition) { + // By default, observe all transitions + // You can add your own filter here if needed + // when you want to observer a specific bloc + return true; + }, + ), + ); + } } @override diff --git a/frontend/appflowy_flutter/lib/startup/tasks/device_info_task.dart b/frontend/appflowy_flutter/lib/startup/tasks/device_info_task.dart index 1558eefa53..2c90afbdda 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/device_info_task.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/device_info_task.dart @@ -1,8 +1,11 @@ import 'dart:io'; import 'package:appflowy_backend/log.dart'; +import 'package:auto_updater/auto_updater.dart'; import 'package:device_info_plus/device_info_plus.dart'; +import 'package:flutter/foundation.dart'; import 'package:package_info_plus/package_info_plus.dart'; +import 'package:version/version.dart'; import '../startup.dart'; @@ -11,10 +14,42 @@ class ApplicationInfo { static String applicationVersion = ''; static String buildNumber = ''; static String deviceId = ''; + static String architecture = ''; + static String os = ''; // macOS major version static int? macOSMajorVersion; static int? macOSMinorVersion; + + // latest version + static ValueNotifier latestVersionNotifier = ValueNotifier(''); + // the version number is like 0.9.0 + static String get latestVersion => latestVersionNotifier.value; + + // If the latest version is greater than the current version, it means there is an update available + static bool get isUpdateAvailable { + try { + if (latestVersion.isEmpty) { + return false; + } + return Version.parse(latestVersion) > Version.parse(applicationVersion); + } catch (e) { + return false; + } + } + + // the latest appcast item + static AppcastItem? _latestAppcastItem; + static AppcastItem? get latestAppcastItem => _latestAppcastItem; + static set latestAppcastItem(AppcastItem? value) { + _latestAppcastItem = value; + + isCriticalUpdateNotifier.value = value?.criticalUpdate == true; + } + + // is critical update + static ValueNotifier isCriticalUpdateNotifier = ValueNotifier(false); + static bool get isCriticalUpdate => isCriticalUpdateNotifier.value; } class ApplicationInfoTask extends LaunchTask { @@ -36,38 +71,54 @@ class ApplicationInfoTask extends LaunchTask { ApplicationInfo.androidSDKVersion = androidInfo.version.sdkInt; } - if (Platform.isAndroid || Platform.isIOS) { - ApplicationInfo.applicationVersion = packageInfo.version; - ApplicationInfo.buildNumber = packageInfo.buildNumber; - } + ApplicationInfo.applicationVersion = packageInfo.version; + ApplicationInfo.buildNumber = packageInfo.buildNumber; String? deviceId; + String? architecture; + String? os; try { if (Platform.isAndroid) { final AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo; deviceId = androidInfo.device; + architecture = androidInfo.supportedAbis.firstOrNull; + os = 'android'; } else if (Platform.isIOS) { final IosDeviceInfo iosInfo = await deviceInfoPlugin.iosInfo; deviceId = iosInfo.identifierForVendor; + architecture = iosInfo.utsname.machine; + os = 'ios'; } else if (Platform.isMacOS) { final MacOsDeviceInfo macInfo = await deviceInfoPlugin.macOsInfo; deviceId = macInfo.systemGUID; + architecture = macInfo.arch; + os = 'macos'; } else if (Platform.isWindows) { final WindowsDeviceInfo windowsInfo = await deviceInfoPlugin.windowsInfo; deviceId = windowsInfo.deviceId; + // we only support x86_64 on Windows + architecture = 'x86_64'; + os = 'windows'; } else if (Platform.isLinux) { final LinuxDeviceInfo linuxInfo = await deviceInfoPlugin.linuxInfo; deviceId = linuxInfo.machineId; + // we only support x86_64 on Linux + architecture = 'x86_64'; + os = 'linux'; } else { deviceId = null; + architecture = null; + os = null; } } catch (e) { Log.error('Failed to get platform version, $e'); } ApplicationInfo.deviceId = deviceId ?? ''; + ApplicationInfo.architecture = architecture ?? ''; + ApplicationInfo.os = os ?? ''; } @override diff --git a/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart b/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart index 27bfdcee5b..e64e0f98de 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart @@ -29,6 +29,7 @@ import 'package:appflowy/user/application/auth/auth_service.dart'; import 'package:appflowy/user/presentation/presentation.dart'; import 'package:appflowy/workspace/presentation/home/desktop_home_screen.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/feature_flags/mobile_feature_flag_screen.dart'; +import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:flowy_infra/time/duration.dart'; import 'package:flutter/foundation.dart'; @@ -50,7 +51,6 @@ GoRouter generateRouter(Widget child) { // Routes in both desktop and mobile _signInScreenRoute(), _skipLogInScreenRoute(), - _encryptSecretScreenRoute(), _workspaceErrorScreenRoute(), // Desktop only if (UniversalPlatform.isDesktop) _desktopHomeScreenRoute(), @@ -119,18 +119,6 @@ GoRouter generateRouter(Widget child) { ); }, ), - GoRoute( - path: SignUpScreen.routeName, - pageBuilder: (context, state) { - return CustomTransitionPage( - child: SignUpScreen( - router: getIt(), - ), - transitionsBuilder: _buildFadeTransition, - transitionDuration: _slowDuration, - ); - }, - ), ], ); } @@ -285,14 +273,33 @@ GoRoute _mobileEmojiPickerPageRoute() { state.uri.queryParameters[MobileEmojiPickerScreen.pageTitle]; final selectTabs = state.uri.queryParameters[MobileEmojiPickerScreen.selectTabs] ?? ''; - final tabs = selectTabs - .split('-') - .map((e) => PickerTabType.values.byName(e)) - .toList(); + final selectedType = state + .uri.queryParameters[MobileEmojiPickerScreen.iconSelectedType] + ?.toPickerTabType(); + final documentId = + state.uri.queryParameters[MobileEmojiPickerScreen.uploadDocumentId]; + List tabs = []; + try { + tabs = selectTabs + .split('-') + .map((e) => PickerTabType.values.byName(e)) + .toList(); + } on ArgumentError catch (e) { + Log.error('convert selectTabs to pickerTab error', e); + } return MaterialExtendedPage( child: tabs.isEmpty - ? MobileEmojiPickerScreen(title: title) - : MobileEmojiPickerScreen(title: title, tabs: tabs), + ? MobileEmojiPickerScreen( + title: title, + selectedType: selectedType, + documentId: documentId, + ) + : MobileEmojiPickerScreen( + title: title, + selectedType: selectedType, + tabs: tabs, + documentId: documentId, + ), ); }, ); @@ -451,23 +458,6 @@ GoRoute _workspaceErrorScreenRoute() { ); } -GoRoute _encryptSecretScreenRoute() { - return GoRoute( - path: EncryptSecretScreen.routeName, - pageBuilder: (context, state) { - final args = state.extra as Map; - return CustomTransitionPage( - child: EncryptSecretScreen( - user: args[EncryptSecretScreen.argUser], - key: args[EncryptSecretScreen.argKey], - ), - transitionsBuilder: _buildFadeTransition, - transitionDuration: _slowDuration, - ); - }, - ); -} - GoRoute _skipLogInScreenRoute() { return GoRoute( path: SkipLogInScreen.routeName, @@ -510,6 +500,21 @@ GoRoute _mobileEditorScreenRoute() { final blockId = state.uri.queryParameters[MobileDocumentScreen.viewBlockId]; + final selectTabs = + state.uri.queryParameters[MobileDocumentScreen.viewSelectTabs] ?? ''; + List tabs = []; + try { + tabs = selectTabs + .split('-') + .map((e) => PickerTabType.values.byName(e)) + .toList(); + } on ArgumentError catch (e) { + Log.error('convert selectTabs to pickerTab error', e); + } + if (tabs.isEmpty) { + tabs = const [PickerTabType.emoji, PickerTabType.icon]; + } + return MaterialExtendedPage( child: MobileDocumentScreen( id: id, @@ -517,6 +522,7 @@ GoRoute _mobileEditorScreenRoute() { showMoreButton: showMoreButton ?? true, fixedTitle: fixedTitle, blockId: blockId, + tabs: tabs, ), ); }, diff --git a/frontend/appflowy_flutter/lib/startup/tasks/prelude.dart b/frontend/appflowy_flutter/lib/startup/tasks/prelude.dart index 4be5f0f6f7..9e8f9df49a 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/prelude.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/prelude.dart @@ -1,7 +1,9 @@ export 'app_widget.dart'; export 'appflowy_cloud_task.dart'; +export 'auto_update_task.dart'; export 'debug_task.dart'; export 'device_info_task.dart'; +export 'feature_flag_task.dart'; export 'generate_router.dart'; export 'hot_key.dart'; export 'load_plugin.dart'; diff --git a/frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart b/frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart index 58d6aacbc3..c406dd161a 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart @@ -5,9 +5,8 @@ import 'package:appflowy/env/backend_env.dart'; import 'package:appflowy/env/cloud_env.dart'; import 'package:appflowy/user/application/auth/device_id.dart'; import 'package:appflowy_backend/appflowy_backend.dart'; -import 'package:flutter/foundation.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart' as path; +import 'package:path_provider/path_provider.dart'; import '../startup.dart'; @@ -29,7 +28,6 @@ class InitRustSDKTask extends LaunchTask { final dir = customApplicationPath ?? applicationPath; final deviceId = await getDeviceId(); - debugPrint('application path: ${applicationPath.path}'); // Pass the environment variables to the Rust SDK final env = _makeAppFlowyConfiguration( root.path, @@ -75,10 +73,10 @@ Future appFlowyApplicationDataDirectory() async { case IntegrationMode.develop: final Directory documentsDir = await getApplicationSupportDirectory() .then((directory) => directory.create()); - return Directory(path.join(documentsDir.path, 'data_dev')).create(); + return Directory(path.join(documentsDir.path, 'data_dev')); case IntegrationMode.release: final Directory documentsDir = await getApplicationSupportDirectory(); - return Directory(path.join(documentsDir.path, 'data')).create(); + return Directory(path.join(documentsDir.path, 'data')); case IntegrationMode.unitTest: case IntegrationMode.integrationTest: return Directory(path.join(Directory.current.path, '.sandbox')); diff --git a/frontend/appflowy_flutter/lib/user/application/ai_service.dart b/frontend/appflowy_flutter/lib/user/application/ai_service.dart deleted file mode 100644 index e4100cec59..0000000000 --- a/frontend/appflowy_flutter/lib/user/application/ai_service.dart +++ /dev/null @@ -1,134 +0,0 @@ -import 'dart:async'; -import 'dart:ffi'; -import 'dart:isolate'; - -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/ai_client.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/error.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/text_completion.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/ask_ai_action.dart'; -import 'package:appflowy_backend/dispatch/dispatch.dart'; -import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart'; -import 'package:appflowy_result/appflowy_result.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:fixnum/fixnum.dart' as fixnum; - -class AppFlowyAIService implements AIRepository { - @override - Future, AIError>> generateImage({ - required String prompt, - int n = 1, - }) { - throw UnimplementedError(); - } - - @override - Future getStreamedCompletions({ - required String prompt, - required Future Function() onStart, - required Future Function(TextCompletionResponse response) onProcess, - required Future Function() onEnd, - required void Function(AIError error) onError, - String? suffix, - int maxTokens = 2048, - double temperature = 0.3, - bool useAction = false, - }) { - throw UnimplementedError(); - } - - @override - Future streamCompletion({ - required String text, - required CompletionTypePB completionType, - required Future Function() onStart, - required Future Function(String text) onProcess, - required Future Function() onEnd, - required void Function(AIError error) onError, - }) async { - final stream = CompletionStream( - onStart, - onProcess, - onEnd, - onError, - ); - final payload = CompleteTextPB( - text: text, - completionType: completionType, - streamPort: fixnum.Int64(stream.nativePort), - ); - - // ignore: unawaited_futures - AIEventCompleteText(payload).send(); - return stream; - } -} - -CompletionTypePB completionTypeFromInt(AskAIAction action) { - switch (action) { - case AskAIAction.summarize: - return CompletionTypePB.MakeShorter; - case AskAIAction.fixSpelling: - return CompletionTypePB.SpellingAndGrammar; - case AskAIAction.improveWriting: - return CompletionTypePB.ImproveWriting; - case AskAIAction.makeItLonger: - return CompletionTypePB.MakeLonger; - } -} - -class CompletionStream { - CompletionStream( - Future Function() onStart, - Future Function(String text) onProcess, - Future Function() onEnd, - void Function(AIError error) onError, - ) { - _port.handler = _controller.add; - _subscription = _controller.stream.listen( - (event) async { - if (event == "AI_RESPONSE_LIMIT") { - onError( - AIError( - message: LocaleKeys.sideBar_aiResponseLimit.tr(), - code: AIErrorCode.aiResponseLimitExceeded, - ), - ); - } - - if (event.startsWith("start:")) { - await onStart(); - } - - if (event.startsWith("data:")) { - await onProcess(event.substring(5)); - } - - if (event.startsWith("finish:")) { - await onEnd(); - } - - if (event.startsWith("error:")) { - onError(AIError(message: event.substring(6))); - } - }, - ); - } - - final RawReceivePort _port = RawReceivePort(); - final StreamController _controller = StreamController.broadcast(); - late StreamSubscription _subscription; - int get nativePort => _port.sendPort.nativePort; - - Future dispose() async { - await _controller.close(); - await _subscription.cancel(); - _port.close(); - } - - StreamSubscription listen( - void Function(String event)? onData, - ) { - return _controller.stream.listen(onData); - } -} diff --git a/frontend/appflowy_flutter/lib/user/application/auth/af_cloud_auth_service.dart b/frontend/appflowy_flutter/lib/user/application/auth/af_cloud_auth_service.dart index 149bddc951..4f4cece9bb 100644 --- a/frontend/appflowy_flutter/lib/user/application/auth/af_cloud_auth_service.dart +++ b/frontend/appflowy_flutter/lib/user/application/auth/af_cloud_auth_service.dart @@ -18,7 +18,7 @@ class AppFlowyCloudAuthService implements AuthService { AppFlowyCloudAuthService(); final BackendAuthService _backendAuthService = BackendAuthService( - AuthenticatorPB.AppFlowyCloud, + AuthTypePB.Server, ); @override @@ -32,12 +32,17 @@ class AppFlowyCloudAuthService implements AuthService { } @override - Future> signInWithEmailPassword({ + Future> + signInWithEmailPassword({ required String email, required String password, Map params = const {}, }) async { - throw UnimplementedError(); + return _backendAuthService.signInWithEmailPassword( + email: email, + password: password, + params: params, + ); } @override @@ -106,6 +111,17 @@ class AppFlowyCloudAuthService implements AuthService { ); } + @override + Future> signInWithPasscode({ + required String email, + required String passcode, + }) async { + return _backendAuthService.signInWithPasscode( + email: email, + passcode: passcode, + ); + } + @override Future> getUser() async { return UserBackendService.getCurrentUserProfile(); diff --git a/frontend/appflowy_flutter/lib/user/application/auth/af_cloud_mock_auth_service.dart b/frontend/appflowy_flutter/lib/user/application/auth/af_cloud_mock_auth_service.dart index 5f8ea7cac6..8be71dc648 100644 --- a/frontend/appflowy_flutter/lib/user/application/auth/af_cloud_mock_auth_service.dart +++ b/frontend/appflowy_flutter/lib/user/application/auth/af_cloud_mock_auth_service.dart @@ -20,7 +20,7 @@ class AppFlowyCloudMockAuthService implements AuthService { final String userEmail; final BackendAuthService _appFlowyAuthService = - BackendAuthService(AuthenticatorPB.AppFlowyCloud); + BackendAuthService(AuthTypePB.Server); @override Future> signUp({ @@ -33,7 +33,8 @@ class AppFlowyCloudMockAuthService implements AuthService { } @override - Future> signInWithEmailPassword({ + Future> + signInWithEmailPassword({ required String email, required String password, Map params = const {}, @@ -47,7 +48,7 @@ class AppFlowyCloudMockAuthService implements AuthService { Map params = const {}, }) async { final payload = SignInUrlPayloadPB.create() - ..authenticator = AuthenticatorPB.AppFlowyCloud + ..authenticator = AuthTypePB.Server // don't use nanoid here, the gotrue server will transform the email ..email = userEmail; @@ -57,7 +58,7 @@ class AppFlowyCloudMockAuthService implements AuthService { return getSignInURLResult.fold( (urlPB) async { final payload = OauthSignInPB( - authenticator: AuthenticatorPB.AppFlowyCloud, + authenticator: AuthTypePB.Server, map: { AuthServiceMapKeys.signInURL: urlPB.signInUrl, AuthServiceMapKeys.deviceId: deviceId, @@ -106,4 +107,12 @@ class AppFlowyCloudMockAuthService implements AuthService { Future> getUser() async { return UserBackendService.getCurrentUserProfile(); } + + @override + Future> signInWithPasscode({ + required String email, + required String passcode, + }) async { + throw UnimplementedError(); + } } diff --git a/frontend/appflowy_flutter/lib/user/application/auth/auth_service.dart b/frontend/appflowy_flutter/lib/user/application/auth/auth_service.dart index 90c6954afe..9879b9a18e 100644 --- a/frontend/appflowy_flutter/lib/user/application/auth/auth_service.dart +++ b/frontend/appflowy_flutter/lib/user/application/auth/auth_service.dart @@ -1,5 +1,5 @@ import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; -import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pbserver.dart'; +import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; import 'package:appflowy_result/appflowy_result.dart'; class AuthServiceMapKeys { @@ -23,7 +23,8 @@ abstract class AuthService { /// /// Returns [UserProfilePB] if the user is authenticated, otherwise returns [FlowyError]. - Future> signInWithEmailPassword({ + Future> + signInWithEmailPassword({ required String email, required String password, Map params, @@ -75,6 +76,17 @@ abstract class AuthService { Map params, }); + /// Authenticates a user with a passcode sent to their email. + /// + /// - `email`: The email address of the user. + /// - `passcode`: The passcode of the user. + /// + /// Returns [UserProfilePB] if the user is authenticated, otherwise returns [FlowyError]. + Future> signInWithPasscode({ + required String email, + required String passcode, + }); + /// Signs out the currently authenticated user. Future signOut(); diff --git a/frontend/appflowy_flutter/lib/user/application/auth/backend_auth_service.dart b/frontend/appflowy_flutter/lib/user/application/auth/backend_auth_service.dart index 9147fb4fb9..cab8cd170c 100644 --- a/frontend/appflowy_flutter/lib/user/application/auth/backend_auth_service.dart +++ b/frontend/appflowy_flutter/lib/user/application/auth/backend_auth_service.dart @@ -6,9 +6,9 @@ import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-user/auth.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart' show SignInPayloadPB, SignUpPayloadPB, UserProfilePB; +import 'package:appflowy_backend/protobuf/flowy-user/workspace.pb.dart'; import 'package:appflowy_result/appflowy_result.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/uuid.dart'; import '../../../generated/locale_keys.g.dart'; import 'device_id.dart'; @@ -16,10 +16,11 @@ import 'device_id.dart'; class BackendAuthService implements AuthService { BackendAuthService(this.authType); - final AuthenticatorPB authType; + final AuthTypePB authType; @override - Future> signInWithEmailPassword({ + Future> + signInWithEmailPassword({ required String email, required String password, Map params = const {}, @@ -29,8 +30,7 @@ class BackendAuthService implements AuthService { ..password = password ..authType = authType ..deviceId = await getDeviceId(); - final response = UserEventSignInWithEmailPassword(request).send(); - return response.then((value) => value); + return UserEventSignInWithEmailPassword(request).send(); } @override @@ -65,15 +65,14 @@ class BackendAuthService implements AuthService { Map params = const {}, }) async { const password = "Guest!@123456"; - final uid = uuid(); - final userEmail = "$uid@appflowy.io"; + final userEmail = "anon@appflowy.io"; final request = SignUpPayloadPB.create() ..name = LocaleKeys.defaultUsername.tr() ..email = userEmail ..password = password // When sign up as guest, the auth type is always local. - ..authType = AuthenticatorPB.Local + ..authType = AuthTypePB.Local ..deviceId = await getDeviceId(); final response = await UserEventSignUp(request).send().then( (value) => value, @@ -84,7 +83,7 @@ class BackendAuthService implements AuthService { @override Future> signUpWithOAuth({ required String platform, - AuthenticatorPB authType = AuthenticatorPB.Local, + AuthTypePB authType = AuthTypePB.Local, Map params = const {}, }) async { return FlowyResult.failure( @@ -107,4 +106,12 @@ class BackendAuthService implements AuthService { // No need to pass the redirect URL. return UserBackendService.signInWithMagicLink(email, ''); } + + @override + Future> signInWithPasscode({ + required String email, + required String passcode, + }) async { + return UserBackendService.signInWithPasscode(email, passcode); + } } diff --git a/frontend/appflowy_flutter/lib/user/application/encrypt_secret_bloc.dart b/frontend/appflowy_flutter/lib/user/application/encrypt_secret_bloc.dart deleted file mode 100644 index 19b8101ae8..0000000000 --- a/frontend/appflowy_flutter/lib/user/application/encrypt_secret_bloc.dart +++ /dev/null @@ -1,114 +0,0 @@ -import 'package:appflowy/plugins/database/application/defines.dart'; -import 'package:appflowy/startup/startup.dart'; -import 'package:appflowy_backend/dispatch/dispatch.dart'; -import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; -import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; -import 'package:appflowy_result/appflowy_result.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; - -import 'auth/auth_service.dart'; - -part 'encrypt_secret_bloc.freezed.dart'; - -class EncryptSecretBloc extends Bloc { - EncryptSecretBloc({required this.user}) - : super(EncryptSecretState.initial()) { - _dispatch(); - } - - final UserProfilePB user; - - void _dispatch() { - on((event, emit) async { - await event.when( - setEncryptSecret: (secret) async { - if (isLoading()) { - return; - } - - final payload = UserSecretPB.create() - ..encryptionSecret = secret - ..encryptionSign = user.encryptionSign - ..encryptionType = user.encryptionType - ..userId = user.id; - final result = await UserEventSetEncryptionSecret(payload).send(); - if (!isClosed) { - add(EncryptSecretEvent.didFinishCheck(result)); - } - emit( - state.copyWith( - loadingState: const LoadingState.loading(), - successOrFail: null, - ), - ); - }, - cancelInputSecret: () async { - await getIt().signOut(); - emit( - state.copyWith( - successOrFail: null, - isSignOut: true, - ), - ); - }, - didFinishCheck: (result) { - result.fold( - (unit) { - emit( - state.copyWith( - loadingState: const LoadingState.loading(), - successOrFail: result, - ), - ); - }, - (err) { - emit( - state.copyWith( - loadingState: LoadingState.finish(FlowyResult.failure(err)), - successOrFail: result, - ), - ); - }, - ); - }, - ); - }); - } - - bool isLoading() { - final loadingState = state.loadingState; - if (loadingState != null) { - return loadingState.when( - loading: () => true, - finish: (_) => false, - idle: () => false, - ); - } - return false; - } -} - -@freezed -class EncryptSecretEvent with _$EncryptSecretEvent { - const factory EncryptSecretEvent.setEncryptSecret(String secret) = - _SetEncryptSecret; - const factory EncryptSecretEvent.didFinishCheck( - FlowyResult result, - ) = _DidFinishCheck; - const factory EncryptSecretEvent.cancelInputSecret() = _CancelInputSecret; -} - -@freezed -class EncryptSecretState with _$EncryptSecretState { - const factory EncryptSecretState({ - required FlowyResult? successOrFail, - required bool isSignOut, - LoadingState? loadingState, - }) = _EncryptSecretState; - - factory EncryptSecretState.initial() => const EncryptSecretState( - successOrFail: null, - isSignOut: false, - ); -} diff --git a/frontend/appflowy_flutter/lib/user/application/password/password_bloc.dart b/frontend/appflowy_flutter/lib/user/application/password/password_bloc.dart new file mode 100644 index 0000000000..80dd5ca3c9 --- /dev/null +++ b/frontend/appflowy_flutter/lib/user/application/password/password_bloc.dart @@ -0,0 +1,241 @@ +import 'dart:convert'; + +import 'package:appflowy/env/cloud_env.dart'; +import 'package:appflowy/user/application/password/password_http_service.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-user/workspace.pb.dart'; +import 'package:appflowy_result/appflowy_result.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'password_bloc.freezed.dart'; + +class PasswordBloc extends Bloc { + PasswordBloc(this.userProfile) : super(PasswordState.initial()) { + on( + (event, emit) async { + await event.when( + init: () async => _init(), + changePassword: (oldPassword, newPassword) async => _onChangePassword( + emit, + oldPassword: oldPassword, + newPassword: newPassword, + ), + setupPassword: (newPassword) async => _onSetupPassword( + emit, + newPassword: newPassword, + ), + forgotPassword: (email) async => _onForgotPassword( + emit, + email: email, + ), + checkHasPassword: () async => _onCheckHasPassword( + emit, + ), + cancel: () {}, + ); + }, + ); + } + + final UserProfilePB userProfile; + late final PasswordHttpService passwordHttpService; + + bool _isInitialized = false; + + Future _init() async { + if (userProfile.workspaceAuthType == AuthTypePB.Local) { + Log.debug('PasswordBloc: skip init because user is local authenticator'); + return; + } + + final baseUrl = await getAppFlowyCloudUrl(); + try { + final authToken = jsonDecode(userProfile.token)['access_token']; + passwordHttpService = PasswordHttpService( + baseUrl: baseUrl, + authToken: authToken, + ); + _isInitialized = true; + } catch (e) { + Log.error('PasswordBloc: _init: error: $e'); + } + } + + Future _onChangePassword( + Emitter emit, { + required String oldPassword, + required String newPassword, + }) async { + if (!_isInitialized) { + Log.info('changePassword: not initialized'); + return; + } + + if (state.isSubmitting) { + Log.info('changePassword: already submitting'); + return; + } + + _clearState(emit, true); + + final result = await passwordHttpService.changePassword( + currentPassword: oldPassword, + newPassword: newPassword, + ); + + emit( + state.copyWith( + isSubmitting: false, + changePasswordResult: result, + ), + ); + } + + Future _onSetupPassword( + Emitter emit, { + required String newPassword, + }) async { + if (!_isInitialized) { + Log.info('setupPassword: not initialized'); + return; + } + + if (state.isSubmitting) { + Log.info('setupPassword: already submitting'); + return; + } + + _clearState(emit, true); + + final result = await passwordHttpService.setupPassword( + newPassword: newPassword, + ); + + emit( + state.copyWith( + isSubmitting: false, + hasPassword: result.fold( + (success) => true, + (error) => false, + ), + setupPasswordResult: result, + ), + ); + } + + Future _onForgotPassword( + Emitter emit, { + required String email, + }) async { + if (!_isInitialized) { + Log.info('forgotPassword: not initialized'); + return; + } + + if (state.isSubmitting) { + Log.info('forgotPassword: already submitting'); + return; + } + + _clearState(emit, true); + + final result = await passwordHttpService.forgotPassword(email: email); + + emit( + state.copyWith( + isSubmitting: false, + forgotPasswordResult: result, + ), + ); + } + + Future _onCheckHasPassword(Emitter emit) async { + if (!_isInitialized) { + Log.info('checkHasPassword: not initialized'); + return; + } + + if (state.isSubmitting) { + Log.info('checkHasPassword: already submitting'); + return; + } + + _clearState(emit, true); + + final result = await passwordHttpService.checkHasPassword(); + + emit( + state.copyWith( + isSubmitting: false, + hasPassword: result.fold( + (success) => success, + (error) => false, + ), + checkHasPasswordResult: result, + ), + ); + } + + void _clearState(Emitter emit, bool isSubmitting) { + emit( + state.copyWith( + isSubmitting: isSubmitting, + changePasswordResult: null, + setupPasswordResult: null, + forgotPasswordResult: null, + checkHasPasswordResult: null, + ), + ); + } +} + +@freezed +class PasswordEvent with _$PasswordEvent { + const factory PasswordEvent.init() = Init; + + // Change password + const factory PasswordEvent.changePassword({ + required String oldPassword, + required String newPassword, + }) = ChangePassword; + + // Setup password + const factory PasswordEvent.setupPassword({ + required String newPassword, + }) = SetupPassword; + + // Forgot password + const factory PasswordEvent.forgotPassword({ + required String email, + }) = ForgotPassword; + + // Check has password + const factory PasswordEvent.checkHasPassword() = CheckHasPassword; + + // Cancel operation + const factory PasswordEvent.cancel() = Cancel; +} + +@freezed +class PasswordState with _$PasswordState { + const factory PasswordState({ + required bool isSubmitting, + required bool hasPassword, + required FlowyResult? changePasswordResult, + required FlowyResult? setupPasswordResult, + required FlowyResult? forgotPasswordResult, + required FlowyResult? checkHasPasswordResult, + }) = _PasswordState; + + factory PasswordState.initial() => const PasswordState( + isSubmitting: false, + hasPassword: false, + changePasswordResult: null, + setupPasswordResult: null, + forgotPasswordResult: null, + checkHasPasswordResult: null, + ); +} diff --git a/frontend/appflowy_flutter/lib/user/application/password/password_http_service.dart b/frontend/appflowy_flutter/lib/user/application/password/password_http_service.dart new file mode 100644 index 0000000000..c56c4f595d --- /dev/null +++ b/frontend/appflowy_flutter/lib/user/application/password/password_http_service.dart @@ -0,0 +1,189 @@ +import 'dart:convert'; + +import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; +import 'package:appflowy_result/appflowy_result.dart'; +import 'package:http/http.dart' as http; + +enum PasswordEndpoint { + changePassword, + forgotPassword, + setupPassword, + checkHasPassword; + + String get path { + switch (this) { + case PasswordEndpoint.changePassword: + return '/gotrue/user/change-password'; + case PasswordEndpoint.forgotPassword: + return '/gotrue/user/recover'; + case PasswordEndpoint.setupPassword: + return '/gotrue/user/change-password'; + case PasswordEndpoint.checkHasPassword: + return '/gotrue/user/auth-info'; + } + } + + String get method { + switch (this) { + case PasswordEndpoint.changePassword: + case PasswordEndpoint.setupPassword: + case PasswordEndpoint.forgotPassword: + return 'POST'; + case PasswordEndpoint.checkHasPassword: + return 'GET'; + } + } + + Uri uri(String baseUrl) => Uri.parse('$baseUrl$path'); +} + +class PasswordHttpService { + PasswordHttpService({ + required this.baseUrl, + required this.authToken, + }); + + final String baseUrl; + final String authToken; + + final http.Client client = http.Client(); + + Map get headers => { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $authToken', + }; + + /// Changes the user's password + /// + /// [currentPassword] - The user's current password + /// [newPassword] - The new password to set + Future> changePassword({ + required String currentPassword, + required String newPassword, + }) async { + final result = await _makeRequest( + endpoint: PasswordEndpoint.changePassword, + body: { + 'current_password': currentPassword, + 'password': newPassword, + }, + errorMessage: 'Failed to change password', + ); + + return result.fold( + (data) => FlowyResult.success(true), + (error) => FlowyResult.failure(error), + ); + } + + /// Sends a password reset email to the user + /// + /// [email] - The email address of the user + Future> forgotPassword({ + required String email, + }) async { + final result = await _makeRequest( + endpoint: PasswordEndpoint.forgotPassword, + body: {'email': email}, + errorMessage: 'Failed to send password reset email', + ); + + return result.fold( + (data) => FlowyResult.success(true), + (error) => FlowyResult.failure(error), + ); + } + + /// Sets up a password for a user that doesn't have one + /// + /// [newPassword] - The new password to set + Future> setupPassword({ + required String newPassword, + }) async { + final result = await _makeRequest( + endpoint: PasswordEndpoint.setupPassword, + body: {'password': newPassword}, + errorMessage: 'Failed to setup password', + ); + + return result.fold( + (data) => FlowyResult.success(true), + (error) => FlowyResult.failure(error), + ); + } + + /// Checks if the user has a password set + Future> checkHasPassword() async { + final result = await _makeRequest( + endpoint: PasswordEndpoint.checkHasPassword, + errorMessage: 'Failed to check password status', + ); + + try { + return result.fold( + (data) => FlowyResult.success(data['has_password'] ?? false), + (error) => FlowyResult.failure(error), + ); + } catch (e) { + return FlowyResult.failure( + FlowyError(msg: 'Failed to check password status: $e'), + ); + } + } + + /// Makes a request to the specified endpoint with the given body + Future> _makeRequest({ + required PasswordEndpoint endpoint, + Map? body, + String errorMessage = 'Request failed', + }) async { + try { + final uri = endpoint.uri(baseUrl); + http.Response response; + + if (endpoint.method == 'POST') { + response = await client.post( + uri, + headers: headers, + body: body != null ? jsonEncode(body) : null, + ); + } else if (endpoint.method == 'GET') { + response = await client.get( + uri, + headers: headers, + ); + } else { + return FlowyResult.failure( + FlowyError(msg: 'Invalid request method: ${endpoint.method}'), + ); + } + + if (response.statusCode == 200) { + if (response.body.isNotEmpty) { + return FlowyResult.success(jsonDecode(response.body)); + } + return FlowyResult.success(true); + } else { + final errorBody = + response.body.isNotEmpty ? jsonDecode(response.body) : {}; + + Log.info( + '${endpoint.name} request failed: ${response.statusCode}, $errorBody ', + ); + + return FlowyResult.failure( + FlowyError( + msg: errorBody['msg'] ?? errorMessage, + ), + ); + } + } catch (e) { + Log.error('${endpoint.name} request failed: error: $e'); + + return FlowyResult.failure( + FlowyError(msg: 'Network error: ${e.toString()}'), + ); + } + } +} diff --git a/frontend/appflowy_flutter/lib/user/application/sign_in_bloc.dart b/frontend/appflowy_flutter/lib/user/application/sign_in_bloc.dart index 6fda156567..9691a1269b 100644 --- a/frontend/appflowy_flutter/lib/user/application/sign_in_bloc.dart +++ b/frontend/appflowy_flutter/lib/user/application/sign_in_bloc.dart @@ -30,12 +30,26 @@ class SignInBloc extends Bloc { on( (event, emit) async { await event.when( - signedInWithUserEmailAndPassword: () async => _onSignIn(emit), - signedInWithOAuth: (platform) async => - _onSignInWithOAuth(emit, platform), - signedInAsGuest: () async => _onSignInAsGuest(emit), - signedWithMagicLink: (email) async => - _onSignInWithMagicLink(emit, email), + signInWithEmailAndPassword: (email, password) async => + _onSignInWithEmailAndPassword( + emit, + email: email, + password: password, + ), + signInWithOAuth: (platform) async => _onSignInWithOAuth( + emit, + platform: platform, + ), + signInAsGuest: () async => _onSignInAsGuest(emit), + signInWithMagicLink: (email) async => _onSignInWithMagicLink( + emit, + email: email, + ), + signInWithPasscode: (email, passcode) async => _onSignInWithPasscode( + emit, + email: email, + passcode: passcode, + ), deepLinkStateChange: (result) => _onDeepLinkStateChange(emit, result), cancel: () { emit( @@ -119,26 +133,34 @@ class SignInBloc extends Bloc { } } - Future _onSignIn(Emitter emit) async { + Future _onSignInWithEmailAndPassword( + Emitter emit, { + required String email, + required String password, + }) async { final result = await authService.signInWithEmailPassword( - email: state.email ?? '', - password: state.password ?? '', + email: email, + password: password, ); emit( result.fold( - (userProfile) => state.copyWith( - isSubmitting: false, - successOrFail: FlowyResult.success(userProfile), - ), + (gotrueTokenResponse) { + getIt().passGotrueTokenResponse( + gotrueTokenResponse, + ); + return state.copyWith( + isSubmitting: false, + ); + }, (error) => _stateFromCode(error), ), ); } Future _onSignInWithOAuth( - Emitter emit, - String platform, - ) async { + Emitter emit, { + required String platform, + }) async { emit( state.copyWith( isSubmitting: true, @@ -161,9 +183,16 @@ class SignInBloc extends Bloc { } Future _onSignInWithMagicLink( - Emitter emit, - String email, - ) async { + Emitter emit, { + required String email, + }) async { + if (state.isSubmitting) { + Log.error('Sign in with magic link is already in progress'); + return; + } + + Log.info('Sign in with magic link: $email'); + emit( state.copyWith( isSubmitting: true, @@ -177,7 +206,50 @@ class SignInBloc extends Bloc { emit( result.fold( - (userProfile) => state.copyWith(isSubmitting: true), + (userProfile) => state.copyWith( + isSubmitting: false, + ), + (error) => _stateFromCode(error), + ), + ); + } + + Future _onSignInWithPasscode( + Emitter emit, { + required String email, + required String passcode, + }) async { + if (state.isSubmitting) { + Log.error('Sign in with passcode is already in progress'); + return; + } + + Log.info('Sign in with passcode: $email, $passcode'); + + emit( + state.copyWith( + isSubmitting: true, + emailError: null, + passwordError: null, + successOrFail: null, + ), + ); + + final result = await authService.signInWithPasscode( + email: email, + passcode: passcode, + ); + + emit( + result.fold( + (gotrueTokenResponse) { + getIt().passGotrueTokenResponse( + gotrueTokenResponse, + ); + return state.copyWith( + isSubmitting: false, + ); + }, (error) => _stateFromCode(error), ), ); @@ -224,10 +296,20 @@ class SignInBloc extends Bloc { emailError: null, ); case ErrorCode.UserUnauthorized: + final errorMsg = error.msg; + String msg = LocaleKeys.signIn_generalError.tr(); + if (errorMsg.contains('rate limit') || + errorMsg.contains('For security purposes')) { + msg = LocaleKeys.signIn_tooFrequentVerificationCodeRequest.tr(); + } else if (errorMsg.contains('invalid')) { + msg = LocaleKeys.signIn_tokenHasExpiredOrInvalid.tr(); + } else if (errorMsg.contains('Invalid login credentials')) { + msg = LocaleKeys.signIn_invalidLoginCredentials.tr(); + } return state.copyWith( isSubmitting: false, successOrFail: FlowyResult.failure( - FlowyError(msg: LocaleKeys.signIn_limitRateError.tr()), + FlowyError(msg: msg), ), ); default: @@ -243,19 +325,35 @@ class SignInBloc extends Bloc { @freezed class SignInEvent with _$SignInEvent { - const factory SignInEvent.signedInWithUserEmailAndPassword() = - SignedInWithUserEmailAndPassword; - const factory SignInEvent.signedInWithOAuth(String platform) = - SignedInWithOAuth; - const factory SignInEvent.signedInAsGuest() = SignedInAsGuest; - const factory SignInEvent.signedWithMagicLink(String email) = - SignedWithMagicLink; - const factory SignInEvent.emailChanged(String email) = EmailChanged; - const factory SignInEvent.passwordChanged(String password) = PasswordChanged; + // Sign in methods + const factory SignInEvent.signInWithEmailAndPassword({ + required String email, + required String password, + }) = SignInWithEmailAndPassword; + const factory SignInEvent.signInWithOAuth({ + required String platform, + }) = SignInWithOAuth; + const factory SignInEvent.signInAsGuest() = SignInAsGuest; + const factory SignInEvent.signInWithMagicLink({ + required String email, + }) = SignInWithMagicLink; + const factory SignInEvent.signInWithPasscode({ + required String email, + required String passcode, + }) = SignInWithPasscode; + + // Event handlers + const factory SignInEvent.emailChanged({ + required String email, + }) = EmailChanged; + const factory SignInEvent.passwordChanged({ + required String password, + }) = PasswordChanged; const factory SignInEvent.deepLinkStateChange(DeepLinkResult result) = DeepLinkStateChange; - const factory SignInEvent.cancel() = _Cancel; - const factory SignInEvent.switchLoginType(LoginType type) = _SwitchLoginType; + + const factory SignInEvent.cancel() = Cancel; + const factory SignInEvent.switchLoginType(LoginType type) = SwitchLoginType; } // we support sign in directly without sign up, but we want to allow the users to sign up if they want to diff --git a/frontend/appflowy_flutter/lib/user/application/user_listener.dart b/frontend/appflowy_flutter/lib/user/application/user_listener.dart index 36d6039d40..d3ebe0201b 100644 --- a/frontend/appflowy_flutter/lib/user/application/user_listener.dart +++ b/frontend/appflowy_flutter/lib/user/application/user_listener.dart @@ -24,7 +24,7 @@ typedef DidUpdateUserWorkspacesCallback = void Function( ); typedef UserProfileNotifyValue = FlowyResult; typedef DidUpdateUserWorkspaceSetting = void Function( - UseAISettingPB settings, + WorkspaceSettingsPB settings, ); class UserListener { @@ -101,10 +101,10 @@ class UserListener { result.map( (r) => onUserWorkspaceUpdated?.call(UserWorkspacePB.fromBuffer(r)), ); - case user.UserNotification.DidUpdateAISetting: + case user.UserNotification.DidUpdateWorkspaceSetting: result.map( - (r) => - onUserWorkspaceSettingUpdated?.call(UseAISettingPB.fromBuffer(r)), + (r) => onUserWorkspaceSettingUpdated + ?.call(WorkspaceSettingsPB.fromBuffer(r)), ); break; default: @@ -113,22 +113,21 @@ class UserListener { } } -typedef WorkspaceSettingNotifyValue - = FlowyResult; +typedef WorkspaceLatestNotifyValue = FlowyResult; class FolderListener { FolderListener(); - final PublishNotifier _settingChangedNotifier = + final PublishNotifier _latestChangedNotifier = PublishNotifier(); FolderNotificationListener? _listener; void start({ - void Function(WorkspaceSettingNotifyValue)? onSettingUpdated, + void Function(WorkspaceLatestNotifyValue)? onLatestUpdated, }) { - if (onSettingUpdated != null) { - _settingChangedNotifier.addPublishListener(onSettingUpdated); + if (onLatestUpdated != null) { + _latestChangedNotifier.addPublishListener(onLatestUpdated); } // The "current-workspace" is predefined in the backend. Do not try to @@ -146,9 +145,9 @@ class FolderListener { switch (ty) { case FolderNotification.DidUpdateWorkspaceSetting: result.fold( - (payload) => _settingChangedNotifier.value = - FlowyResult.success(WorkspaceSettingPB.fromBuffer(payload)), - (error) => _settingChangedNotifier.value = FlowyResult.failure(error), + (payload) => _latestChangedNotifier.value = + FlowyResult.success(WorkspaceLatestPB.fromBuffer(payload)), + (error) => _latestChangedNotifier.value = FlowyResult.failure(error), ); break; default: @@ -158,6 +157,6 @@ class FolderListener { Future stop() async { await _listener?.stop(); - _settingChangedNotifier.dispose(); + _latestChangedNotifier.dispose(); } } diff --git a/frontend/appflowy_flutter/lib/user/application/user_service.dart b/frontend/appflowy_flutter/lib/user/application/user_service.dart index 5a75a4df3e..ff1cfb6575 100644 --- a/frontend/appflowy_flutter/lib/user/application/user_service.dart +++ b/frontend/appflowy_flutter/lib/user/application/user_service.dart @@ -40,8 +40,6 @@ class UserBackendService implements IUserBackendService { String? password, String? email, String? iconUrl, - String? openAIKey, - String? stabilityAiKey, }) { final payload = UpdateUserProfilePayloadPB.create()..id = userId; @@ -61,14 +59,6 @@ class UserBackendService implements IUserBackendService { payload.iconUrl = iconUrl; } - if (openAIKey != null) { - payload.openaiKey = openAIKey; - } - - if (stabilityAiKey != null) { - payload.stabilityAiKey = stabilityAiKey; - } - return UserEventUpdateUserProfile(payload).send(); } @@ -86,6 +76,26 @@ class UserBackendService implements IUserBackendService { return UserEventMagicLinkSignIn(payload).send(); } + static Future> + signInWithPasscode( + String email, + String passcode, + ) async { + final payload = PasscodeSignInPB(email: email, passcode: passcode); + return UserEventPasscodeSignIn(payload).send(); + } + + Future> signInWithPassword( + String email, + String password, + ) { + final payload = SignInPayloadPB( + email: email, + password: password, + ); + return UserEventSignInWithEmailPassword(payload).send(); + } + static Future> signOut() { return UserEventSignOut().send(); } @@ -111,8 +121,13 @@ class UserBackendService implements IUserBackendService { }); } - Future> openWorkspace(String workspaceId) { - final payload = UserWorkspaceIdPB.create()..workspaceId = workspaceId; + Future> openWorkspace( + String workspaceId, + AuthTypePB authType, + ) { + final payload = OpenUserWorkspacePB() + ..workspaceId = workspaceId + ..workspaceAuthType = authType; return UserEventOpenWorkspace(payload).send(); } @@ -125,25 +140,13 @@ class UserBackendService implements IUserBackendService { }); } - Future> createWorkspace( - String name, - String desc, - ) { - final request = CreateWorkspacePayloadPB.create() - ..name = name - ..desc = desc; - return FolderEventCreateFolderWorkspace(request).send().then((result) { - return result.fold( - (workspace) => FlowyResult.success(workspace), - (error) => FlowyResult.failure(error), - ); - }); - } - Future> createUserWorkspace( String name, + AuthTypePB authType, ) { - final request = CreateWorkspacePB.create()..name = name; + final request = CreateWorkspacePB.create() + ..name = name + ..authType = authType; return UserEventCreateWorkspace(request).send(); } @@ -241,13 +244,6 @@ class UserBackendService implements IUserBackendService { return UserEventGetWorkspaceSubscriptionInfo(params).send(); } - Future> - getWorkspaceMember() async { - final data = WorkspaceMemberIdPB.create()..uid = userId; - - return UserEventGetMemberInfo(data).send(); - } - @override Future> createSubscription( String workspaceId, diff --git a/frontend/appflowy_flutter/lib/user/application/workspace_error_bloc.dart b/frontend/appflowy_flutter/lib/user/application/workspace_error_bloc.dart index ce51fdd10b..7ff50dbd02 100644 --- a/frontend/appflowy_flutter/lib/user/application/workspace_error_bloc.dart +++ b/frontend/appflowy_flutter/lib/user/application/workspace_error_bloc.dart @@ -1,9 +1,7 @@ import 'package:appflowy/plugins/database/application/defines.dart'; -import 'package:appflowy_backend/dispatch/dispatch.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart'; -import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart'; import 'package:appflowy_result/appflowy_result.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -22,20 +20,10 @@ class WorkspaceErrorBloc void _dispatch() { on( (event, emit) async { - await event.when( + event.when( init: () { // _loadSnapshots(); }, - resetWorkspace: () async { - emit(state.copyWith(loadingState: const LoadingState.loading())); - final payload = ResetWorkspacePB.create() - ..workspaceId = userFolder.workspaceId - ..uid = userFolder.uid; - final result = await UserEventResetWorkspace(payload).send(); - if (!isClosed) { - add(WorkspaceErrorEvent.didResetWorkspace(result)); - } - }, didResetWorkspace: (result) { result.fold( (_) { @@ -68,7 +56,6 @@ class WorkspaceErrorBloc class WorkspaceErrorEvent with _$WorkspaceErrorEvent { const factory WorkspaceErrorEvent.init() = _Init; const factory WorkspaceErrorEvent.logout() = _DidLogout; - const factory WorkspaceErrorEvent.resetWorkspace() = _ResetWorkspace; const factory WorkspaceErrorEvent.didResetWorkspace( FlowyResult result, ) = _DidResetWorkspace; diff --git a/frontend/appflowy_flutter/lib/user/presentation/anon_user.dart b/frontend/appflowy_flutter/lib/user/presentation/anon_user.dart index a9b11cb42e..ddb1a07f96 100644 --- a/frontend/appflowy_flutter/lib/user/presentation/anon_user.dart +++ b/frontend/appflowy_flutter/lib/user/presentation/anon_user.dart @@ -74,9 +74,8 @@ class AnonUserItem extends StatelessWidget { @override Widget build(BuildContext context) { final icon = isSelected ? const FlowySvg(FlowySvgs.check_s) : null; - final isDisabled = - isSelected || user.authenticator != AuthenticatorPB.Local; - final desc = "${user.name}\t ${user.authenticator}\t"; + final isDisabled = isSelected || user.workspaceAuthType != AuthTypePB.Local; + final desc = "${user.name}\t ${user.workspaceAuthType}\t"; final child = SizedBox( height: 30, child: FlowyButton( diff --git a/frontend/appflowy_flutter/lib/user/presentation/helpers/handle_open_workspace_error.dart b/frontend/appflowy_flutter/lib/user/presentation/helpers/handle_open_workspace_error.dart index 0aeb92fc18..ccad6c0a26 100644 --- a/frontend/appflowy_flutter/lib/user/presentation/helpers/handle_open_workspace_error.dart +++ b/frontend/appflowy_flutter/lib/user/presentation/helpers/handle_open_workspace_error.dart @@ -15,16 +15,14 @@ void handleOpenWorkspaceError(BuildContext context, FlowyError error) { getIt().pushWorkspaceErrorScreen(context, userFolder, error); break; case ErrorCode.InvalidEncryptSecret: - case ErrorCode.HttpError: + case ErrorCode.NetworkError: showToastNotification( - context, message: error.msg, type: ToastificationType.error, ); break; default: showToastNotification( - context, message: error.msg, type: ToastificationType.error, callbacks: ToastificationCallbacks( diff --git a/frontend/appflowy_flutter/lib/user/presentation/helpers/handle_user_profile_result.dart b/frontend/appflowy_flutter/lib/user/presentation/helpers/handle_user_profile_result.dart deleted file mode 100644 index 9abd417df3..0000000000 --- a/frontend/appflowy_flutter/lib/user/presentation/helpers/handle_user_profile_result.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:appflowy/user/presentation/helpers/helpers.dart'; -import 'package:appflowy/user/presentation/presentation.dart'; -import 'package:appflowy_backend/protobuf/flowy-error/protobuf.dart'; -import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart'; -import 'package:appflowy_result/appflowy_result.dart'; -import 'package:flutter/material.dart'; - -void handleUserProfileResult( - FlowyResult userProfileResult, - BuildContext context, - AuthRouter authRouter, -) { - userProfileResult.fold( - (userProfile) { - if (userProfile.encryptionType == EncryptionTypePB.Symmetric) { - authRouter.pushEncryptionScreen(context, userProfile); - } else { - authRouter.goHomeScreen(context, userProfile); - } - }, - (error) { - handleOpenWorkspaceError(context, error); - }, - ); -} diff --git a/frontend/appflowy_flutter/lib/user/presentation/helpers/helpers.dart b/frontend/appflowy_flutter/lib/user/presentation/helpers/helpers.dart index 084a360666..11f321232e 100644 --- a/frontend/appflowy_flutter/lib/user/presentation/helpers/helpers.dart +++ b/frontend/appflowy_flutter/lib/user/presentation/helpers/helpers.dart @@ -1,2 +1 @@ export 'handle_open_workspace_error.dart'; -export 'handle_user_profile_result.dart'; diff --git a/frontend/appflowy_flutter/lib/user/presentation/router.dart b/frontend/appflowy_flutter/lib/user/presentation/router.dart index 370d9c2062..339c2f29f7 100644 --- a/frontend/appflowy_flutter/lib/user/presentation/router.dart +++ b/frontend/appflowy_flutter/lib/user/presentation/router.dart @@ -21,10 +21,6 @@ class AuthRouter { getIt().pushWorkspaceStartScreen(context, userProfile); } - void pushSignUpScreen(BuildContext context) { - context.push(SignUpScreen.routeName); - } - /// Navigates to the home screen based on the current workspace and platform. /// /// This function takes in a [BuildContext] and a [UserProfilePB] object to @@ -61,20 +57,6 @@ class AuthRouter { ); } - void pushEncryptionScreen( - BuildContext context, - UserProfilePB userProfile, - ) { - // After log in,push EncryptionScreen on the top SignInScreen - context.push( - EncryptSecretScreen.routeName, - extra: { - EncryptSecretScreen.argUser: userProfile, - EncryptSecretScreen.argKey: ValueKey(userProfile.id), - }, - ); - } - Future pushWorkspaceErrorScreen( BuildContext context, UserFolderPB userFolder, diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/encrypt_secret_screen.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/encrypt_secret_screen.dart deleted file mode 100644 index f0b79ed9d2..0000000000 --- a/frontend/appflowy_flutter/lib/user/presentation/screens/encrypt_secret_screen.dart +++ /dev/null @@ -1,130 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/startup/startup.dart'; -import 'package:appflowy/user/presentation/helpers/helpers.dart'; -import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; -import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/widget/buttons/secondary_button.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -import '../../application/encrypt_secret_bloc.dart'; - -class EncryptSecretScreen extends StatefulWidget { - const EncryptSecretScreen({required this.user, super.key}); - - final UserProfilePB user; - - static const routeName = '/EncryptSecretScreen'; - - // arguments used in GoRouter - static const argUser = 'user'; - static const argKey = 'key'; - - @override - State createState() => _EncryptSecretScreenState(); -} - -class _EncryptSecretScreenState extends State { - final TextEditingController _textEditingController = TextEditingController(); - - @override - void dispose() { - _textEditingController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: BlocProvider( - create: (context) => EncryptSecretBloc(user: widget.user), - child: MultiBlocListener( - listeners: [ - BlocListener( - listenWhen: (previous, current) => - previous.isSignOut != current.isSignOut, - listener: (context, state) async { - if (state.isSignOut) { - await runAppFlowy(); - } - }, - ), - BlocListener( - listenWhen: (previous, current) => - previous.successOrFail != current.successOrFail, - listener: (context, state) async { - await state.successOrFail?.fold( - (unit) async { - await runAppFlowy(); - }, - (error) { - handleOpenWorkspaceError(context, error); - }, - ); - }, - ), - ], - child: BlocBuilder( - builder: (context, state) { - final indicator = state.loadingState?.when( - loading: () => const Center( - child: CircularProgressIndicator.adaptive(), - ), - finish: (result) => const SizedBox.shrink(), - idle: () => const SizedBox.shrink(), - ) ?? - const SizedBox.shrink(); - return Center( - child: SizedBox( - width: 300, - height: 160, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Opacity( - opacity: 0.6, - child: FlowyText.medium( - "${LocaleKeys.settings_menu_inputEncryptPrompt.tr()} ${widget.user.email}", - fontSize: 14, - maxLines: 10, - ), - ), - const VSpace(6), - SizedBox( - width: 300, - child: FlowyTextField( - controller: _textEditingController, - hintText: - LocaleKeys.settings_menu_inputTextFieldHint.tr(), - onChanged: (_) {}, - ), - ), - OkCancelButton( - alignment: MainAxisAlignment.end, - onOkPressed: () => - context.read().add( - EncryptSecretEvent.setEncryptSecret( - _textEditingController.text, - ), - ), - onCancelPressed: () => context - .read() - .add(const EncryptSecretEvent.cancelInputSecret()), - mode: TextButtonMode.normal, - ), - const VSpace(6), - indicator, - ], - ), - ), - ); - }, - ), - ), - ), - ); - } -} diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/screens.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/screens.dart index 088da38978..2aeba87995 100644 --- a/frontend/appflowy_flutter/lib/user/presentation/screens/screens.dart +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/screens.dart @@ -1,7 +1,5 @@ export 'sign_in_screen/sign_in_screen.dart'; export 'skip_log_in_screen.dart'; export 'splash_screen.dart'; -export 'sign_up_screen.dart'; -export 'encrypt_secret_screen.dart'; export 'workspace_error_screen.dart'; export 'workspace_start_screen/workspace_start_screen.dart'; diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/desktop_sign_in_screen.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/desktop_sign_in_screen.dart index 94b4347869..40901e92e1 100644 --- a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/desktop_sign_in_screen.dart +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/desktop_sign_in_screen.dart @@ -1,11 +1,14 @@ import 'package:appflowy/core/frameless_window.dart'; import 'package:appflowy/env/cloud_env.dart'; +import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/shared/settings/show_settings.dart'; import 'package:appflowy/shared/window_title_bar.dart'; import 'package:appflowy/user/application/sign_in_bloc.dart'; +import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/anonymous_sign_in_button.dart'; import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart'; import 'package:appflowy/user/presentation/widgets/widgets.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; @@ -19,9 +22,11 @@ class DesktopSignInScreen extends StatelessWidget { @override Widget build(BuildContext context) { - const indicatorMinHeight = 4.0; + final theme = AppFlowyTheme.of(context); + return BlocBuilder( builder: (context, state) { + final bottomPadding = UniversalPlatform.isDesktop ? 20.0 : 24.0; return Scaffold( appBar: _buildAppBar(), body: Center( @@ -29,39 +34,31 @@ class DesktopSignInScreen extends StatelessWidget { children: [ const Spacer(), - const VSpace(20), - // logo and title FlowyLogoTitle( title: LocaleKeys.welcomeText.tr(), - logoSize: const Size(60, 60), + logoSize: Size.square(36), ), - const VSpace(20), + VSpace(theme.spacing.xxl), - // magic link sign in - const SignInWithMagicLinkButtons(), - const VSpace(20), + // continue with email and password + isLocalAuthEnabled + ? const SignInAnonymousButtonV3() + : const ContinueWithEmailAndPassword(), + + VSpace(theme.spacing.xxl), // third-party sign in. if (isAuthEnabled) ...[ const _OrDivider(), - const VSpace(20), + VSpace(theme.spacing.xxl), const ThirdPartySignInButtons(), - const VSpace(20), + VSpace(theme.spacing.xxl), ], // sign in agreement const SignInAgreement(), - // loading status - const VSpace(indicatorMinHeight), - state.isSubmitting - ? const LinearProgressIndicator( - minHeight: indicatorMinHeight, - ) - : const VSpace(indicatorMinHeight), - const VSpace(20), - const Spacer(), // anonymous sign in and settings @@ -69,11 +66,11 @@ class DesktopSignInScreen extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ DesktopSignInSettingsButton(), - HSpace(42), + HSpace(20), SignInAnonymousButtonV2(), ], ), - const VSpace(16), + VSpace(bottomPadding), ], ), ), @@ -99,18 +96,24 @@ class DesktopSignInSettingsButton extends StatelessWidget { @override Widget build(BuildContext context) { - return FlowyButton( - useIntrinsicWidth: true, - text: FlowyText( - LocaleKeys.signIn_settings.tr(), - textAlign: TextAlign.center, - fontSize: 12.0, - // fontWeight: FontWeight.w500, - color: Colors.grey, - decoration: TextDecoration.underline, + final theme = AppFlowyTheme.of(context); + return AFGhostIconTextButton( + text: LocaleKeys.signIn_settings.tr(), + textColor: (context, isHovering, disabled) { + return theme.textColorScheme.secondary; + }, + size: AFButtonSize.s, + padding: EdgeInsets.symmetric( + horizontal: theme.spacing.m, + vertical: theme.spacing.xs, ), - onTap: () { - showSimpleSettingsDialog(context); + onTap: () => showSimpleSettingsDialog(context), + iconBuilder: (context, isHovering, disabled) { + return FlowySvg( + FlowySvgs.settings_s, + size: Size.square(20), + color: theme.textColorScheme.secondary, + ); }, ); } @@ -121,14 +124,30 @@ class _OrDivider extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); return Row( children: [ - const Flexible(child: Divider(thickness: 1)), + Flexible( + child: Divider( + thickness: 1, + color: theme.borderColorScheme.greyTertiary, + ), + ), Padding( padding: const EdgeInsets.symmetric(horizontal: 10), - child: FlowyText.regular(LocaleKeys.signIn_or.tr()), + child: Text( + LocaleKeys.signIn_or.tr(), + style: theme.textStyle.body.standard( + color: theme.textColorScheme.secondary, + ), + ), + ), + Flexible( + child: Divider( + thickness: 1, + color: theme.borderColorScheme.greyTertiary, + ), ), - const Flexible(child: Divider(thickness: 1)), ], ); } diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/mobile_sign_in_screen.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/mobile_sign_in_screen.dart index 863aadc49c..9eb7d5a965 100644 --- a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/mobile_sign_in_screen.dart +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/mobile_sign_in_screen.dart @@ -5,7 +5,10 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/setting/launch_settings_page.dart'; import 'package:appflowy/user/application/sign_in_bloc.dart'; +import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/anonymous_sign_in_button.dart'; import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart'; +import 'package:appflowy/user/presentation/widgets/flowy_logo_title.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; @@ -19,32 +22,29 @@ class MobileSignInScreen extends StatelessWidget { @override Widget build(BuildContext context) { - const double spacing = 16; - final colorScheme = Theme.of(context).colorScheme; return BlocBuilder( builder: (context, state) { + final theme = AppFlowyTheme.of(context); return Scaffold( resizeToAvoidBottomInset: false, body: Padding( padding: const EdgeInsets.symmetric(vertical: 38, horizontal: 40), child: Column( children: [ - const Spacer(flex: 4), - _buildLogo(), - const VSpace(spacing), - _buildAppNameText(colorScheme), - const VSpace(spacing * 2), - const SignInWithMagicLinkButtons(), - const VSpace(spacing), - if (isAuthEnabled) _buildThirdPartySignInButtons(colorScheme), - const VSpace(spacing * 1.5), - const SignInAgreement(), - const VSpace(spacing), - if (!isAuthEnabled) const Spacer(flex: 2), - const Spacer(flex: 2), const Spacer(), - Expanded(child: _buildSettingsButton(context)), - if (Platform.isAndroid) const Spacer(), + FlowyLogoTitle(title: LocaleKeys.welcomeText.tr()), + VSpace(theme.spacing.xxl), + isLocalAuthEnabled + ? const SignInAnonymousButtonV3() + : const ContinueWithEmailAndPassword(), + VSpace(theme.spacing.xxl), + if (isAuthEnabled) ...[ + _buildThirdPartySignInButtons(context), + VSpace(theme.spacing.xxl), + ], + const SignInAgreement(), + const Spacer(), + _buildSettingsButton(context), ], ), ), @@ -53,25 +53,8 @@ class MobileSignInScreen extends StatelessWidget { ); } - Widget _buildLogo() { - return const FlowySvg( - FlowySvgs.flowy_logo_xl, - size: Size.square(56), - blendMode: null, - ); - } - - Widget _buildAppNameText(ColorScheme colorScheme) { - return FlowyText( - LocaleKeys.appName.tr(), - textAlign: TextAlign.center, - fontSize: 28, - color: const Color(0xFF00BCF0), - fontWeight: FontWeight.w700, - ); - } - - Widget _buildThirdPartySignInButtons(ColorScheme colorScheme) { + Widget _buildThirdPartySignInButtons(BuildContext context) { + final theme = AppFlowyTheme.of(context); return Column( children: [ Row( @@ -80,10 +63,12 @@ class MobileSignInScreen extends StatelessWidget { const Expanded(child: Divider()), Padding( padding: const EdgeInsets.symmetric(horizontal: 8), - child: FlowyText( + child: Text( LocaleKeys.signIn_or.tr(), - fontSize: 12, - color: colorScheme.onSecondary, + style: TextStyle( + fontSize: 16, + color: theme.textColorScheme.secondary, + ), ), ), const Expanded(child: Divider()), @@ -100,25 +85,34 @@ class MobileSignInScreen extends StatelessWidget { } Widget _buildSettingsButton(BuildContext context) { + final theme = AppFlowyTheme.of(context); + return Row( mainAxisSize: MainAxisSize.min, children: [ - FlowyButton( - useIntrinsicWidth: true, - text: FlowyText( - LocaleKeys.signIn_settings.tr(), - textAlign: TextAlign.center, - fontSize: 12.0, - // fontWeight: FontWeight.w500, - color: Colors.grey, - decoration: TextDecoration.underline, + AFGhostIconTextButton( + text: LocaleKeys.signIn_settings.tr(), + textColor: (context, isHovering, disabled) { + return theme.textColorScheme.secondary; + }, + size: AFButtonSize.s, + padding: EdgeInsets.symmetric( + horizontal: theme.spacing.m, + vertical: theme.spacing.xs, ), - onTap: () { - context.push(MobileLaunchSettingsPage.routeName); + onTap: () => context.push(MobileLaunchSettingsPage.routeName), + iconBuilder: (context, isHovering, disabled) { + return FlowySvg( + FlowySvgs.settings_s, + size: Size.square(20), + color: theme.textColorScheme.secondary, + ); }, ), const HSpace(24), - const SignInAnonymousButtonV2(), + isLocalAuthEnabled + ? const ChangeCloudModeButton() + : const SignInAnonymousButtonV2(), ], ); } diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/sign_in_screen.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/sign_in_screen.dart index 5b99ad83f3..b359b2e217 100644 --- a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/sign_in_screen.dart +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/sign_in_screen.dart @@ -2,14 +2,12 @@ import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/user/application/sign_in_bloc.dart'; import 'package:appflowy/user/presentation/router.dart'; import 'package:appflowy/user/presentation/screens/sign_in_screen/desktop_sign_in_screen.dart'; -import 'package:appflowy/user/presentation/screens/sign_in_screen/mobile_loading_screen.dart'; import 'package:appflowy/user/presentation/screens/sign_in_screen/mobile_sign_in_screen.dart'; +import 'package:appflowy_backend/log.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:universal_platform/universal_platform.dart'; -import '../../helpers/helpers.dart'; - class SignInScreen extends StatelessWidget { const SignInScreen({super.key}); @@ -22,13 +20,9 @@ class SignInScreen extends StatelessWidget { child: BlocConsumer( listener: _showSignInError, builder: (context, state) { - final isLoading = context.read().state.isSubmitting; - if (UniversalPlatform.isMobile) { - return isLoading - ? const MobileLoadingScreen() - : const MobileSignInScreen(); - } - return const DesktopSignInScreen(); + return UniversalPlatform.isDesktop + ? const DesktopSignInScreen() + : const MobileSignInScreen(); }, ), ); @@ -37,10 +31,13 @@ class SignInScreen extends StatelessWidget { void _showSignInError(BuildContext context, SignInState state) { final successOrFail = state.successOrFail; if (successOrFail != null) { - handleUserProfileResult( - successOrFail, - context, - getIt(), + successOrFail.fold( + (userProfile) { + getIt().goHomeScreen(context, userProfile); + }, + (error) { + Log.error('Sign in error: $error'); + }, ); } } diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/anonymous_sign_in_button.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/anonymous_sign_in_button.dart new file mode 100644 index 0000000000..a7a1b9722d --- /dev/null +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/anonymous_sign_in_button.dart @@ -0,0 +1,57 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/user/application/anon_user_bloc.dart'; +import 'package:appflowy/user/application/sign_in_bloc.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class SignInAnonymousButtonV3 extends StatelessWidget { + const SignInAnonymousButtonV3({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, signInState) { + return BlocProvider( + create: (context) => AnonUserBloc() + ..add( + const AnonUserEvent.initial(), + ), + child: BlocListener( + listener: (context, state) async { + if (state.openedAnonUser != null) { + await runAppFlowy(); + } + }, + child: BlocBuilder( + builder: (context, state) { + final text = LocaleKeys.signIn_continueWithLocalModel.tr(); + final onTap = state.anonUsers.isEmpty + ? () { + context + .read() + .add(const SignInEvent.signInAsGuest()); + } + : () { + final bloc = context.read(); + final user = bloc.state.anonUsers.first; + bloc.add(AnonUserEvent.openAnonUser(user)); + }; + return AFFilledTextButton.primary( + text: text, + size: AFButtonSize.l, + alignment: Alignment.center, + onTap: onTap, + ); + }, + ), + ), + ); + }, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/anonymous_sign_in_button/anonymous_sign_in_button.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/anonymous_sign_in_button/anonymous_sign_in_button.dart new file mode 100644 index 0000000000..351527137f --- /dev/null +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/anonymous_sign_in_button/anonymous_sign_in_button.dart @@ -0,0 +1,16 @@ +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:flutter/material.dart'; + +class AnonymousSignInButton extends StatelessWidget { + const AnonymousSignInButton({super.key}); + + @override + Widget build(BuildContext context) { + return AFGhostButton.normal( + onTap: () {}, + builder: (context, isHovering, disabled) { + return const Placeholder(); + }, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/continue_with/continue_with_email.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/continue_with/continue_with_email.dart new file mode 100644 index 0000000000..c4cf504ef5 --- /dev/null +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/continue_with/continue_with_email.dart @@ -0,0 +1,23 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:easy_localization/easy_localization.dart'; + +class ContinueWithEmail extends StatelessWidget { + const ContinueWithEmail({ + super.key, + required this.onTap, + }); + + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return AFFilledTextButton.primary( + text: LocaleKeys.signIn_continueWithEmail.tr(), + size: AFButtonSize.l, + alignment: Alignment.center, + onTap: onTap, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/continue_with/continue_with_email_and_password.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/continue_with/continue_with_email_and_password.dart new file mode 100644 index 0000000000..5027874418 --- /dev/null +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/continue_with/continue_with_email_and_password.dart @@ -0,0 +1,187 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/user/application/sign_in_bloc.dart'; +import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/continue_with/continue_with_email.dart'; +import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/continue_with/continue_with_magic_link_or_passcode_page.dart'; +import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/continue_with/continue_with_password.dart'; +import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/continue_with/continue_with_password_page.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:string_validator/string_validator.dart'; + +class ContinueWithEmailAndPassword extends StatefulWidget { + const ContinueWithEmailAndPassword({super.key}); + + @override + State createState() => + _ContinueWithEmailAndPasswordState(); +} + +class _ContinueWithEmailAndPasswordState + extends State { + final controller = TextEditingController(); + final focusNode = FocusNode(); + final emailKey = GlobalKey(); + + bool _hasPushedContinueWithMagicLinkOrPasscodePage = false; + + @override + void dispose() { + controller.dispose(); + focusNode.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); + + return BlocListener( + listener: (context, state) { + final successOrFail = state.successOrFail; + // only push the continue with magic link or passcode page if the magic link is sent successfully + if (successOrFail != null) { + successOrFail.fold( + (_) => emailKey.currentState?.clearError(), + (error) => emailKey.currentState?.syncError( + errorText: error.msg, + ), + ); + } else if (successOrFail == null && !state.isSubmitting) { + emailKey.currentState?.clearError(); + } + }, + child: Column( + children: [ + AFTextField( + key: emailKey, + controller: controller, + hintText: LocaleKeys.signIn_pleaseInputYourEmail.tr(), + onSubmitted: (value) => _signInWithEmail( + context, + value, + ), + ), + VSpace(theme.spacing.l), + ContinueWithEmail( + onTap: () => _signInWithEmail( + context, + controller.text, + ), + ), + VSpace(theme.spacing.l), + ContinueWithPassword( + onTap: () { + final email = controller.text; + + if (!isEmail(email)) { + emailKey.currentState?.syncError( + errorText: LocaleKeys.signIn_invalidEmail.tr(), + ); + return; + } + + _pushContinueWithPasswordPage( + context, + email, + ); + }, + ), + ], + ), + ); + } + + void _signInWithEmail(BuildContext context, String email) { + if (!isEmail(email)) { + emailKey.currentState?.syncError( + errorText: LocaleKeys.signIn_invalidEmail.tr(), + ); + return; + } + + context + .read() + .add(SignInEvent.signInWithMagicLink(email: email)); + + _pushContinueWithMagicLinkOrPasscodePage( + context, + email, + ); + } + + void _pushContinueWithMagicLinkOrPasscodePage( + BuildContext context, + String email, + ) { + if (_hasPushedContinueWithMagicLinkOrPasscodePage) { + return; + } + + final signInBloc = context.read(); + + // push the a continue with magic link or passcode screen + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => BlocProvider.value( + value: signInBloc, + child: ContinueWithMagicLinkOrPasscodePage( + email: email, + backToLogin: () { + Navigator.pop(context); + + emailKey.currentState?.clearError(); + + _hasPushedContinueWithMagicLinkOrPasscodePage = false; + }, + onEnterPasscode: (passcode) { + signInBloc.add( + SignInEvent.signInWithPasscode( + email: email, + passcode: passcode, + ), + ); + }, + ), + ), + ), + ); + + _hasPushedContinueWithMagicLinkOrPasscodePage = true; + } + + void _pushContinueWithPasswordPage( + BuildContext context, + String email, + ) { + final signInBloc = context.read(); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => BlocProvider.value( + value: signInBloc, + child: ContinueWithPasswordPage( + email: email, + backToLogin: () { + emailKey.currentState?.clearError(); + Navigator.pop(context); + }, + onEnterPassword: (password) => signInBloc.add( + SignInEvent.signInWithEmailAndPassword( + email: email, + password: password, + ), + ), + onForgotPassword: () { + // todo: implement forgot password + }, + ), + ), + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/continue_with/continue_with_magic_link_or_passcode_page.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/continue_with/continue_with_magic_link_or_passcode_page.dart new file mode 100644 index 0000000000..c29a18ea30 --- /dev/null +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/continue_with/continue_with_magic_link_or_passcode_page.dart @@ -0,0 +1,270 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/user/application/sign_in_bloc.dart'; +import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/logo/logo.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/widget/spacing.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class ContinueWithMagicLinkOrPasscodePage extends StatefulWidget { + const ContinueWithMagicLinkOrPasscodePage({ + super.key, + required this.backToLogin, + required this.email, + required this.onEnterPasscode, + }); + + final String email; + final VoidCallback backToLogin; + final ValueChanged onEnterPasscode; + + @override + State createState() => + _ContinueWithMagicLinkOrPasscodePageState(); +} + +class _ContinueWithMagicLinkOrPasscodePageState + extends State { + final passcodeController = TextEditingController(); + + bool isEnteringPasscode = false; + + ToastificationItem? toastificationItem; + + final inputPasscodeKey = GlobalKey(); + + bool isSubmitting = false; + + @override + void dispose() { + passcodeController.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return BlocListener( + listener: (context, state) { + final successOrFail = state.successOrFail; + if (successOrFail != null && successOrFail.isFailure) { + successOrFail.onFailure((error) { + inputPasscodeKey.currentState?.syncError( + errorText: LocaleKeys.signIn_tokenHasExpiredOrInvalid.tr(), + ); + }); + } + + if (state.isSubmitting != isSubmitting) { + setState(() => isSubmitting = state.isSubmitting); + } + }, + child: Scaffold( + body: Center( + child: SizedBox( + width: 320, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Logo, title and description + ..._buildLogoTitleAndDescription(), + + // Enter code manually + ..._buildEnterCodeManually(), + + // Back to login + ..._buildBackToLogin(), + ], + ), + ), + ), + ), + ); + } + + List _buildEnterCodeManually() { + // todo: ask designer to provide the spacing + final spacing = VSpace(20); + final textStyle = AFButtonSize.l.buildTextStyle(context); + final textHeight = textStyle.height; + final textFontSize = textStyle.fontSize; + + // the indicator height is the height of the text style. + double indicatorHeight = 20; + if (textHeight != null && textFontSize != null) { + indicatorHeight = textHeight * textFontSize; + } + + if (!isEnteringPasscode) { + return [ + AFFilledTextButton.primary( + text: LocaleKeys.signIn_enterCodeManually.tr(), + onTap: () => setState(() => isEnteringPasscode = true), + size: AFButtonSize.l, + alignment: Alignment.center, + ), + spacing, + ]; + } + + return [ + // Enter code manually + AFTextField( + key: inputPasscodeKey, + controller: passcodeController, + hintText: LocaleKeys.signIn_enterCode.tr(), + keyboardType: TextInputType.number, + autoFocus: true, + onSubmitted: (passcode) { + if (passcode.isEmpty) { + inputPasscodeKey.currentState?.syncError( + errorText: LocaleKeys.signIn_invalidVerificationCode.tr(), + ); + } else { + widget.onEnterPasscode(passcode); + } + }, + ), + // todo: ask designer to provide the spacing + VSpace(12), + + // continue to login + !isSubmitting + ? _buildContinueButton(textStyle: textStyle) + : _buildIndicator(indicatorHeight: indicatorHeight), + + spacing, + ]; + } + + Widget _buildContinueButton({ + required TextStyle textStyle, + }) { + return AFFilledTextButton.primary( + text: LocaleKeys.signIn_continueToSignIn.tr(), + onTap: () { + final passcode = passcodeController.text; + if (passcode.isEmpty) { + inputPasscodeKey.currentState?.syncError( + errorText: LocaleKeys.signIn_invalidVerificationCode.tr(), + ); + } else { + widget.onEnterPasscode(passcode); + } + }, + textStyle: textStyle.copyWith( + color: AppFlowyTheme.of(context).textColorScheme.onFill, + ), + size: AFButtonSize.l, + alignment: Alignment.center, + ); + } + + Widget _buildIndicator({ + required double indicatorHeight, + }) { + return AFFilledButton.disabled( + size: AFButtonSize.l, + builder: (context, isHovering, disabled) { + return Align( + child: SizedBox.square( + dimension: indicatorHeight, + child: CircularProgressIndicator( + strokeWidth: 3.0, + ), + ), + ); + }, + ); + } + + List _buildBackToLogin() { + return [ + AFGhostTextButton( + text: LocaleKeys.signIn_backToLogin.tr(), + size: AFButtonSize.s, + onTap: widget.backToLogin, + padding: EdgeInsets.zero, + textColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + if (isHovering) { + return theme.fillColorScheme.themeThickHover; + } + return theme.textColorScheme.theme; + }, + ), + ]; + } + + List _buildLogoTitleAndDescription() { + final theme = AppFlowyTheme.of(context); + final spacing = VSpace(theme.spacing.xxl); + if (!isEnteringPasscode) { + return [ + // logo + const AFLogo(), + spacing, + + // title + Text( + LocaleKeys.signIn_checkYourEmail.tr(), + style: theme.textStyle.heading3.enhanced( + color: theme.textColorScheme.primary, + ), + ), + spacing, + + // description + Text( + LocaleKeys.signIn_temporaryVerificationLinkSent.tr(), + style: theme.textStyle.body.standard( + color: theme.textColorScheme.primary, + ), + textAlign: TextAlign.center, + ), + Text( + widget.email, + style: theme.textStyle.body.enhanced( + color: theme.textColorScheme.primary, + ), + textAlign: TextAlign.center, + ), + spacing, + ]; + } else { + return [ + // logo + const AFLogo(), + spacing, + + // title + Text( + LocaleKeys.signIn_enterCode.tr(), + style: theme.textStyle.heading3.enhanced( + color: theme.textColorScheme.primary, + ), + ), + spacing, + + // description + Text( + LocaleKeys.signIn_temporaryVerificationCodeSent.tr(), + style: theme.textStyle.body.standard( + color: theme.textColorScheme.primary, + ), + textAlign: TextAlign.center, + ), + Text( + widget.email, + style: theme.textStyle.body.enhanced( + color: theme.textColorScheme.primary, + ), + textAlign: TextAlign.center, + ), + spacing, + ]; + } + } +} diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/continue_with/continue_with_password.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/continue_with/continue_with_password.dart new file mode 100644 index 0000000000..5bfd191e22 --- /dev/null +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/continue_with/continue_with_password.dart @@ -0,0 +1,21 @@ +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:flutter/material.dart'; + +class ContinueWithPassword extends StatelessWidget { + const ContinueWithPassword({ + super.key, + required this.onTap, + }); + + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return AFOutlinedTextButton.normal( + text: 'Continue with password', + size: AFButtonSize.l, + alignment: Alignment.center, + onTap: onTap, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/continue_with/continue_with_password_page.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/continue_with/continue_with_password_page.dart new file mode 100644 index 0000000000..1e2ed6e100 --- /dev/null +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/continue_with/continue_with_password_page.dart @@ -0,0 +1,196 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/user/application/sign_in_bloc.dart'; +import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/logo/logo.dart'; +import 'package:appflowy/workspace/presentation/settings/pages/account/password/password_suffix_icon.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flowy_infra_ui/widget/spacing.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class ContinueWithPasswordPage extends StatefulWidget { + const ContinueWithPasswordPage({ + super.key, + required this.backToLogin, + required this.email, + required this.onEnterPassword, + required this.onForgotPassword, + }); + + final String email; + final VoidCallback backToLogin; + final ValueChanged onEnterPassword; + final VoidCallback onForgotPassword; + + @override + State createState() => + _ContinueWithPasswordPageState(); +} + +class _ContinueWithPasswordPageState extends State { + final passwordController = TextEditingController(); + final inputPasswordKey = GlobalKey(); + + @override + void dispose() { + passwordController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: SizedBox( + width: 320, + child: BlocListener( + listener: (context, state) { + final successOrFail = state.successOrFail; + if (successOrFail != null && successOrFail.isFailure) { + successOrFail.onFailure((error) { + inputPasswordKey.currentState?.syncError( + errorText: LocaleKeys.signIn_invalidLoginCredentials.tr(), + ); + }); + } else if (state.passwordError != null) { + inputPasswordKey.currentState?.syncError( + errorText: LocaleKeys.signIn_invalidLoginCredentials.tr(), + ); + } else { + inputPasswordKey.currentState?.clearError(); + } + }, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Logo and title + ..._buildLogoAndTitle(), + + // Password input and buttons + ..._buildPasswordSection(), + + // Back to login + ..._buildBackToLogin(), + ], + ), + ), + ), + ), + ); + } + + List _buildLogoAndTitle() { + final theme = AppFlowyTheme.of(context); + final spacing = VSpace(theme.spacing.xxl); + return [ + // logo + const AFLogo(), + spacing, + + // title + Text( + LocaleKeys.signIn_enterPassword.tr(), + style: theme.textStyle.heading3.enhanced( + color: theme.textColorScheme.primary, + ), + ), + spacing, + + // email display + RichText( + text: TextSpan( + children: [ + TextSpan( + text: LocaleKeys.signIn_loginAs.tr(), + style: theme.textStyle.body.standard( + color: theme.textColorScheme.primary, + ), + ), + TextSpan( + text: ' ${widget.email}', + style: theme.textStyle.body.enhanced( + color: theme.textColorScheme.primary, + ), + ), + ], + ), + ), + spacing, + ]; + } + + List _buildPasswordSection() { + final theme = AppFlowyTheme.of(context); + final iconSize = 20.0; + return [ + // Password input + AFTextField( + key: inputPasswordKey, + controller: passwordController, + hintText: LocaleKeys.signIn_enterPassword.tr(), + autoFocus: true, + obscureText: true, + suffixIconConstraints: BoxConstraints.tightFor( + width: iconSize + theme.spacing.m, + height: iconSize, + ), + suffixIconBuilder: (context, isObscured) => PasswordSuffixIcon( + isObscured: isObscured, + onTap: () { + inputPasswordKey.currentState?.syncObscured(!isObscured); + }, + ), + onSubmitted: widget.onEnterPassword, + ), + // todo: ask designer to provide the spacing + VSpace(8), + + // Forgot password button + Align( + alignment: Alignment.centerLeft, + child: AFGhostTextButton( + text: LocaleKeys.signIn_forgotPassword.tr(), + size: AFButtonSize.s, + padding: EdgeInsets.zero, + onTap: widget.onForgotPassword, + textColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + if (isHovering) { + return theme.fillColorScheme.themeThickHover; + } + return theme.textColorScheme.theme; + }, + ), + ), + VSpace(20), + + // Continue button + AFFilledTextButton.primary( + text: LocaleKeys.web_continue.tr(), + onTap: () => widget.onEnterPassword(passwordController.text), + size: AFButtonSize.l, + alignment: Alignment.center, + ), + VSpace(20), + ]; + } + + List _buildBackToLogin() { + return [ + AFGhostTextButton( + text: LocaleKeys.signIn_backToLogin.tr(), + size: AFButtonSize.s, + onTap: widget.backToLogin, + padding: EdgeInsets.zero, + textColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + if (isHovering) { + return theme.fillColorScheme.themeThickHover; + } + return theme.textColorScheme.theme; + }, + ), + ]; + } +} diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/logo/logo.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/logo/logo.dart new file mode 100644 index 0000000000..8e126db7ad --- /dev/null +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/logo/logo.dart @@ -0,0 +1,20 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:flutter/material.dart'; + +class AFLogo extends StatelessWidget { + const AFLogo({ + super.key, + this.size = const Size.square(36), + }); + + final Size size; + + @override + Widget build(BuildContext context) { + return FlowySvg( + FlowySvgs.app_logo_xl, + blendMode: null, + size: size, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/magic_link_sign_in_buttons.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/magic_link_sign_in_buttons.dart index 0486d67838..45e4fe7273 100644 --- a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/magic_link_sign_in_buttons.dart +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/magic_link_sign_in_buttons.dart @@ -64,14 +64,16 @@ class _SignInWithMagicLinkButtonsState void _sendMagicLink(BuildContext context, String email) { if (!isEmail(email)) { - return showToastNotification( - context, + showToastNotification( message: LocaleKeys.signIn_invalidEmail.tr(), type: ToastificationType.error, ); + return; } - context.read().add(SignInEvent.signedWithMagicLink(email)); + context + .read() + .add(SignInEvent.signInWithMagicLink(email: email)); showConfirmDialog( context: context, diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/sign_in_agreement.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/sign_in_agreement.dart index 7351871b6a..76ce87ffc1 100644 --- a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/sign_in_agreement.dart +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/sign_in_agreement.dart @@ -1,5 +1,6 @@ import 'package:appflowy/core/helpers/url_launcher.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; @@ -11,39 +12,38 @@ class SignInAgreement extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); + final textStyle = theme.textStyle.caption.standard( + color: theme.textColorScheme.secondary, + ); + final underlinedTextStyle = theme.textStyle.caption.underline( + color: theme.textColorScheme.secondary, + ); return RichText( textAlign: TextAlign.center, text: TextSpan( children: [ TextSpan( - text: '${LocaleKeys.web_signInAgreement.tr()} ', - style: const TextStyle(color: Colors.grey, fontSize: 12), + text: LocaleKeys.web_signInAgreement.tr(), + style: textStyle, ), TextSpan( text: '${LocaleKeys.web_termOfUse.tr()} ', - style: const TextStyle( - color: Colors.grey, - fontSize: 12, - decoration: TextDecoration.underline, - ), + style: underlinedTextStyle, mouseCursor: SystemMouseCursors.click, recognizer: TapGestureRecognizer() - ..onTap = () => afLaunchUrlString('https://appflowy.io/terms'), + ..onTap = () => afLaunchUrlString('https://appflowy.com/terms'), ), TextSpan( text: '${LocaleKeys.web_and.tr()} ', - style: const TextStyle(color: Colors.grey, fontSize: 12), + style: textStyle, ), TextSpan( text: LocaleKeys.web_privacyPolicy.tr(), - style: const TextStyle( - color: Colors.grey, - fontSize: 12, - decoration: TextDecoration.underline, - ), + style: underlinedTextStyle, mouseCursor: SystemMouseCursors.click, recognizer: TapGestureRecognizer() - ..onTap = () => afLaunchUrlString('https://appflowy.io/privacy'), + ..onTap = () => afLaunchUrlString('https://appflowy.com/privacy'), ), ], ), diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/sign_in_anonymous_button.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/sign_in_anonymous_button.dart index bce22a714d..33ef1d7bb0 100644 --- a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/sign_in_anonymous_button.dart +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/sign_in_anonymous_button.dart @@ -1,90 +1,13 @@ +import 'package:appflowy/env/cloud_env.dart'; +import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/user/application/anon_user_bloc.dart'; import 'package:appflowy/user/application/sign_in_bloc.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/size.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:universal_platform/universal_platform.dart'; - -/// Used in DesktopSignInScreen and MobileSignInScreen -class SignInAnonymousButton extends StatelessWidget { - const SignInAnonymousButton({ - super.key, - }); - - @override - Widget build(BuildContext context) { - final isMobile = UniversalPlatform.isMobile; - - return BlocBuilder( - builder: (context, signInState) { - return BlocProvider( - create: (context) => AnonUserBloc() - ..add( - const AnonUserEvent.initial(), - ), - child: BlocListener( - listener: (context, state) async { - if (state.openedAnonUser != null) { - await runAppFlowy(); - } - }, - child: BlocBuilder( - builder: (context, state) { - final text = state.anonUsers.isEmpty - ? LocaleKeys.signIn_loginStartWithAnonymous.tr() - : LocaleKeys.signIn_continueAnonymousUser.tr(); - final onTap = state.anonUsers.isEmpty - ? () { - context - .read() - .add(const SignInEvent.signedInAsGuest()); - } - : () { - final bloc = context.read(); - final user = bloc.state.anonUsers.first; - bloc.add(AnonUserEvent.openAnonUser(user)); - }; - // SignInAnonymousButton in mobile - if (isMobile) { - return ElevatedButton( - style: ElevatedButton.styleFrom( - minimumSize: const Size(double.infinity, 56), - ), - onPressed: onTap, - child: FlowyText( - LocaleKeys.signIn_loginStartWithAnonymous.tr(), - fontSize: 14, - color: Theme.of(context).colorScheme.onPrimary, - fontWeight: FontWeight.w500, - ), - ); - } - // SignInAnonymousButton in desktop - return SizedBox( - height: 48, - child: FlowyButton( - isSelected: true, - disable: signInState.isSubmitting, - text: FlowyText.medium( - text, - textAlign: TextAlign.center, - ), - radius: Corners.s6Border, - onTap: onTap, - ), - ); - }, - ), - ), - ); - }, - ); - } -} class SignInAnonymousButtonV2 extends StatelessWidget { const SignInAnonymousButtonV2({ @@ -108,27 +31,35 @@ class SignInAnonymousButtonV2 extends StatelessWidget { }, child: BlocBuilder( builder: (context, state) { - final text = LocaleKeys.signIn_anonymous.tr(); + final theme = AppFlowyTheme.of(context); final onTap = state.anonUsers.isEmpty ? () { context .read() - .add(const SignInEvent.signedInAsGuest()); + .add(const SignInEvent.signInAsGuest()); } : () { final bloc = context.read(); final user = bloc.state.anonUsers.first; bloc.add(AnonUserEvent.openAnonUser(user)); }; - return FlowyButton( - useIntrinsicWidth: true, - onTap: onTap, - text: FlowyText( - text, - color: Colors.grey, - decoration: TextDecoration.underline, - fontSize: 12, + return AFGhostIconTextButton( + text: LocaleKeys.signIn_anonymousMode.tr(), + textColor: (context, isHovering, disabled) { + return theme.textColorScheme.secondary; + }, + padding: EdgeInsets.symmetric( + horizontal: theme.spacing.m, + vertical: theme.spacing.xs, ), + size: AFButtonSize.s, + onTap: onTap, + iconBuilder: (context, isHovering, disabled) { + return FlowySvg( + FlowySvgs.anonymous_mode_m, + color: theme.textColorScheme.secondary, + ); + }, ); }, ), @@ -138,3 +69,39 @@ class SignInAnonymousButtonV2 extends StatelessWidget { ); } } + +class ChangeCloudModeButton extends StatelessWidget { + const ChangeCloudModeButton({ + super.key, + }); + + @override + Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); + return AFGhostIconTextButton( + text: LocaleKeys.signIn_switchToAppFlowyCloud.tr(), + textColor: (context, isHovering, disabled) { + return theme.textColorScheme.secondary; + }, + size: AFButtonSize.s, + padding: EdgeInsets.symmetric( + horizontal: theme.spacing.m, + vertical: theme.spacing.xs, + ), + onTap: () async { + await useAppFlowyBetaCloudWithURL( + kAppflowyCloudUrl, + AuthenticatorType.appflowyCloud, + ); + await runAppFlowy(); + }, + iconBuilder: (context, isHovering, disabled) { + return FlowySvg( + FlowySvgs.cloud_mode_m, + size: Size.square(20), + color: theme.textColorScheme.secondary, + ); + }, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/sign_in_or_logout_button.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/sign_in_or_logout_button.dart index 5146e29962..7067844500 100644 --- a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/sign_in_or_logout_button.dart +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/sign_in_or_logout_button.dart @@ -1,5 +1,5 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; import 'package:flutter/material.dart'; class MobileLogoutButton extends StatelessWidget { @@ -18,50 +18,19 @@ class MobileLogoutButton extends StatelessWidget { @override Widget build(BuildContext context) { - final style = Theme.of(context); - return GestureDetector( + return AFOutlinedIconTextButton.normal( + text: text, onTap: onPressed, - child: Container( - height: 38, - decoration: BoxDecoration( - borderRadius: const BorderRadius.all( - Radius.circular(4), - ), - border: Border.all( - color: textColor ?? style.colorScheme.outline, - width: 0.5, - ), - ), - alignment: Alignment.center, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (icon != null) ...[ - SizedBox( - // The icon could be in different height as original aspect ratio, we use a fixed sizebox to wrap it to make sure they all occupy the same space. - width: 30, - height: 30, - child: Center( - child: SizedBox( - width: 24, - child: FlowySvg( - icon!, - blendMode: null, - ), - ), - ), - ), - const HSpace(8), - ], - FlowyText( - text, - fontSize: 14.0, - fontWeight: FontWeight.w400, - color: textColor, - ), - ], - ), - ), + size: AFButtonSize.l, + iconBuilder: (context, isHovering, disabled) { + if (icon == null) { + return const SizedBox.shrink(); + } + return FlowySvg( + icon!, + size: Size.square(18), + ); + }, ); } } diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/third_party_sign_in_button.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/third_party_sign_in_button.dart deleted file mode 100644 index 35d16b031f..0000000000 --- a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/third_party_sign_in_button.dart +++ /dev/null @@ -1,219 +0,0 @@ -import 'package:appflowy/generated/flowy_svgs.g.dart'; -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/mobile/presentation/base/animated_gesture.dart'; -import 'package:appflowy/user/presentation/widgets/widgets.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/size.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/material.dart'; - -enum ThirdPartySignInButtonType { - apple, - google, - github, - discord, - anonymous; - - String get provider { - switch (this) { - case ThirdPartySignInButtonType.apple: - return 'apple'; - case ThirdPartySignInButtonType.google: - return 'google'; - case ThirdPartySignInButtonType.github: - return 'github'; - case ThirdPartySignInButtonType.discord: - return 'discord'; - case ThirdPartySignInButtonType.anonymous: - throw UnsupportedError('Anonymous session does not have a provider'); - } - } - - FlowySvgData get icon { - switch (this) { - case ThirdPartySignInButtonType.apple: - return FlowySvgs.m_apple_icon_xl; - case ThirdPartySignInButtonType.google: - return FlowySvgs.m_google_icon_xl; - case ThirdPartySignInButtonType.github: - return FlowySvgs.m_github_icon_xl; - case ThirdPartySignInButtonType.discord: - return FlowySvgs.m_discord_icon_xl; - case ThirdPartySignInButtonType.anonymous: - return FlowySvgs.m_discord_icon_xl; - } - } - - String get labelText { - switch (this) { - case ThirdPartySignInButtonType.apple: - return LocaleKeys.signIn_signInWithApple.tr(); - case ThirdPartySignInButtonType.google: - return LocaleKeys.signIn_signInWithGoogle.tr(); - case ThirdPartySignInButtonType.github: - return LocaleKeys.signIn_signInWithGithub.tr(); - case ThirdPartySignInButtonType.discord: - return LocaleKeys.signIn_signInWithDiscord.tr(); - case ThirdPartySignInButtonType.anonymous: - return 'Anonymous session'; - } - } - - // https://developer.apple.com/design/human-interface-guidelines/sign-in-with-apple - Color backgroundColor(BuildContext context) { - final isDarkMode = Theme.of(context).brightness == Brightness.dark; - switch (this) { - case ThirdPartySignInButtonType.apple: - return isDarkMode ? Colors.white : Colors.black; - case ThirdPartySignInButtonType.google: - case ThirdPartySignInButtonType.github: - case ThirdPartySignInButtonType.discord: - case ThirdPartySignInButtonType.anonymous: - return isDarkMode ? Colors.black : Colors.grey.shade100; - } - } - - Color textColor(BuildContext context) { - final isDarkMode = Theme.of(context).brightness == Brightness.dark; - switch (this) { - case ThirdPartySignInButtonType.apple: - return isDarkMode ? Colors.black : Colors.white; - case ThirdPartySignInButtonType.google: - case ThirdPartySignInButtonType.github: - case ThirdPartySignInButtonType.discord: - case ThirdPartySignInButtonType.anonymous: - return isDarkMode ? Colors.white : Colors.black; - } - } - - BlendMode? get blendMode { - switch (this) { - case ThirdPartySignInButtonType.apple: - case ThirdPartySignInButtonType.github: - return BlendMode.srcIn; - default: - return null; - } - } -} - -class MobileThirdPartySignInButton extends StatelessWidget { - const MobileThirdPartySignInButton({ - super.key, - this.height = 38, - this.fontSize = 14.0, - required this.onPressed, - required this.type, - }); - - final VoidCallback onPressed; - final double height; - final double fontSize; - final ThirdPartySignInButtonType type; - - @override - Widget build(BuildContext context) { - final style = Theme.of(context); - - return AnimatedGestureDetector( - scaleFactor: 1.0, - onTapUp: onPressed, - child: Container( - height: height, - decoration: BoxDecoration( - color: type.backgroundColor(context), - borderRadius: const BorderRadius.all( - Radius.circular(4), - ), - border: Border.all( - color: style.colorScheme.outline, - width: 0.5, - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (type != ThirdPartySignInButtonType.anonymous) - FlowySvg( - type.icon, - size: Size.square(fontSize), - blendMode: type.blendMode, - color: type.textColor(context), - ), - const HSpace(8.0), - FlowyText( - type.labelText, - fontSize: fontSize, - color: type.textColor(context), - ), - ], - ), - ), - ); - } -} - -class DesktopSignInButton extends StatelessWidget { - const DesktopSignInButton({ - super.key, - required this.type, - required this.onPressed, - }); - - final ThirdPartySignInButtonType type; - final VoidCallback onPressed; - - @override - Widget build(BuildContext context) { - final style = Theme.of(context); - // In desktop, the width of button is limited by [AuthFormContainer] - return SizedBox( - height: 48, - width: AuthFormContainer.width, - child: OutlinedButton.icon( - // In order to align all the labels vertically in a relatively centered position to the button, we use a fixed width container to wrap the icon(align to the right), then use another container to align the label to left. - icon: Container( - width: AuthFormContainer.width / 4, - alignment: Alignment.centerRight, - child: SizedBox( - // Some icons are not square, so we just use a fixed width here. - width: 24, - child: FlowySvg( - type.icon, - blendMode: type.blendMode, - ), - ), - ), - label: Container( - padding: const EdgeInsets.only(left: 8), - alignment: Alignment.centerLeft, - child: FlowyText( - type.labelText, - fontSize: 14, - ), - ), - style: ButtonStyle( - overlayColor: WidgetStateProperty.resolveWith( - (states) { - if (states.contains(WidgetState.hovered)) { - return style.colorScheme.onSecondaryContainer; - } - return null; - }, - ), - shape: WidgetStateProperty.all( - const RoundedRectangleBorder( - borderRadius: Corners.s6Border, - ), - ), - side: WidgetStateProperty.all( - BorderSide( - color: style.dividerColor, - ), - ), - ), - onPressed: onPressed, - ), - ); - } -} diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/third_party_sign_in_button/third_party_sign_in_button.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/third_party_sign_in_button/third_party_sign_in_button.dart new file mode 100644 index 0000000000..9a7234ab6b --- /dev/null +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/third_party_sign_in_button/third_party_sign_in_button.dart @@ -0,0 +1,153 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; + +enum ThirdPartySignInButtonType { + apple, + google, + github, + discord, + anonymous; + + String get provider { + switch (this) { + case ThirdPartySignInButtonType.apple: + return 'apple'; + case ThirdPartySignInButtonType.google: + return 'google'; + case ThirdPartySignInButtonType.github: + return 'github'; + case ThirdPartySignInButtonType.discord: + return 'discord'; + case ThirdPartySignInButtonType.anonymous: + throw UnsupportedError('Anonymous session does not have a provider'); + } + } + + FlowySvgData get icon { + switch (this) { + case ThirdPartySignInButtonType.apple: + return FlowySvgs.m_apple_icon_xl; + case ThirdPartySignInButtonType.google: + return FlowySvgs.m_google_icon_xl; + case ThirdPartySignInButtonType.github: + return FlowySvgs.m_github_icon_xl; + case ThirdPartySignInButtonType.discord: + return FlowySvgs.m_discord_icon_xl; + case ThirdPartySignInButtonType.anonymous: + return FlowySvgs.m_discord_icon_xl; + } + } + + String get labelText { + switch (this) { + case ThirdPartySignInButtonType.apple: + return LocaleKeys.signIn_signInWithApple.tr(); + case ThirdPartySignInButtonType.google: + return LocaleKeys.signIn_signInWithGoogle.tr(); + case ThirdPartySignInButtonType.github: + return LocaleKeys.signIn_signInWithGithub.tr(); + case ThirdPartySignInButtonType.discord: + return LocaleKeys.signIn_signInWithDiscord.tr(); + case ThirdPartySignInButtonType.anonymous: + return 'Anonymous session'; + } + } + + // https://developer.apple.com/design/human-interface-guidelines/sign-in-with-apple + Color backgroundColor(BuildContext context) { + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + switch (this) { + case ThirdPartySignInButtonType.apple: + return isDarkMode ? Colors.white : Colors.black; + case ThirdPartySignInButtonType.google: + case ThirdPartySignInButtonType.github: + case ThirdPartySignInButtonType.discord: + case ThirdPartySignInButtonType.anonymous: + return isDarkMode ? Colors.black : Colors.grey.shade100; + } + } + + Color textColor(BuildContext context) { + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + switch (this) { + case ThirdPartySignInButtonType.apple: + return isDarkMode ? Colors.black : Colors.white; + case ThirdPartySignInButtonType.google: + case ThirdPartySignInButtonType.github: + case ThirdPartySignInButtonType.discord: + case ThirdPartySignInButtonType.anonymous: + return isDarkMode ? Colors.white : Colors.black; + } + } + + BlendMode? get blendMode { + switch (this) { + case ThirdPartySignInButtonType.apple: + case ThirdPartySignInButtonType.github: + return BlendMode.srcIn; + default: + return null; + } + } +} + +class MobileThirdPartySignInButton extends StatelessWidget { + const MobileThirdPartySignInButton({ + super.key, + this.height = 38, + this.fontSize = 14.0, + required this.onTap, + required this.type, + }); + + final VoidCallback onTap; + final double height; + final double fontSize; + final ThirdPartySignInButtonType type; + + @override + Widget build(BuildContext context) { + return AFOutlinedIconTextButton.normal( + text: type.labelText, + onTap: onTap, + size: AFButtonSize.l, + iconBuilder: (context, isHovering, disabled) { + return FlowySvg( + type.icon, + size: Size.square(16), + blendMode: type.blendMode, + ); + }, + ); + } +} + +class DesktopThirdPartySignInButton extends StatelessWidget { + const DesktopThirdPartySignInButton({ + super.key, + required this.type, + required this.onTap, + }); + + final ThirdPartySignInButtonType type; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + return AFOutlinedIconTextButton.normal( + text: type.labelText, + onTap: onTap, + size: AFButtonSize.l, + iconBuilder: (context, isHovering, disabled) { + return FlowySvg( + type.icon, + size: Size.square(18), + blendMode: type.blendMode, + ); + }, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/third_party_sign_in_button/third_party_sign_in_buttons.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/third_party_sign_in_button/third_party_sign_in_buttons.dart new file mode 100644 index 0000000000..8d27846c46 --- /dev/null +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/third_party_sign_in_button/third_party_sign_in_buttons.dart @@ -0,0 +1,204 @@ +import 'dart:io'; + +import 'package:appflowy/user/application/sign_in_bloc.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:universal_platform/universal_platform.dart'; + +import 'third_party_sign_in_button.dart'; + +typedef _SignInCallback = void Function(ThirdPartySignInButtonType signInType); + +@visibleForTesting +const Key signInWithGoogleButtonKey = Key('signInWithGoogleButton'); + +class ThirdPartySignInButtons extends StatelessWidget { + /// Used in DesktopSignInScreen, MobileSignInScreen and SettingThirdPartyLogin + const ThirdPartySignInButtons({ + super.key, + this.expanded = false, + }); + + final bool expanded; + + @override + Widget build(BuildContext context) { + if (UniversalPlatform.isDesktopOrWeb) { + return _DesktopThirdPartySignIn( + onSignIn: (type) => _signIn(context, type.provider), + ); + } else { + return _MobileThirdPartySignIn( + isExpanded: expanded, + onSignIn: (type) => _signIn(context, type.provider), + ); + } + } + + void _signIn(BuildContext context, String provider) { + context.read().add( + SignInEvent.signInWithOAuth(platform: provider), + ); + } +} + +class _DesktopThirdPartySignIn extends StatefulWidget { + const _DesktopThirdPartySignIn({ + required this.onSignIn, + }); + + final _SignInCallback onSignIn; + + @override + State<_DesktopThirdPartySignIn> createState() => + _DesktopThirdPartySignInState(); +} + +class _DesktopThirdPartySignInState extends State<_DesktopThirdPartySignIn> { + bool isExpanded = false; + + @override + Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); + return Column( + children: [ + DesktopThirdPartySignInButton( + key: signInWithGoogleButtonKey, + type: ThirdPartySignInButtonType.google, + onTap: () => widget.onSignIn(ThirdPartySignInButtonType.google), + ), + VSpace(theme.spacing.l), + DesktopThirdPartySignInButton( + type: ThirdPartySignInButtonType.apple, + onTap: () => widget.onSignIn(ThirdPartySignInButtonType.apple), + ), + ...isExpanded ? _buildExpandedButtons() : _buildCollapsedButtons(), + ], + ); + } + + List _buildExpandedButtons() { + final theme = AppFlowyTheme.of(context); + return [ + VSpace(theme.spacing.l), + DesktopThirdPartySignInButton( + type: ThirdPartySignInButtonType.github, + onTap: () => widget.onSignIn(ThirdPartySignInButtonType.github), + ), + VSpace(theme.spacing.l), + DesktopThirdPartySignInButton( + type: ThirdPartySignInButtonType.discord, + onTap: () => widget.onSignIn(ThirdPartySignInButtonType.discord), + ), + ]; + } + + List _buildCollapsedButtons() { + final theme = AppFlowyTheme.of(context); + return [ + VSpace(theme.spacing.l), + AFGhostTextButton( + text: 'More options', + padding: EdgeInsets.zero, + textColor: (context, isHovering, disabled) { + if (isHovering) { + return theme.fillColorScheme.themeThickHover; + } + return theme.textColorScheme.theme; + }, + onTap: () { + setState(() { + isExpanded = !isExpanded; + }); + }, + ), + ]; + } +} + +class _MobileThirdPartySignIn extends StatefulWidget { + const _MobileThirdPartySignIn({ + required this.isExpanded, + required this.onSignIn, + }); + + final bool isExpanded; + final _SignInCallback onSignIn; + + @override + State<_MobileThirdPartySignIn> createState() => + _MobileThirdPartySignInState(); +} + +class _MobileThirdPartySignInState extends State<_MobileThirdPartySignIn> { + static const padding = 8.0; + + bool isExpanded = false; + + @override + void initState() { + super.initState(); + + isExpanded = widget.isExpanded; + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + // only display apple sign in button on iOS + if (Platform.isIOS) ...[ + MobileThirdPartySignInButton( + type: ThirdPartySignInButtonType.apple, + onTap: () => widget.onSignIn(ThirdPartySignInButtonType.apple), + ), + const VSpace(padding), + ], + MobileThirdPartySignInButton( + key: signInWithGoogleButtonKey, + type: ThirdPartySignInButtonType.google, + onTap: () => widget.onSignIn(ThirdPartySignInButtonType.google), + ), + ...isExpanded ? _buildExpandedButtons() : _buildCollapsedButtons(), + ], + ); + } + + List _buildExpandedButtons() { + return [ + const VSpace(padding), + MobileThirdPartySignInButton( + type: ThirdPartySignInButtonType.github, + onTap: () => widget.onSignIn(ThirdPartySignInButtonType.github), + ), + const VSpace(padding), + MobileThirdPartySignInButton( + type: ThirdPartySignInButtonType.discord, + onTap: () => widget.onSignIn(ThirdPartySignInButtonType.discord), + ), + ]; + } + + List _buildCollapsedButtons() { + final theme = AppFlowyTheme.of(context); + return [ + const VSpace(padding * 2), + AFGhostTextButton( + text: 'More options', + textColor: (context, isHovering, disabled) { + if (isHovering) { + return theme.fillColorScheme.themeThickHover; + } + return theme.textColorScheme.theme; + }, + onTap: () { + setState(() { + isExpanded = !isExpanded; + }); + }, + ), + ]; + } +} diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/third_party_sign_in_buttons.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/third_party_sign_in_buttons.dart deleted file mode 100644 index 7baa243e5f..0000000000 --- a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/third_party_sign_in_buttons.dart +++ /dev/null @@ -1,203 +0,0 @@ -import 'dart:io'; - -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/user/application/sign_in_bloc.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:universal_platform/universal_platform.dart'; - -import 'third_party_sign_in_button.dart'; - -typedef _SignInCallback = void Function(ThirdPartySignInButtonType signInType); - -@visibleForTesting -const Key signInWithGoogleButtonKey = Key('signInWithGoogleButton'); - -class ThirdPartySignInButtons extends StatelessWidget { - /// Used in DesktopSignInScreen, MobileSignInScreen and SettingThirdPartyLogin - const ThirdPartySignInButtons({ - super.key, - this.expanded = false, - }); - - final bool expanded; - - @override - Widget build(BuildContext context) { - if (UniversalPlatform.isDesktopOrWeb) { - return _DesktopThirdPartySignIn( - onSignIn: (type) => _signIn(context, type.provider), - ); - } else { - return _MobileThirdPartySignIn( - isExpanded: expanded, - onSignIn: (type) => _signIn(context, type.provider), - ); - } - } - - void _signIn(BuildContext context, String provider) { - context.read().add( - SignInEvent.signedInWithOAuth(provider), - ); - } -} - -class _DesktopThirdPartySignIn extends StatefulWidget { - const _DesktopThirdPartySignIn({ - required this.onSignIn, - }); - - final _SignInCallback onSignIn; - - @override - State<_DesktopThirdPartySignIn> createState() => - _DesktopThirdPartySignInState(); -} - -class _DesktopThirdPartySignInState extends State<_DesktopThirdPartySignIn> { - static const padding = 12.0; - - bool isExpanded = false; - - @override - Widget build(BuildContext context) { - return Column( - children: [ - DesktopSignInButton( - key: signInWithGoogleButtonKey, - type: ThirdPartySignInButtonType.google, - onPressed: () => widget.onSignIn(ThirdPartySignInButtonType.google), - ), - const VSpace(padding), - DesktopSignInButton( - type: ThirdPartySignInButtonType.apple, - onPressed: () => widget.onSignIn(ThirdPartySignInButtonType.apple), - ), - ...isExpanded ? _buildExpandedButtons() : _buildCollapsedButtons(), - ], - ); - } - - List _buildExpandedButtons() { - return [ - const VSpace(padding * 1.5), - DesktopSignInButton( - type: ThirdPartySignInButtonType.github, - onPressed: () => widget.onSignIn(ThirdPartySignInButtonType.github), - ), - const VSpace(padding), - DesktopSignInButton( - type: ThirdPartySignInButtonType.discord, - onPressed: () => widget.onSignIn(ThirdPartySignInButtonType.discord), - ), - ]; - } - - List _buildCollapsedButtons() { - return [ - const VSpace(padding), - MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: () { - setState(() { - isExpanded = !isExpanded; - }); - }, - child: FlowyText( - LocaleKeys.signIn_continueAnotherWay.tr(), - color: Theme.of(context).colorScheme.onSurface, - decoration: TextDecoration.underline, - fontSize: 14, - ), - ), - ), - ]; - } -} - -class _MobileThirdPartySignIn extends StatefulWidget { - const _MobileThirdPartySignIn({ - required this.isExpanded, - required this.onSignIn, - }); - - final bool isExpanded; - final _SignInCallback onSignIn; - - @override - State<_MobileThirdPartySignIn> createState() => - _MobileThirdPartySignInState(); -} - -class _MobileThirdPartySignInState extends State<_MobileThirdPartySignIn> { - static const padding = 8.0; - - bool isExpanded = false; - - @override - void initState() { - super.initState(); - - isExpanded = widget.isExpanded; - } - - @override - Widget build(BuildContext context) { - return Column( - children: [ - // only display apple sign in button on iOS - if (Platform.isIOS) ...[ - MobileThirdPartySignInButton( - type: ThirdPartySignInButtonType.apple, - onPressed: () => widget.onSignIn(ThirdPartySignInButtonType.apple), - ), - const VSpace(padding), - ], - MobileThirdPartySignInButton( - key: signInWithGoogleButtonKey, - type: ThirdPartySignInButtonType.google, - onPressed: () => widget.onSignIn(ThirdPartySignInButtonType.google), - ), - ...isExpanded ? _buildExpandedButtons() : _buildCollapsedButtons(), - ], - ); - } - - List _buildExpandedButtons() { - return [ - const VSpace(padding), - MobileThirdPartySignInButton( - type: ThirdPartySignInButtonType.github, - onPressed: () => widget.onSignIn(ThirdPartySignInButtonType.github), - ), - const VSpace(padding), - MobileThirdPartySignInButton( - type: ThirdPartySignInButtonType.discord, - onPressed: () => widget.onSignIn(ThirdPartySignInButtonType.discord), - ), - ]; - } - - List _buildCollapsedButtons() { - return [ - const VSpace(padding * 2), - GestureDetector( - onTap: () { - setState(() { - isExpanded = !isExpanded; - }); - }, - child: FlowyText( - LocaleKeys.signIn_continueAnotherWay.tr(), - color: Theme.of(context).colorScheme.onSurface, - decoration: TextDecoration.underline, - fontSize: 14, - ), - ), - ]; - } -} diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/widgets.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/widgets.dart index 18e260a472..6d79b896c1 100644 --- a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/widgets.dart +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_in_screen/widgets/widgets.dart @@ -1,7 +1,7 @@ -export 'magic_link_sign_in_buttons.dart'; +export 'continue_with/continue_with_email_and_password.dart'; +export 'sign_in_agreement.dart'; export 'sign_in_anonymous_button.dart'; export 'sign_in_or_logout_button.dart'; -export 'third_party_sign_in_button.dart'; +export 'third_party_sign_in_button/third_party_sign_in_button.dart'; // export 'switch_sign_in_sign_up_button.dart'; -export 'third_party_sign_in_buttons.dart'; -export 'sign_in_agreement.dart'; +export 'third_party_sign_in_button/third_party_sign_in_buttons.dart'; diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_up_screen.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/sign_up_screen.dart deleted file mode 100644 index 8aea8dde55..0000000000 --- a/frontend/appflowy_flutter/lib/user/presentation/screens/sign_up_screen.dart +++ /dev/null @@ -1,220 +0,0 @@ -import 'package:appflowy/generated/flowy_svgs.g.dart'; -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/startup/startup.dart'; -import 'package:appflowy/user/application/sign_up_bloc.dart'; -import 'package:appflowy/user/presentation/router.dart'; -import 'package:appflowy/user/presentation/widgets/widgets.dart'; -import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; -import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart' - show UserProfilePB; -import 'package:appflowy_result/appflowy_result.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra_ui/style_widget/snap_bar.dart'; -import 'package:flowy_infra_ui/style_widget/text.dart'; -import 'package:flowy_infra_ui/widget/rounded_button.dart'; -import 'package:flowy_infra_ui/widget/rounded_input_field.dart'; -import 'package:flowy_infra_ui/widget/spacing.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -class SignUpScreen extends StatelessWidget { - const SignUpScreen({ - super.key, - required this.router, - }); - - static const routeName = '/SignUpScreen'; - final AuthRouter router; - - @override - Widget build(BuildContext context) { - return BlocProvider( - create: (context) => getIt(), - child: BlocListener( - listener: (context, state) { - final successOrFail = state.successOrFail; - if (successOrFail != null) { - _handleSuccessOrFail(context, successOrFail); - } - }, - child: const Scaffold(body: SignUpForm()), - ), - ); - } - - void _handleSuccessOrFail( - BuildContext context, - FlowyResult result, - ) { - result.fold( - (user) => router.pushWorkspaceStartScreen(context, user), - (error) => showSnapBar(context, error.msg), - ); - } -} - -class SignUpForm extends StatelessWidget { - const SignUpForm({ - super.key, - }); - - @override - Widget build(BuildContext context) { - return Align( - child: AuthFormContainer( - children: [ - FlowyLogoTitle( - title: LocaleKeys.signUp_title.tr(), - logoSize: const Size(60, 60), - ), - const VSpace(30), - const EmailTextField(), - const VSpace(5), - const PasswordTextField(), - const VSpace(5), - const RepeatPasswordTextField(), - const VSpace(30), - const SignUpButton(), - const VSpace(10), - const SignUpPrompt(), - if (context.read().state.isSubmitting) ...[ - const SizedBox(height: 8), - const LinearProgressIndicator(), - ], - ], - ), - ); - } -} - -class SignUpPrompt extends StatelessWidget { - const SignUpPrompt({ - super.key, - }); - - @override - Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - FlowyText.medium( - LocaleKeys.signUp_alreadyHaveAnAccount.tr(), - color: Theme.of(context).hintColor, - ), - TextButton( - style: TextButton.styleFrom( - textStyle: Theme.of(context).textTheme.bodyMedium, - ), - onPressed: () => Navigator.pop(context), - child: FlowyText.medium( - LocaleKeys.signIn_buttonText.tr(), - color: Theme.of(context).colorScheme.primary, - ), - ), - ], - ); - } -} - -class SignUpButton extends StatelessWidget { - const SignUpButton({ - super.key, - }); - - @override - Widget build(BuildContext context) { - return RoundedTextButton( - title: LocaleKeys.signUp_getStartedText.tr(), - height: 48, - onPressed: () { - context - .read() - .add(const SignUpEvent.signUpWithUserEmailAndPassword()); - }, - ); - } -} - -class PasswordTextField extends StatelessWidget { - const PasswordTextField({ - super.key, - }); - - @override - Widget build(BuildContext context) { - return BlocBuilder( - buildWhen: (previous, current) => - previous.passwordError != current.passwordError, - builder: (context, state) { - return RoundedInputField( - obscureText: true, - obscureIcon: const FlowySvg(FlowySvgs.hide_m), - obscureHideIcon: const FlowySvg(FlowySvgs.show_m), - hintText: LocaleKeys.signUp_passwordHint.tr(), - normalBorderColor: Theme.of(context).colorScheme.outline, - errorBorderColor: Theme.of(context).colorScheme.error, - cursorColor: Theme.of(context).colorScheme.primary, - errorText: context.read().state.passwordError ?? '', - onChanged: (value) => context - .read() - .add(SignUpEvent.passwordChanged(value)), - ); - }, - ); - } -} - -class RepeatPasswordTextField extends StatelessWidget { - const RepeatPasswordTextField({ - super.key, - }); - - @override - Widget build(BuildContext context) { - return BlocBuilder( - buildWhen: (previous, current) => - previous.repeatPasswordError != current.repeatPasswordError, - builder: (context, state) { - return RoundedInputField( - obscureText: true, - obscureIcon: const FlowySvg(FlowySvgs.hide_m), - obscureHideIcon: const FlowySvg(FlowySvgs.show_m), - hintText: LocaleKeys.signUp_repeatPasswordHint.tr(), - normalBorderColor: Theme.of(context).colorScheme.outline, - errorBorderColor: Theme.of(context).colorScheme.error, - cursorColor: Theme.of(context).colorScheme.primary, - errorText: context.read().state.repeatPasswordError ?? '', - onChanged: (value) => context - .read() - .add(SignUpEvent.repeatPasswordChanged(value)), - ); - }, - ); - } -} - -class EmailTextField extends StatelessWidget { - const EmailTextField({ - super.key, - }); - - @override - Widget build(BuildContext context) { - return BlocBuilder( - buildWhen: (previous, current) => - previous.emailError != current.emailError, - builder: (context, state) { - return RoundedInputField( - hintText: LocaleKeys.signUp_emailHint.tr(), - style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500), - normalBorderColor: Theme.of(context).colorScheme.outline, - errorBorderColor: Theme.of(context).colorScheme.error, - cursorColor: Theme.of(context).colorScheme.primary, - errorText: context.read().state.emailError ?? '', - onChanged: (value) => - context.read().add(SignUpEvent.emailChanged(value)), - ); - }, - ); - } -} diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/splash_screen.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/splash_screen.dart index 146bf06df1..4062cedf8e 100644 --- a/frontend/appflowy_flutter/lib/user/presentation/screens/splash_screen.dart +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/splash_screen.dart @@ -8,7 +8,6 @@ import 'package:appflowy/user/presentation/helpers/helpers.dart'; import 'package:appflowy/user/presentation/router.dart'; import 'package:appflowy/user/presentation/screens/screens.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart'; -import 'package:appflowy_backend/log.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; @@ -61,32 +60,15 @@ class SplashScreen extends StatelessWidget { BuildContext context, Authenticated authenticated, ) async { - final userProfile = authenticated.userProfile; - - /// After a user is authenticated, this function checks if encryption is required. - final result = await UserEventCheckEncryptionSign().send(); - await result.fold( - (check) async { - /// If encryption is needed, the user is navigated to the encryption screen. - /// Otherwise, it fetches the current workspace for the user and navigates them - if (check.requireSecret) { - getIt().pushEncryptionScreen(context, userProfile); - } else { - final result = await FolderEventGetCurrentWorkspaceSetting().send(); - result.fold( - (workspaceSetting) { - // After login, replace Splash screen by corresponding home screen - getIt().goHomeScreen( - context, - ); - }, - (error) => handleOpenWorkspaceError(context, error), - ); - } - }, - (err) { - Log.error(err); + final result = await FolderEventGetCurrentWorkspaceSetting().send(); + result.fold( + (workspaceSetting) { + // After login, replace Splash screen by corresponding home screen + getIt().goHomeScreen( + context, + ); }, + (error) => handleOpenWorkspaceError(context, error), ); } @@ -115,7 +97,7 @@ class Body extends StatelessWidget { return Container( alignment: Alignment.center, child: UniversalPlatform.isMobile - ? const FlowySvg(FlowySvgs.flowy_logo_xl, blendMode: null) + ? const FlowySvg(FlowySvgs.app_logo_xl, blendMode: null) : const _DesktopSplashBody(), ); } diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/workspace_error_screen.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/workspace_error_screen.dart index d79127e04c..af6d4ad770 100644 --- a/frontend/appflowy_flutter/lib/user/presentation/screens/workspace_error_screen.dart +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/workspace_error_screen.dart @@ -1,7 +1,6 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/user/application/auth/auth_service.dart'; -import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -86,7 +85,6 @@ class WorkspaceErrorScreen extends StatelessWidget { const VSpace(50), const LogoutButton(), const VSpace(20), - const ResetWorkspaceButton(), ]); return Center( @@ -157,43 +155,3 @@ class LogoutButton extends StatelessWidget { ); } } - -class ResetWorkspaceButton extends StatelessWidget { - const ResetWorkspaceButton({super.key}); - - @override - Widget build(BuildContext context) { - return SizedBox( - width: 200, - height: 40, - child: BlocBuilder( - builder: (context, state) { - final isLoading = state.loadingState?.isLoading() ?? false; - final icon = isLoading - ? const Center( - child: CircularProgressIndicator.adaptive(), - ) - : null; - - return FlowyButton( - text: FlowyText.medium( - LocaleKeys.workspace_reset.tr(), - textAlign: TextAlign.center, - ), - onTap: () { - NavigatorAlertDialog( - title: LocaleKeys.workspace_resetWorkspacePrompt.tr(), - confirm: () { - context.read().add( - const WorkspaceErrorEvent.resetWorkspace(), - ); - }, - ).show(context); - }, - rightIcon: icon, - ); - }, - ), - ); - } -} diff --git a/frontend/appflowy_flutter/lib/user/presentation/screens/workspace_start_screen/mobile_workspace_start_screen.dart b/frontend/appflowy_flutter/lib/user/presentation/screens/workspace_start_screen/mobile_workspace_start_screen.dart index 59b61aa54b..a6124da60b 100644 --- a/frontend/appflowy_flutter/lib/user/presentation/screens/workspace_start_screen/mobile_workspace_start_screen.dart +++ b/frontend/appflowy_flutter/lib/user/presentation/screens/workspace_start_screen/mobile_workspace_start_screen.dart @@ -57,7 +57,7 @@ class _MobileWorkspaceStartScreenState children: [ const Spacer(), const FlowySvg( - FlowySvgs.flowy_logo_xl, + FlowySvgs.app_logo_xl, size: Size.square(64), blendMode: null, ), diff --git a/frontend/appflowy_flutter/lib/user/presentation/widgets/auth_form_container.dart b/frontend/appflowy_flutter/lib/user/presentation/widgets/auth_form_container.dart index 8ce09a5b7f..c0b8e7e5ae 100644 --- a/frontend/appflowy_flutter/lib/user/presentation/widgets/auth_form_container.dart +++ b/frontend/appflowy_flutter/lib/user/presentation/widgets/auth_form_container.dart @@ -8,7 +8,7 @@ class AuthFormContainer extends StatelessWidget { final List children; - static const double width = 340; + static const double width = 320; @override Widget build(BuildContext context) { diff --git a/frontend/appflowy_flutter/lib/user/presentation/widgets/flowy_logo_title.dart b/frontend/appflowy_flutter/lib/user/presentation/widgets/flowy_logo_title.dart index c2a13eac82..14b1c896a9 100644 --- a/frontend/appflowy_flutter/lib/user/presentation/widgets/flowy_logo_title.dart +++ b/frontend/appflowy_flutter/lib/user/presentation/widgets/flowy_logo_title.dart @@ -1,8 +1,7 @@ -import 'package:appflowy/generated/flowy_svgs.g.dart'; -import 'package:flowy_infra/size.dart'; +import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/logo/logo.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; -import 'package:google_fonts/google_fonts.dart'; class FlowyLogoTitle extends StatelessWidget { const FlowyLogoTitle({ @@ -16,24 +15,19 @@ class FlowyLogoTitle extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); + return SizedBox( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - SizedBox.fromSize( - size: logoSize, - child: const FlowySvg( - FlowySvgs.flowy_logo_xl, - blendMode: null, - ), - ), + AFLogo(size: logoSize), const VSpace(20), - FlowyText.regular( + Text( title, - fontSize: FontSizes.s24, - fontFamily: - GoogleFonts.poppins(fontWeight: FontWeight.w500).fontFamily, - color: Theme.of(context).colorScheme.tertiary, + style: theme.textStyle.heading3.enhanced( + color: theme.textColorScheme.primary, + ), ), ], ), diff --git a/frontend/appflowy_flutter/lib/util/color_to_hex_string.dart b/frontend/appflowy_flutter/lib/util/color_to_hex_string.dart index 34925235cb..61694367bb 100644 --- a/frontend/appflowy_flutter/lib/util/color_to_hex_string.dart +++ b/frontend/appflowy_flutter/lib/util/color_to_hex_string.dart @@ -5,12 +5,17 @@ import 'package:flutter/material.dart'; extension ColorExtension on Color { /// return a hex string in 0xff000000 format String toHexString() { - return '0x${value.toRadixString(16).padLeft(8, '0')}'; + final alpha = (a * 255).toInt().toRadixString(16).padLeft(2, '0'); + final red = (r * 255).toInt().toRadixString(16).padLeft(2, '0'); + final green = (g * 255).toInt().toRadixString(16).padLeft(2, '0'); + final blue = (b * 255).toInt().toRadixString(16).padLeft(2, '0'); + + return '0x$alpha$red$green$blue'.toLowerCase(); } /// return a random color static Color random({double opacity = 1.0}) { return Color((math.Random().nextDouble() * 0xFFFFFF).toInt()) - .withOpacity(opacity); + .withValues(alpha: opacity); } } diff --git a/frontend/appflowy_flutter/lib/util/default_extensions.dart b/frontend/appflowy_flutter/lib/util/default_extensions.dart index d0d36d2698..603a66d6cf 100644 --- a/frontend/appflowy_flutter/lib/util/default_extensions.dart +++ b/frontend/appflowy_flutter/lib/util/default_extensions.dart @@ -14,12 +14,10 @@ const List defaultImageExtensions = [ 'bmp', ]; -extension ImageStringExtension on String { - bool isImageUrl() { - final imagePattern = RegExp( - r'\.(jpe?g|png|gif|webp|bmp)$', - caseSensitive: false, - ); - return imagePattern.hasMatch(this); - } +bool isNotImageUrl(String url) { + final nonImageSuffixRegex = RegExp( + r'(\.(io|html|php|json|txt|js|css|xml|md|log)(\?.*)?(#.*)?$)|/$', + caseSensitive: false, + ); + return nonImageSuffixRegex.hasMatch(url); } diff --git a/frontend/appflowy_flutter/lib/util/expand_views.dart b/frontend/appflowy_flutter/lib/util/expand_views.dart new file mode 100644 index 0000000000..115c0d2d29 --- /dev/null +++ b/frontend/appflowy_flutter/lib/util/expand_views.dart @@ -0,0 +1,40 @@ +import 'package:flutter/cupertino.dart'; + +class ViewExpanderRegistry { + /// the key is view id + final Map> _viewExpanders = {}; + + bool isViewExpanded(String id) => getExpander(id)?.isViewExpanded ?? false; + + void register(String id, ViewExpander expander) { + final expanders = _viewExpanders[id] ?? {}; + expanders.add(expander); + _viewExpanders[id] = expanders; + } + + void unregister(String id, ViewExpander expander) { + final expanders = _viewExpanders[id] ?? {}; + expanders.remove(expander); + if (expanders.isEmpty) { + _viewExpanders.remove(id); + } else { + _viewExpanders[id] = expanders; + } + } + + ViewExpander? getExpander(String id) { + final expanders = _viewExpanders[id] ?? {}; + return expanders.isEmpty ? null : expanders.first; + } +} + +class ViewExpander { + ViewExpander(this._isExpandedCallback, this._expandCallback); + + final ValueGetter _isExpandedCallback; + final VoidCallback _expandCallback; + + bool get isViewExpanded => _isExpandedCallback.call(); + + void expand() => _expandCallback.call(); +} diff --git a/frontend/appflowy_flutter/lib/util/share_log_files.dart b/frontend/appflowy_flutter/lib/util/share_log_files.dart index d7e7b6ce87..b8dd390627 100644 --- a/frontend/appflowy_flutter/lib/util/share_log_files.dart +++ b/frontend/appflowy_flutter/lib/util/share_log_files.dart @@ -25,7 +25,6 @@ Future shareLogFiles(BuildContext? context) async { if (archiveLogFiles.isEmpty) { if (context != null && context.mounted) { showToastNotification( - context, message: LocaleKeys.noLogFiles.tr(), type: ToastificationType.error, ); @@ -42,7 +41,6 @@ Future shareLogFiles(BuildContext? context) async { if (zip == null) { if (context != null && context.mounted) { showToastNotification( - context, message: LocaleKeys.noLogFiles.tr(), type: ToastificationType.error, ); @@ -72,7 +70,6 @@ Future shareLogFiles(BuildContext? context) async { } catch (e) { if (context != null && context.mounted) { showToastNotification( - context, message: e.toString(), type: ToastificationType.error, ); diff --git a/frontend/appflowy_flutter/lib/util/string_extension.dart b/frontend/appflowy_flutter/lib/util/string_extension.dart index 3e66e73052..84dba35e1a 100644 --- a/frontend/appflowy_flutter/lib/util/string_extension.dart +++ b/frontend/appflowy_flutter/lib/util/string_extension.dart @@ -84,3 +84,11 @@ extension IconExtension on String { )..iconGroup = iconGroup; } } + +extension CounterExtension on String { + Counters getCounter() { + final wordCount = wordRegex.allMatches(this).length; + final charCount = runes.length; + return Counters(wordCount: wordCount, charCount: charCount); + } +} diff --git a/frontend/appflowy_flutter/lib/util/throttle.dart b/frontend/appflowy_flutter/lib/util/throttle.dart index c8c6dcf0ca..0aaa9f2d3a 100644 --- a/frontend/appflowy_flutter/lib/util/throttle.dart +++ b/frontend/appflowy_flutter/lib/util/throttle.dart @@ -16,6 +16,10 @@ class Throttler { }); } + void cancel() { + _timer?.cancel(); + } + void dispose() { _timer?.cancel(); _timer = null; diff --git a/frontend/appflowy_flutter/lib/workspace/application/appearance_defaults.dart b/frontend/appflowy_flutter/lib/workspace/application/appearance_defaults.dart index d900afd6eb..c3190a8e40 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/appearance_defaults.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/appearance_defaults.dart @@ -1,7 +1,6 @@ -import 'package:flutter/material.dart'; - import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart'; import 'package:flowy_infra/theme.dart'; +import 'package:flutter/material.dart'; /// A class for the default appearance settings for the app class DefaultAppearanceSettings { @@ -15,6 +14,6 @@ class DefaultAppearanceSettings { } static Color getDefaultSelectionColor(BuildContext context) { - return Theme.of(context).colorScheme.primary.withOpacity(0.2); + return Theme.of(context).colorScheme.primary.withValues(alpha: 0.2); } } diff --git a/frontend/appflowy_flutter/lib/workspace/application/command_palette/command_palette_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/command_palette/command_palette_bloc.dart index 7787525847..01f638fe7a 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/command_palette/command_palette_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/command_palette/command_palette_bloc.dart @@ -1,204 +1,348 @@ import 'dart:async'; -import 'package:flutter/foundation.dart'; - import 'package:appflowy/plugins/trash/application/trash_listener.dart'; import 'package:appflowy/plugins/trash/application/trash_service.dart'; -import 'package:appflowy/workspace/application/command_palette/search_listener.dart'; import 'package:appflowy/workspace/application/command_palette/search_service.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/trash.pb.dart'; -import 'package:appflowy_backend/protobuf/flowy-search/notification.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-search/result.pb.dart'; import 'package:bloc/bloc.dart'; +import 'package:flutter/foundation.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'command_palette_bloc.freezed.dart'; -const _searchChannel = 'CommandPalette'; +class Debouncer { + Debouncer({required this.delay}); + + final Duration delay; + Timer? _timer; + + void run(void Function() action) { + _timer?.cancel(); + _timer = Timer(delay, action); + } + + void dispose() { + _timer?.cancel(); + } +} class CommandPaletteBloc extends Bloc { CommandPaletteBloc() : super(CommandPaletteState.initial()) { - _searchListener.start( - onResultsChanged: _onResultsChanged, - ); + on<_SearchChanged>(_onSearchChanged); + on<_PerformSearch>(_onPerformSearch); + on<_NewSearchStream>(_onNewSearchStream); + on<_ResultsChanged>(_onResultsChanged); + on<_TrashChanged>(_onTrashChanged); + on<_WorkspaceChanged>(_onWorkspaceChanged); + on<_ClearSearch>(_onClearSearch); _initTrash(); - _dispatch(); } - Timer? _debounceOnChanged; - final TrashService _trashService = TrashService(); - final SearchListener _searchListener = SearchListener( - channel: _searchChannel, + final Debouncer _searchDebouncer = Debouncer( + delay: const Duration(milliseconds: 300), ); + final TrashService _trashService = TrashService(); final TrashListener _trashListener = TrashListener(); - String? _oldQuery; + String? _activeQuery; String? _workspaceId; - int _messagesReceived = 0; @override Future close() { _trashListener.close(); - _searchListener.stop(); - _debounceOnChanged?.cancel(); + _searchDebouncer.dispose(); + state.searchResponseStream?.dispose(); return super.close(); } - void _dispatch() { - on((event, emit) async { - event.when( - searchChanged: _debounceOnSearchChanged, - trashChanged: (trash) async { - if (trash != null) { - return emit(state.copyWith(trash: trash)); - } - - final trashOrFailure = await _trashService.readTrash(); - final trashRes = trashOrFailure.fold( - (trash) => trash, - (error) => null, - ); - - if (trashRes != null) { - emit(state.copyWith(trash: trashRes.items)); - } - }, - performSearch: (search) async { - if (search.isNotEmpty && search != state.query) { - _oldQuery = state.query; - emit(state.copyWith(query: search, isLoading: true)); - await SearchBackendService.performSearch( - search, - workspaceId: _workspaceId, - channel: _searchChannel, - ); - } else { - emit(state.copyWith(query: null, isLoading: false, results: [])); - } - }, - resultsChanged: (results) { - if (state.query != _oldQuery) { - emit(state.copyWith(results: [], isLoading: true)); - _oldQuery = state.query; - _messagesReceived = 0; - } - - if (state.query != results.query) { - return; - } - - _messagesReceived++; - - final searchResults = _filterDuplicates(results.items); - searchResults.sort((a, b) => b.score.compareTo(a.score)); - - emit( - state.copyWith( - results: searchResults, - isLoading: _messagesReceived != results.sends.toInt(), - ), - ); - }, - workspaceChanged: (workspaceId) { - _workspaceId = workspaceId; - emit(state.copyWith(results: [], query: '', isLoading: false)); - }, - clearSearch: () { - emit(state.copyWith(results: [], query: '', isLoading: false)); - }, - ); - }); - } - Future _initTrash() async { _trashListener.start( - trashUpdated: (trashOrFailed) { - final trash = trashOrFailed.toNullable(); - add(CommandPaletteEvent.trashChanged(trash: trash)); - }, + trashUpdated: (trashOrFailed) => add( + CommandPaletteEvent.trashChanged( + trash: trashOrFailed.toNullable(), + ), + ), ); final trashOrFailure = await _trashService.readTrash(); - final trash = trashOrFailure.toNullable(); - - add(CommandPaletteEvent.trashChanged(trash: trash?.items)); - } - - void _debounceOnSearchChanged(String value) { - _debounceOnChanged?.cancel(); - _debounceOnChanged = Timer( - const Duration(milliseconds: 300), - () => _performSearch(value), + trashOrFailure.fold( + (trash) => add(CommandPaletteEvent.trashChanged(trash: trash.items)), + (error) => debugPrint('Failed to load trash: $error'), ); } - List _filterDuplicates(List results) { - final currentItems = [...state.results]; - final res = [...results]; - - for (final item in results) { - if (item.data.trim().isEmpty) { - continue; - } - - final duplicateIndex = currentItems.indexWhere((a) => a.id == item.id); - if (duplicateIndex == -1) { - continue; - } - - final duplicate = currentItems[duplicateIndex]; - if (item.score < duplicate.score) { - res.remove(item); - } else { - currentItems.remove(duplicate); - } - } - - return res..addAll(currentItems); + FutureOr _onSearchChanged( + _SearchChanged event, + Emitter emit, + ) { + _searchDebouncer.run( + () { + if (!isClosed) { + add(CommandPaletteEvent.performSearch(search: event.search)); + } + }, + ); } - void _performSearch(String value) => - add(CommandPaletteEvent.performSearch(search: value)); + FutureOr _onPerformSearch( + _PerformSearch event, + Emitter emit, + ) async { + if (event.search.isEmpty && event.search != state.query) { + emit( + state.copyWith( + query: null, + searching: false, + serverResponseItems: [], + localResponseItems: [], + combinedResponseItems: {}, + resultSummaries: [], + generatingAIOverview: false, + ), + ); + } else { + emit(state.copyWith(query: event.search, searching: true)); + _activeQuery = event.search; - void _onResultsChanged(SearchResultNotificationPB results) => - add(CommandPaletteEvent.resultsChanged(results: results)); + unawaited( + SearchBackendService.performSearch( + event.search, + workspaceId: _workspaceId, + ).then( + (result) => result.fold( + (stream) { + if (!isClosed && _activeQuery == event.search) { + add(CommandPaletteEvent.newSearchStream(stream: stream)); + } + }, + (error) { + debugPrint('Search error: $error'); + if (!isClosed) { + add( + CommandPaletteEvent.resultsChanged( + searchId: '', + searching: false, + generatingAIOverview: false, + ), + ); + } + }, + ), + ), + ); + } + } + + FutureOr _onNewSearchStream( + _NewSearchStream event, + Emitter emit, + ) { + state.searchResponseStream?.dispose(); + emit( + state.copyWith( + searchId: event.stream.searchId, + searchResponseStream: event.stream, + ), + ); + + event.stream.listen( + onLocalItems: (items, searchId) => _handleResultsUpdate( + searchId: searchId, + localItems: items, + ), + onServerItems: (items, searchId, searching, generatingAIOverview) => + _handleResultsUpdate( + searchId: searchId, + serverItems: items, + searching: searching, + generatingAIOverview: generatingAIOverview, + ), + onSummaries: (summaries, searchId, searching, generatingAIOverview) => + _handleResultsUpdate( + searchId: searchId, + summaries: summaries, + searching: searching, + generatingAIOverview: generatingAIOverview, + ), + onFinished: (searchId) => _handleResultsUpdate( + searchId: searchId, + searching: false, + ), + ); + } + + void _handleResultsUpdate({ + required String searchId, + List? serverItems, + List? localItems, + List? summaries, + bool searching = true, + bool generatingAIOverview = false, + }) { + if (_isActiveSearch(searchId)) { + add( + CommandPaletteEvent.resultsChanged( + searchId: searchId, + serverItems: serverItems, + localItems: localItems, + summaries: summaries, + searching: searching, + generatingAIOverview: generatingAIOverview, + ), + ); + } + } + + FutureOr _onResultsChanged( + _ResultsChanged event, + Emitter emit, + ) async { + if (state.searchId != event.searchId) return; + + final combinedItems = {}; + for (final item in event.serverItems ?? state.serverResponseItems) { + combinedItems[item.id] = SearchResultItem( + id: item.id, + icon: item.icon, + displayName: item.displayName, + content: item.content, + workspaceId: item.workspaceId, + ); + } + + for (final item in event.localItems ?? state.localResponseItems) { + combinedItems.putIfAbsent( + item.id, + () => SearchResultItem( + id: item.id, + icon: item.icon, + displayName: item.displayName, + content: '', + workspaceId: item.workspaceId, + ), + ); + } + + emit( + state.copyWith( + serverResponseItems: event.serverItems ?? state.serverResponseItems, + localResponseItems: event.localItems ?? state.localResponseItems, + resultSummaries: event.summaries ?? state.resultSummaries, + combinedResponseItems: combinedItems, + searching: event.searching, + generatingAIOverview: event.generatingAIOverview, + ), + ); + } + + FutureOr _onTrashChanged( + _TrashChanged event, + Emitter emit, + ) async { + if (event.trash != null) { + emit(state.copyWith(trash: event.trash!)); + } else { + final trashOrFailure = await _trashService.readTrash(); + trashOrFailure.fold((trash) { + emit(state.copyWith(trash: trash.items)); + }, (error) { + // Optionally handle error; otherwise, we simply do nothing. + }); + } + } + + FutureOr _onWorkspaceChanged( + _WorkspaceChanged event, + Emitter emit, + ) { + _workspaceId = event.workspaceId; + emit( + state.copyWith( + query: '', + serverResponseItems: [], + localResponseItems: [], + combinedResponseItems: {}, + resultSummaries: [], + searching: false, + generatingAIOverview: false, + ), + ); + } + + FutureOr _onClearSearch( + _ClearSearch event, + Emitter emit, + ) { + emit(CommandPaletteState.initial().copyWith(trash: state.trash)); + } + + bool _isActiveSearch(String searchId) => + !isClosed && state.searchId == searchId; } @freezed class CommandPaletteEvent with _$CommandPaletteEvent { const factory CommandPaletteEvent.searchChanged({required String search}) = _SearchChanged; - const factory CommandPaletteEvent.performSearch({required String search}) = _PerformSearch; - + const factory CommandPaletteEvent.newSearchStream({ + required SearchResponseStream stream, + }) = _NewSearchStream; const factory CommandPaletteEvent.resultsChanged({ - required SearchResultNotificationPB results, + required String searchId, + required bool searching, + required bool generatingAIOverview, + List? serverItems, + List? localItems, + List? summaries, }) = _ResultsChanged; const factory CommandPaletteEvent.trashChanged({ @Default(null) List? trash, }) = _TrashChanged; - const factory CommandPaletteEvent.workspaceChanged({ @Default(null) String? workspaceId, }) = _WorkspaceChanged; - const factory CommandPaletteEvent.clearSearch() = _ClearSearch; } +class SearchResultItem { + const SearchResultItem({ + required this.id, + required this.icon, + required this.content, + required this.displayName, + this.workspaceId, + }); + + final String id; + final String content; + final ResultIconPB icon; + final String displayName; + final String? workspaceId; +} + @freezed class CommandPaletteState with _$CommandPaletteState { const CommandPaletteState._(); - const factory CommandPaletteState({ @Default(null) String? query, - required List results, - required bool isLoading, + @Default([]) List serverResponseItems, + @Default([]) List localResponseItems, + @Default({}) Map combinedResponseItems, + @Default([]) List resultSummaries, + @Default(null) SearchResponseStream? searchResponseStream, + required bool searching, + required bool generatingAIOverview, @Default([]) List trash, + @Default(null) String? searchId, }) = _CommandPaletteState; - factory CommandPaletteState.initial() => - const CommandPaletteState(results: [], isLoading: false); + factory CommandPaletteState.initial() => const CommandPaletteState( + searching: false, + generatingAIOverview: false, + ); } diff --git a/frontend/appflowy_flutter/lib/workspace/application/command_palette/search_listener.dart b/frontend/appflowy_flutter/lib/workspace/application/command_palette/search_listener.dart deleted file mode 100644 index b22630eb74..0000000000 --- a/frontend/appflowy_flutter/lib/workspace/application/command_palette/search_listener.dart +++ /dev/null @@ -1,74 +0,0 @@ -import 'dart:async'; -import 'dart:typed_data'; - -import 'package:appflowy/core/notification/search_notification.dart'; -import 'package:appflowy_backend/log.dart'; -import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; -import 'package:appflowy_backend/protobuf/flowy-search/notification.pb.dart'; -import 'package:appflowy_result/appflowy_result.dart'; -import 'package:flowy_infra/notifier.dart'; - -// Do not modify! -const _searchObjectId = "SEARCH_IDENTIFIER"; - -class SearchListener { - SearchListener({this.channel}); - - /// Use this to filter out search results from other channels. - /// - /// If null, it will receive search results from all - /// channels, otherwise it will only receive search results from the specified - /// channel. - /// - final String? channel; - - PublishNotifier? _updateNotifier = - PublishNotifier(); - PublishNotifier? _updateDidCloseNotifier = - PublishNotifier(); - SearchNotificationListener? _listener; - - void start({ - void Function(SearchResultNotificationPB)? onResultsChanged, - void Function(SearchResultNotificationPB)? onResultsClosed, - }) { - if (onResultsChanged != null) { - _updateNotifier?.addPublishListener(onResultsChanged); - } - - if (onResultsClosed != null) { - _updateDidCloseNotifier?.addPublishListener(onResultsClosed); - } - - _listener = SearchNotificationListener( - objectId: _searchObjectId, - handler: _handler, - channel: channel, - ); - } - - void _handler( - SearchNotification ty, - FlowyResult result, - ) { - switch (ty) { - case SearchNotification.DidUpdateResults: - result.fold( - (payload) => _updateNotifier?.value = - SearchResultNotificationPB.fromBuffer(payload), - (err) => Log.error(err), - ); - break; - default: - break; - } - } - - Future stop() async { - await _listener?.stop(); - _updateNotifier?.dispose(); - _updateNotifier = null; - _updateDidCloseNotifier?.dispose(); - _updateDidCloseNotifier = null; - } -} diff --git a/frontend/appflowy_flutter/lib/workspace/application/command_palette/search_result_ext.dart b/frontend/appflowy_flutter/lib/workspace/application/command_palette/search_result_ext.dart index 7b2eece965..6b6ea6d5c0 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/command_palette/search_result_ext.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/command_palette/search_result_ext.dart @@ -1,31 +1,54 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart'; +import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; +import 'package:appflowy_backend/protobuf/flowy-search/result.pb.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flutter/material.dart'; -import 'package:appflowy/generated/flowy_svgs.g.dart'; -import 'package:appflowy_backend/protobuf/flowy-search/result.pb.dart'; - -extension GetIcon on SearchResultPB { +extension GetIcon on ResultIconPB { Widget? getIcon() { - if (icon.ty == ResultIconTypePB.Emoji) { - return icon.value.isNotEmpty - ? Text( - icon.value, - style: const TextStyle(fontSize: 18.0), - ) + final iconValue = value, iconType = ty; + if (iconType == ResultIconTypePB.Emoji) { + return iconValue.isNotEmpty + ? FlowyText.emoji(iconValue, fontSize: 18) : null; - } else if (icon.ty == ResultIconTypePB.Icon) { - return FlowySvg(icon.getViewSvg(), size: const Size.square(20)); + } else if (ty == ResultIconTypePB.Icon) { + if (_resultIconValueTypes.contains(iconValue)) { + return FlowySvg(getViewSvg(), size: const Size.square(18)); + } + return RawEmojiIconWidget( + emoji: EmojiIconData(iconType.toFlowyIconType(), value), + emojiSize: 18, + ); } - return null; } } +extension ResultIconTypePBToFlowyIconType on ResultIconTypePB { + FlowyIconType toFlowyIconType() { + switch (this) { + case ResultIconTypePB.Emoji: + return FlowyIconType.emoji; + case ResultIconTypePB.Icon: + return FlowyIconType.icon; + case ResultIconTypePB.Url: + return FlowyIconType.custom; + default: + return FlowyIconType.custom; + } + } +} + extension _ToViewIcon on ResultIconPB { FlowySvgData getViewSvg() => switch (value) { "0" => FlowySvgs.icon_document_s, "1" => FlowySvgs.icon_grid_s, "2" => FlowySvgs.icon_board_s, "3" => FlowySvgs.icon_calendar_s, + "4" => FlowySvgs.chat_ai_page_s, _ => FlowySvgs.icon_document_s, }; } + +const _resultIconValueTypes = {'0', '1', '2', '3', '4'}; diff --git a/frontend/appflowy_flutter/lib/workspace/application/command_palette/search_result_list_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/command_palette/search_result_list_bloc.dart new file mode 100644 index 0000000000..e5953ae61b --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/application/command_palette/search_result_list_bloc.dart @@ -0,0 +1,83 @@ +import 'dart:async'; + +import 'package:appflowy/workspace/application/command_palette/command_palette_bloc.dart'; +import 'package:appflowy_backend/protobuf/flowy-search/result.pb.dart'; +import 'package:bloc/bloc.dart'; +import 'package:flutter/foundation.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'search_result_list_bloc.freezed.dart'; + +class SearchResultListBloc + extends Bloc { + SearchResultListBloc() : super(SearchResultListState.initial()) { + // Register event handlers + on<_OnHoverSummary>(_onHoverSummary); + on<_OnHoverResult>(_onHoverResult); + on<_OpenPage>(_onOpenPage); + } + + FutureOr _onHoverSummary( + _OnHoverSummary event, + Emitter emit, + ) { + emit( + state.copyWith( + hoveredSummary: event.summary, + hoveredResult: null, + userHovered: event.userHovered, + openPageId: null, + ), + ); + } + + FutureOr _onHoverResult( + _OnHoverResult event, + Emitter emit, + ) { + emit( + state.copyWith( + hoveredSummary: null, + hoveredResult: event.item, + userHovered: event.userHovered, + openPageId: null, + ), + ); + } + + FutureOr _onOpenPage( + _OpenPage event, + Emitter emit, + ) { + emit(state.copyWith(openPageId: event.pageId)); + } +} + +@freezed +class SearchResultListEvent with _$SearchResultListEvent { + const factory SearchResultListEvent.onHoverSummary({ + required SearchSummaryPB summary, + required bool userHovered, + }) = _OnHoverSummary; + const factory SearchResultListEvent.onHoverResult({ + required SearchResultItem item, + required bool userHovered, + }) = _OnHoverResult; + + const factory SearchResultListEvent.openPage({ + required String pageId, + }) = _OpenPage; +} + +@freezed +class SearchResultListState with _$SearchResultListState { + const SearchResultListState._(); + const factory SearchResultListState({ + @Default(null) SearchSummaryPB? hoveredSummary, + @Default(null) SearchResultItem? hoveredResult, + @Default(null) String? openPageId, + @Default(false) bool userHovered, + }) = _SearchResultListState; + + factory SearchResultListState.initial() => const SearchResultListState(); +} diff --git a/frontend/appflowy_flutter/lib/workspace/application/command_palette/search_service.dart b/frontend/appflowy_flutter/lib/workspace/application/command_palette/search_service.dart index 53a229ae66..89e5b604f8 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/command_palette/search_service.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/command_palette/search_service.dart @@ -1,22 +1,131 @@ +import 'dart:async'; +import 'dart:ffi'; +import 'dart:isolate'; +import 'dart:typed_data'; + import 'package:appflowy_backend/dispatch/dispatch.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-search/notification.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-search/query.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-search/result.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-search/search_filter.pb.dart'; import 'package:appflowy_result/appflowy_result.dart'; +import 'package:nanoid/nanoid.dart'; +import 'package:fixnum/fixnum.dart'; class SearchBackendService { - static Future> performSearch( + static Future> performSearch( String keyword, { String? workspaceId, - String? channel, }) async { + final searchId = nanoid(6); + final stream = SearchResponseStream(searchId: searchId); + final filter = SearchFilterPB(workspaceId: workspaceId); final request = SearchQueryPB( search: keyword, filter: filter, - channel: channel, + searchId: searchId, + streamPort: Int64(stream.nativePort), ); - return SearchEventSearch(request).send(); + unawaited(SearchEventSearch(request).send()); + return FlowyResult.success(stream); + } +} + +class SearchResponseStream { + SearchResponseStream({required this.searchId}) { + _port.handler = _controller.add; + _subscription = _controller.stream.listen( + (Uint8List data) => _onResultsChanged(data), + ); + } + + final String searchId; + final RawReceivePort _port = RawReceivePort(); + final StreamController _controller = StreamController.broadcast(); + late StreamSubscription _subscription; + void Function( + List items, + String searchId, + bool searching, + bool generatingAIOverview, + )? _onServerItems; + void Function( + List summaries, + String searchId, + bool searching, + bool generatingAIOverview, + )? _onSummaries; + + void Function( + List items, + String searchId, + )? _onLocalItems; + + void Function(String searchId)? _onFinished; + int get nativePort => _port.sendPort.nativePort; + + Future dispose() async { + await _subscription.cancel(); + _port.close(); + } + + void _onResultsChanged(Uint8List data) { + final searchState = SearchStatePB.fromBuffer(data); + + if (searchState.hasResponse()) { + if (searchState.response.hasSearchResult()) { + _onServerItems?.call( + searchState.response.searchResult.items, + searchId, + searchState.response.searching, + searchState.response.generatingAiSummary, + ); + } + if (searchState.response.hasSearchSummary()) { + _onSummaries?.call( + searchState.response.searchSummary.items, + searchId, + searchState.response.searching, + searchState.response.generatingAiSummary, + ); + } + + if (searchState.response.hasLocalSearchResult()) { + _onLocalItems?.call( + searchState.response.localSearchResult.items, + searchId, + ); + } + } else { + _onFinished?.call(searchId); + } + } + + void listen({ + required void Function( + List items, + String searchId, + bool isLoading, + bool generatingAIOverview, + )? onServerItems, + required void Function( + List summaries, + String searchId, + bool isLoading, + bool generatingAIOverview, + )? onSummaries, + required void Function( + List items, + String searchId, + )? onLocalItems, + required void Function(String searchId)? onFinished, + }) { + _onServerItems = onServerItems; + _onSummaries = onSummaries; + _onLocalItems = onLocalItems; + _onFinished = onFinished; } } diff --git a/frontend/appflowy_flutter/lib/workspace/application/export/document_exporter.dart b/frontend/appflowy_flutter/lib/workspace/application/export/document_exporter.dart index df46ab97e7..a17b5741bc 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/export/document_exporter.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/export/document_exporter.dart @@ -25,12 +25,13 @@ class DocumentExporter { final ViewPB view; Future> export( - DocumentExportType type, - ) async { + DocumentExportType type, { + String? path, + }) async { final documentService = DocumentService(); final result = await documentService.openDocument(documentId: view.id); return result.fold( - (r) { + (r) async { final document = r.toDocument(); if (document == null) { return FlowyResult.failure( @@ -43,8 +44,14 @@ class DocumentExporter { case DocumentExportType.json: return FlowyResult.success(jsonEncode(document)); case DocumentExportType.markdown: - final markdown = customDocumentToMarkdown(document); - return FlowyResult.success(markdown); + if (path != null) { + await customDocumentToMarkdown(document, path: path); + return FlowyResult.success(''); + } else { + return FlowyResult.success( + await customDocumentToMarkdown(document), + ); + } case DocumentExportType.text: throw UnimplementedError(); case DocumentExportType.html: diff --git a/frontend/appflowy_flutter/lib/workspace/application/favorite/favorite_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/favorite/favorite_bloc.dart index 13322807b3..546b9ba13d 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/favorite/favorite_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/favorite/favorite_bloc.dart @@ -18,6 +18,7 @@ class FavoriteBloc extends Bloc { final _service = FavoriteService(); final _listener = FavoriteListener(); + bool isReordering = false; @override Future close() async { @@ -68,10 +69,7 @@ class FavoriteBloc extends Bloc { await _service.pinFavorite(view); } - await _service.toggleFavorite( - view.id, - !view.isFavorite, - ); + await _service.toggleFavorite(view.id); }, pin: (view) async { await _service.pinFavorite(view); @@ -81,6 +79,23 @@ class FavoriteBloc extends Bloc { await _service.unpinFavorite(view); add(const FavoriteEvent.fetchFavorites()); }, + reorder: (oldIndex, newIndex) async { + /// TODO: this is a workaround to reorder the favorite views + isReordering = true; + final pinnedViews = state.pinnedViews.toList(); + if (oldIndex < newIndex) newIndex -= 1; + final target = pinnedViews.removeAt(oldIndex); + pinnedViews.insert(newIndex, target); + emit(state.copyWith(pinnedViews: pinnedViews)); + for (final view in pinnedViews) { + await _service.toggleFavorite(view.item.id); + await _service.toggleFavorite(view.item.id); + } + if (!isClosed) { + add(const FavoriteEvent.fetchFavorites()); + } + isReordering = false; + }, ); }, ); @@ -90,20 +105,29 @@ class FavoriteBloc extends Bloc { FlowyResult favoriteOrFailed, bool didFavorite, ) { - favoriteOrFailed.fold( - (favorite) => add(const FetchFavorites()), - (error) => Log.error(error), - ); + if (!isReordering) { + favoriteOrFailed.fold( + (favorite) => add(const FetchFavorites()), + (error) => Log.error(error), + ); + } } } @freezed class FavoriteEvent with _$FavoriteEvent { const factory FavoriteEvent.initial() = Initial; + const factory FavoriteEvent.toggle(ViewPB view) = ToggleFavorite; + const factory FavoriteEvent.fetchFavorites() = FetchFavorites; + const factory FavoriteEvent.pin(ViewPB view) = PinFavorite; + const factory FavoriteEvent.unpin(ViewPB view) = UnpinFavorite; + + const factory FavoriteEvent.reorder(int oldIndex, int newIndex) = + ReorderFavorite; } @freezed diff --git a/frontend/appflowy_flutter/lib/workspace/application/favorite/favorite_service.dart b/frontend/appflowy_flutter/lib/workspace/application/favorite/favorite_service.dart index 2ff57bd80f..7f0f844dda 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/favorite/favorite_service.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/favorite/favorite_service.dart @@ -25,10 +25,7 @@ class FavoriteService { }); } - Future> toggleFavorite( - String viewId, - bool favoriteStatus, - ) async { + Future> toggleFavorite(String viewId) async { final id = RepeatedViewIdPB.create()..items.add(viewId); return FolderEventToggleFavorite(id).send(); } diff --git a/frontend/appflowy_flutter/lib/workspace/application/home/home_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/home/home_bloc.dart index 2fb801595c..531e797ff5 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/home/home_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/home/home_bloc.dart @@ -1,15 +1,16 @@ import 'package:appflowy/user/application/user_listener.dart'; +import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart' - show WorkspaceSettingPB; + show WorkspaceLatestPB; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'home_bloc.freezed.dart'; class HomeBloc extends Bloc { - HomeBloc(WorkspaceSettingPB workspaceSetting) + HomeBloc(WorkspaceLatestPB workspaceSetting) : _workspaceListener = FolderListener(), super(HomeState.initial(workspaceSetting)) { _dispatch(workspaceSetting); @@ -23,7 +24,7 @@ class HomeBloc extends Bloc { return super.close(); } - void _dispatch(WorkspaceSettingPB workspaceSetting) { + void _dispatch(WorkspaceLatestPB workspaceSetting) { on( (event, emit) async { await event.map( @@ -35,10 +36,9 @@ class HomeBloc extends Bloc { }); _workspaceListener.start( - onSettingUpdated: (result) { + onLatestUpdated: (result) { result.fold( - (setting) => - add(HomeEvent.didReceiveWorkspaceSetting(setting)), + (latest) => add(HomeEvent.didReceiveWorkspaceSetting(latest)), (r) => Log.error(r), ); }, @@ -48,10 +48,17 @@ class HomeBloc extends Bloc { emit(state.copyWith(isLoading: e.isLoading)); }, didReceiveWorkspaceSetting: (_DidReceiveWorkspaceSetting value) { + // the latest view is shared across all the members of the workspace. + final latestView = value.setting.hasLatestView() ? value.setting.latestView : state.latestView; + if (latestView != null && latestView.isSpace) { + // If the latest view is a space, we don't need to open it. + return; + } + emit( state.copyWith( workspaceSetting: value.setting, @@ -70,7 +77,7 @@ class HomeEvent with _$HomeEvent { const factory HomeEvent.initial() = _Initial; const factory HomeEvent.showLoading(bool isLoading) = _ShowLoading; const factory HomeEvent.didReceiveWorkspaceSetting( - WorkspaceSettingPB setting, + WorkspaceLatestPB setting, ) = _DidReceiveWorkspaceSetting; } @@ -78,11 +85,11 @@ class HomeEvent with _$HomeEvent { class HomeState with _$HomeState { const factory HomeState({ required bool isLoading, - required WorkspaceSettingPB workspaceSetting, + required WorkspaceLatestPB workspaceSetting, ViewPB? latestView, }) = _HomeState; - factory HomeState.initial(WorkspaceSettingPB workspaceSetting) => HomeState( + factory HomeState.initial(WorkspaceLatestPB workspaceSetting) => HomeState( isLoading: false, workspaceSetting: workspaceSetting, ); diff --git a/frontend/appflowy_flutter/lib/workspace/application/home/home_setting_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/home/home_setting_bloc.dart index 657f2592d7..cde67045b9 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/home/home_setting_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/home/home_setting_bloc.dart @@ -2,7 +2,7 @@ import 'package:appflowy/user/application/user_listener.dart'; import 'package:appflowy/workspace/application/edit_panel/edit_context.dart'; import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart' - show WorkspaceSettingPB; + show WorkspaceLatestPB; import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/time/duration.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -12,7 +12,7 @@ part 'home_setting_bloc.freezed.dart'; class HomeSettingBloc extends Bloc { HomeSettingBloc( - WorkspaceSettingPB workspaceSetting, + WorkspaceLatestPB workspaceSetting, AppearanceSettingsCubit appearanceSettingsCubit, double screenWidthPx, ) : _listener = FolderListener(), @@ -124,7 +124,7 @@ class HomeSettingEvent with _$HomeSettingEvent { _ShowEditPanel; const factory HomeSettingEvent.dismissEditPanel() = _DismissEditPanel; const factory HomeSettingEvent.didReceiveWorkspaceSetting( - WorkspaceSettingPB setting, + WorkspaceLatestPB setting, ) = _DidReceiveWorkspaceSetting; const factory HomeSettingEvent.collapseMenu() = _CollapseMenu; const factory HomeSettingEvent.checkScreenSize(double screenWidthPx) = @@ -139,7 +139,7 @@ class HomeSettingEvent with _$HomeSettingEvent { class HomeSettingState with _$HomeSettingState { const factory HomeSettingState({ required EditPanelContext? panelContext, - required WorkspaceSettingPB workspaceSetting, + required WorkspaceLatestPB workspaceSetting, required bool unauthorized, required bool isMenuCollapsed, required bool keepMenuCollapsed, @@ -150,7 +150,7 @@ class HomeSettingState with _$HomeSettingState { }) = _HomeSettingState; factory HomeSettingState.initial( - WorkspaceSettingPB workspaceSetting, + WorkspaceLatestPB workspaceSetting, AppearanceSettingsState appearanceSettingsState, double screenWidthPx, ) { diff --git a/frontend/appflowy_flutter/lib/workspace/application/menu/sidebar_sections_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/menu/sidebar_sections_bloc.dart index 5a20b29c09..d6a6a73578 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/menu/sidebar_sections_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/menu/sidebar_sections_bloc.dart @@ -244,7 +244,10 @@ class SidebarSectionsBloc } void _initial(UserProfilePB userProfile, String workspaceId) { - _workspaceService = WorkspaceService(workspaceId: workspaceId); + _workspaceService = WorkspaceService( + workspaceId: workspaceId, + userId: userProfile.id, + ); _listener = WorkspaceSectionsListener( user: userProfile, diff --git a/frontend/appflowy_flutter/lib/workspace/application/notification/notification_service.dart b/frontend/appflowy_flutter/lib/workspace/application/notification/notification_service.dart index 5418eb2b1c..3f9657c5cf 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/notification/notification_service.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/notification/notification_service.dart @@ -1,8 +1,11 @@ import 'package:flutter/foundation.dart'; - import 'package:local_notifier/local_notifier.dart'; -const _appName = "AppFlowy"; +/// The app name used in the local notification. +/// +/// DO NOT Use i18n here, because the i18n plugin is not ready +/// before the local notification is initialized. +const _localNotifierAppName = 'AppFlowy'; /// Manages Local Notifications /// @@ -13,7 +16,11 @@ const _appName = "AppFlowy"; /// class NotificationService { static Future initialize() async { - await localNotifier.setup(appName: _appName); + await localNotifier.setup( + appName: _localNotifierAppName, + // Don't create a shortcut on Windows, because the setup.exe will create a shortcut + shortcutPolicy: ShortcutPolicy.requireNoCreate, + ); } } diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/ai/download_model_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/ai/download_model_bloc.dart deleted file mode 100644 index 8be68e813e..0000000000 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/ai/download_model_bloc.dart +++ /dev/null @@ -1,167 +0,0 @@ -import 'dart:async'; -import 'dart:ffi'; -import 'dart:isolate'; - -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart'; -import 'package:appflowy_backend/dispatch/dispatch.dart'; -import 'package:appflowy_backend/log.dart'; -import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart'; -import 'package:bloc/bloc.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:fixnum/fixnum.dart'; -part 'download_model_bloc.freezed.dart'; - -class DownloadModelBloc extends Bloc { - DownloadModelBloc(LLMModelPB model) - : super(DownloadModelState.initial(model)) { - on(_handleEvent); - } - - Future _handleEvent( - DownloadModelEvent event, - Emitter emit, - ) async { - await event.when( - started: () async { - final downloadStream = DownloadingStream(); - downloadStream.listen( - onModelPercentage: (name, percent) { - if (!isClosed) { - add( - DownloadModelEvent.updatePercent(name, percent), - ); - } - }, - onPluginPercentage: (percent) { - if (!isClosed) { - add(DownloadModelEvent.updatePercent("AppFlowy Plugin", percent)); - } - }, - onFinish: () { - add(const DownloadModelEvent.downloadFinish()); - }, - onError: (err) { - Log.error(err); - }, - ); - - final payload = - DownloadLLMPB(progressStream: Int64(downloadStream.nativePort)); - final result = await AIEventDownloadLLMResource(payload).send(); - result.fold((_) { - emit( - state.copyWith( - downloadStream: downloadStream, - loadingState: const ChatLoadingState.finish(), - downloadError: null, - ), - ); - }, (err) { - emit( - state.copyWith( - loadingState: ChatLoadingState.finish(error: err), - ), - ); - }); - }, - updatePercent: (String object, double percent) { - emit(state.copyWith(object: object, percent: percent)); - }, - downloadFinish: () { - emit(state.copyWith(isFinish: true)); - }, - ); - } - - @override - Future close() async { - await state.downloadStream?.dispose(); - return super.close(); - } -} - -@freezed -class DownloadModelEvent with _$DownloadModelEvent { - const factory DownloadModelEvent.started() = _Started; - const factory DownloadModelEvent.updatePercent( - String object, - double percent, - ) = _UpdatePercent; - const factory DownloadModelEvent.downloadFinish() = _DownloadFinish; -} - -@freezed -class DownloadModelState with _$DownloadModelState { - const factory DownloadModelState({ - required LLMModelPB model, - DownloadingStream? downloadStream, - String? downloadError, - @Default("") String object, - @Default(0) double percent, - @Default(false) bool isFinish, - String? bigFileDownloadPrompt, - @Default(ChatLoadingState.loading()) ChatLoadingState loadingState, - }) = _DownloadModelState; - - factory DownloadModelState.initial(LLMModelPB model) { - // bigger than 1 GB then show download big file prompt - String? bigFileDownloadPrompt; - if (model.fileSize > 1 * 1024 * 1024 * 1024) { - bigFileDownloadPrompt = - LocaleKeys.settings_aiPage_keys_downloadBigFilePrompt.tr(); - } - return DownloadModelState( - model: model, - bigFileDownloadPrompt: bigFileDownloadPrompt, - ); - } -} - -class DownloadingStream { - DownloadingStream() { - _port.handler = _controller.add; - } - - final RawReceivePort _port = RawReceivePort(); - StreamSubscription? _sub; - final StreamController _controller = StreamController.broadcast(); - int get nativePort => _port.sendPort.nativePort; - - Future dispose() async { - await _sub?.cancel(); - await _controller.close(); - _port.close(); - } - - void listen({ - void Function(String modelName, double percent)? onModelPercentage, - void Function(double percent)? onPluginPercentage, - void Function(String data)? onError, - void Function()? onFinish, - }) { - _sub = _controller.stream.listen((text) { - if (text.contains(':progress:')) { - final progressIndex = text.indexOf(':progress:'); - final modelName = text.substring(0, progressIndex); - final progressValue = text - .substring(progressIndex + 10); // 10 is the length of ":progress:" - final percent = double.tryParse(progressValue); - if (percent != null) { - onModelPercentage?.call(modelName, percent); - } - } else if (text.startsWith('plugin:progress:')) { - final percent = double.tryParse(text.substring(16)); - if (percent != null) { - onPluginPercentage?.call(percent); - } - } else if (text.startsWith('finish')) { - onFinish?.call(); - } else if (text.startsWith('error:')) { - // substring 6 to remove "error:" - onError?.call(text.substring(6)); - } - }); - } -} diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/ai/download_offline_ai_app_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/ai/download_offline_ai_app_bloc.dart deleted file mode 100644 index 829bd2f62a..0000000000 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/ai/download_offline_ai_app_bloc.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'dart:async'; - -import 'package:appflowy_backend/dispatch/dispatch.dart'; -import 'package:bloc/bloc.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:url_launcher/url_launcher.dart' show launchUrl; -part 'download_offline_ai_app_bloc.freezed.dart'; - -class DownloadOfflineAIBloc - extends Bloc { - DownloadOfflineAIBloc() : super(const DownloadOfflineAIState()) { - on(_handleEvent); - } - - Future _handleEvent( - DownloadOfflineAIEvent event, - Emitter emit, - ) async { - await event.when( - started: () async { - final result = await AIEventGetOfflineAIAppLink().send(); - await result.fold( - (app) async { - await launchUrl(Uri.parse(app.link)); - }, - (err) {}, - ); - }, - ); - } -} - -@freezed -class DownloadOfflineAIEvent with _$DownloadOfflineAIEvent { - const factory DownloadOfflineAIEvent.started() = _Started; -} - -@freezed -class DownloadOfflineAIState with _$DownloadOfflineAIState { - const factory DownloadOfflineAIState() = _DownloadOfflineAIState; -} diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/ai/local_ai_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/ai/local_ai_bloc.dart index 3c3d20039d..492c19ab73 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/ai/local_ai_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/settings/ai/local_ai_bloc.dart @@ -1,93 +1,140 @@ import 'dart:async'; import 'package:appflowy_backend/dispatch/dispatch.dart'; +import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart'; -import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_result/appflowy_result.dart'; import 'package:bloc/bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; + +import 'local_llm_listener.dart'; + part 'local_ai_bloc.freezed.dart'; -class LocalAIToggleBloc extends Bloc { - LocalAIToggleBloc() : super(const LocalAIToggleState()) { - on(_handleEvent); +class LocalAiPluginBloc extends Bloc { + LocalAiPluginBloc() : super(const LoadingLocalAiPluginState()) { + on(_handleEvent); + _startListening(); + _getLocalAiState(); + } + + final listener = LocalAIStateListener(); + + @override + Future close() async { + await listener.stop(); + return super.close(); } Future _handleEvent( - LocalAIToggleEvent event, - Emitter emit, + LocalAiPluginEvent event, + Emitter emit, ) async { + if (isClosed) { + return; + } + await event.when( - started: () async { - final result = await AIEventGetLocalAIState().send(); - _handleResult(emit, result); + didReceiveAiState: (aiState) { + emit( + LocalAiPluginState.ready( + isEnabled: aiState.enabled, + version: aiState.pluginVersion, + runningState: aiState.state, + lackOfResource: + aiState.hasLackOfResource() ? aiState.lackOfResource : null, + ), + ); + }, + didReceiveLackOfResources: (resources) { + state.maybeMap( + ready: (readyState) { + emit(readyState.copyWith(lackOfResource: resources)); + }, + orElse: () {}, + ); }, toggle: () async { - emit( - state.copyWith( - pageIndicator: const LocalAIToggleStateIndicator.loading(), - ), - ); - unawaited( - AIEventToggleLocalAI().send().then( - (result) { - if (!isClosed) { - add(LocalAIToggleEvent.handleResult(result)); - } - }, - ), + emit(LocalAiPluginState.loading()); + await AIEventToggleLocalAI().send().fold( + (aiState) { + if (!isClosed) { + add(LocalAiPluginEvent.didReceiveAiState(aiState)); + } + }, + Log.error, ); }, - handleResult: (result) { - _handleResult(emit, result); + restart: () async { + emit(LocalAiPluginState.loading()); + await AIEventRestartLocalAI().send(); }, ); } - void _handleResult( - Emitter emit, - FlowyResult result, - ) { - result.fold( - (localAI) { - emit( - state.copyWith( - pageIndicator: LocalAIToggleStateIndicator.ready(localAI.enabled), - ), - ); + void _startListening() { + listener.start( + stateCallback: (pluginState) { + if (!isClosed) { + add(LocalAiPluginEvent.didReceiveAiState(pluginState)); + } }, - (err) { - emit( - state.copyWith( - pageIndicator: LocalAIToggleStateIndicator.error(err), - ), - ); + resourceCallback: (data) { + if (!isClosed) { + add(LocalAiPluginEvent.didReceiveLackOfResources(data)); + } }, ); } + + void _getLocalAiState() { + AIEventGetLocalAIState().send().fold( + (aiState) { + if (!isClosed) { + add(LocalAiPluginEvent.didReceiveAiState(aiState)); + } + }, + Log.error, + ); + } } @freezed -class LocalAIToggleEvent with _$LocalAIToggleEvent { - const factory LocalAIToggleEvent.started() = _Started; - const factory LocalAIToggleEvent.toggle() = _Toggle; - const factory LocalAIToggleEvent.handleResult( - FlowyResult result, - ) = _HandleResult; +class LocalAiPluginEvent with _$LocalAiPluginEvent { + const factory LocalAiPluginEvent.didReceiveAiState(LocalAIPB aiState) = + _DidReceiveAiState; + const factory LocalAiPluginEvent.didReceiveLackOfResources( + LackOfAIResourcePB resources, + ) = _DidReceiveLackOfResources; + const factory LocalAiPluginEvent.toggle() = _Toggle; + const factory LocalAiPluginEvent.restart() = _Restart; } @freezed -class LocalAIToggleState with _$LocalAIToggleState { - const factory LocalAIToggleState({ - @Default(LocalAIToggleStateIndicator.loading()) - LocalAIToggleStateIndicator pageIndicator, - }) = _LocalAIToggleState; -} +class LocalAiPluginState with _$LocalAiPluginState { + const LocalAiPluginState._(); -@freezed -class LocalAIToggleStateIndicator with _$LocalAIToggleStateIndicator { - // when start downloading the model - const factory LocalAIToggleStateIndicator.error(FlowyError error) = _OnError; - const factory LocalAIToggleStateIndicator.ready(bool isEnabled) = _Ready; - const factory LocalAIToggleStateIndicator.loading() = _Loading; + const factory LocalAiPluginState.ready({ + required bool isEnabled, + required String version, + required RunningStatePB runningState, + required LackOfAIResourcePB? lackOfResource, + }) = ReadyLocalAiPluginState; + + const factory LocalAiPluginState.loading() = LoadingLocalAiPluginState; + + bool get isEnabled { + return maybeWhen( + ready: (isEnabled, _, __, ___) => isEnabled, + orElse: () => false, + ); + } + + bool get showIndicator { + return maybeWhen( + ready: (isEnabled, _, runningState, lackOfResource) => + runningState != RunningStatePB.Running || lackOfResource != null, + orElse: () => false, + ); + } } diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/ai/local_ai_chat_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/ai/local_ai_chat_bloc.dart deleted file mode 100644 index 7f1df258ea..0000000000 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/ai/local_ai_chat_bloc.dart +++ /dev/null @@ -1,287 +0,0 @@ -import 'dart:async'; - -import 'package:appflowy/plugins/ai_chat/application/chat_entity.dart'; -import 'package:appflowy/workspace/application/settings/ai/local_llm_listener.dart'; -import 'package:appflowy_backend/dispatch/dispatch.dart'; -import 'package:appflowy_backend/log.dart'; -import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart'; -import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; -import 'package:appflowy_result/appflowy_result.dart'; -import 'package:bloc/bloc.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'local_ai_chat_bloc.freezed.dart'; - -class LocalAIChatSettingBloc - extends Bloc { - LocalAIChatSettingBloc() - : listener = LocalLLMListener(), - super(const LocalAIChatSettingState()) { - listener.start( - stateCallback: (newState) { - if (!isClosed) { - add(LocalAIChatSettingEvent.updatePluginState(newState)); - } - }, - ); - - on(_handleEvent); - } - - final LocalLLMListener listener; - - /// Handles incoming events and dispatches them to the appropriate handler. - Future _handleEvent( - LocalAIChatSettingEvent event, - Emitter emit, - ) async { - await event.when( - refreshAISetting: _handleStarted, - didLoadModelInfo: (FlowyResult result) { - result.fold( - (modelInfo) { - _fetchCurremtLLMState(); - emit( - state.copyWith( - modelInfo: modelInfo, - models: modelInfo.models, - selectedLLMModel: modelInfo.selectedModel, - aiModelProgress: const AIModelProgress.finish(), - ), - ); - }, - (err) { - emit( - state.copyWith( - aiModelProgress: AIModelProgress.finish(error: err), - ), - ); - }, - ); - }, - selectLLMConfig: (LLMModelPB llmModel) async { - final result = await AIEventUpdateLocalLLM(llmModel).send(); - result.fold( - (llmResource) { - // If all resources are downloaded, show reload plugin - if (llmResource.pendingResources.isNotEmpty) { - emit( - state.copyWith( - selectedLLMModel: llmModel, - progressIndicator: LocalAIProgress.showDownload( - llmResource, - llmModel, - ), - selectLLMState: const ChatLoadingState.finish(), - ), - ); - } else { - emit( - state.copyWith( - selectedLLMModel: llmModel, - selectLLMState: const ChatLoadingState.finish(), - progressIndicator: const LocalAIProgress.checkPluginState(), - ), - ); - } - }, - (err) { - emit( - state.copyWith( - selectLLMState: ChatLoadingState.finish(error: err), - ), - ); - }, - ); - }, - refreshLLMState: (LocalModelResourcePB llmResource) { - if (state.selectedLLMModel == null) { - Log.error( - 'Unexpected null selected config. It should be set already', - ); - return; - } - - // reload plugin if all resources are downloaded - if (llmResource.pendingResources.isEmpty) { - emit( - state.copyWith( - progressIndicator: const LocalAIProgress.checkPluginState(), - ), - ); - } else { - if (state.selectedLLMModel != null) { - // Go to download page if the selected model is downloading - if (llmResource.isDownloading) { - emit( - state.copyWith( - progressIndicator: - LocalAIProgress.startDownloading(state.selectedLLMModel!), - selectLLMState: const ChatLoadingState.finish(), - ), - ); - return; - } else { - emit( - state.copyWith( - progressIndicator: LocalAIProgress.showDownload( - llmResource, - state.selectedLLMModel!, - ), - selectLLMState: const ChatLoadingState.finish(), - ), - ); - } - } - } - }, - startDownloadModel: (LLMModelPB llmModel) { - emit( - state.copyWith( - progressIndicator: LocalAIProgress.startDownloading(llmModel), - selectLLMState: const ChatLoadingState.finish(), - ), - ); - }, - cancelDownload: () async { - final _ = await AIEventCancelDownloadLLMResource().send(); - _fetchCurremtLLMState(); - }, - finishDownload: () async { - emit( - state.copyWith( - progressIndicator: const LocalAIProgress.finishDownload(), - ), - ); - }, - updatePluginState: (LocalAIPluginStatePB pluginState) { - if (pluginState.offlineAiReady) { - AIEventRefreshLocalAIModelInfo().send().then((result) { - if (!isClosed) { - add(LocalAIChatSettingEvent.didLoadModelInfo(result)); - } - }); - - if (pluginState.state == RunningStatePB.Stopped) { - emit( - state.copyWith( - runningState: pluginState.state, - progressIndicator: const LocalAIProgress.checkPluginState(), - ), - ); - } else { - emit( - state.copyWith( - runningState: pluginState.state, - ), - ); - } - } else { - emit( - state.copyWith( - progressIndicator: const LocalAIProgress.startOfflineAIApp(), - ), - ); - } - }, - ); - } - - void _fetchCurremtLLMState() async { - final result = await AIEventGetLocalLLMState().send(); - result.fold( - (llmResource) { - if (!isClosed) { - add(LocalAIChatSettingEvent.refreshLLMState(llmResource)); - } - }, - (err) { - Log.error(err); - }, - ); - } - - /// Handles the event to fetch local AI settings when the application starts. - Future _handleStarted() async { - final result = await AIEventGetLocalAIPluginState().send(); - result.fold( - (pluginState) async { - if (!isClosed) { - add(LocalAIChatSettingEvent.updatePluginState(pluginState)); - if (pluginState.offlineAiReady) { - final result = await AIEventRefreshLocalAIModelInfo().send(); - if (!isClosed) { - add(LocalAIChatSettingEvent.didLoadModelInfo(result)); - } - } - } - }, - (err) => Log.error(err.toString()), - ); - } - - @override - Future close() async { - await listener.stop(); - return super.close(); - } -} - -@freezed -class LocalAIChatSettingEvent with _$LocalAIChatSettingEvent { - const factory LocalAIChatSettingEvent.refreshAISetting() = _RefreshAISetting; - const factory LocalAIChatSettingEvent.didLoadModelInfo( - FlowyResult result, - ) = _ModelInfo; - const factory LocalAIChatSettingEvent.selectLLMConfig(LLMModelPB config) = - _SelectLLMConfig; - - const factory LocalAIChatSettingEvent.refreshLLMState( - LocalModelResourcePB llmResource, - ) = _RefreshLLMResource; - const factory LocalAIChatSettingEvent.startDownloadModel( - LLMModelPB llmModel, - ) = _StartDownloadModel; - - const factory LocalAIChatSettingEvent.cancelDownload() = _CancelDownload; - const factory LocalAIChatSettingEvent.finishDownload() = _FinishDownload; - const factory LocalAIChatSettingEvent.updatePluginState( - LocalAIPluginStatePB pluginState, - ) = _PluginState; -} - -@freezed -class LocalAIChatSettingState with _$LocalAIChatSettingState { - const factory LocalAIChatSettingState({ - LLMModelInfoPB? modelInfo, - LLMModelPB? selectedLLMModel, - LocalAIProgress? progressIndicator, - @Default(AIModelProgress.init()) AIModelProgress aiModelProgress, - @Default(ChatLoadingState.loading()) ChatLoadingState selectLLMState, - @Default([]) List models, - @Default(RunningStatePB.Connecting) RunningStatePB runningState, - }) = _LocalAIChatSettingState; -} - -@freezed -class LocalAIProgress with _$LocalAIProgress { - // when user comes back to the setting page, it will auto detect current llm state - const factory LocalAIProgress.showDownload( - LocalModelResourcePB llmResource, - LLMModelPB llmModel, - ) = _DownloadNeeded; - - // when start downloading the model - const factory LocalAIProgress.startDownloading(LLMModelPB llmModel) = - _Downloading; - const factory LocalAIProgress.finishDownload() = _Finish; - const factory LocalAIProgress.checkPluginState() = _CheckPluginState; - const factory LocalAIProgress.startOfflineAIApp() = _StartOfflineAIApp; -} - -@freezed -class AIModelProgress with _$AIModelProgress { - const factory AIModelProgress.init() = _AIModelProgressInit; - const factory AIModelProgress.loading() = _AIModelDownloading; - const factory AIModelProgress.finish({FlowyError? error}) = _AIModelFinish; -} diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/ai/local_ai_chat_toggle_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/ai/local_ai_chat_toggle_bloc.dart deleted file mode 100644 index 4feac1247a..0000000000 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/ai/local_ai_chat_toggle_bloc.dart +++ /dev/null @@ -1,95 +0,0 @@ -import 'dart:async'; - -import 'package:appflowy_backend/dispatch/dispatch.dart'; -import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart'; -import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; -import 'package:appflowy_result/appflowy_result.dart'; -import 'package:bloc/bloc.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; -part 'local_ai_chat_toggle_bloc.freezed.dart'; - -class LocalAIChatToggleBloc - extends Bloc { - LocalAIChatToggleBloc() : super(const LocalAIChatToggleState()) { - on(_handleEvent); - } - - Future _handleEvent( - LocalAIChatToggleEvent event, - Emitter emit, - ) async { - await event.when( - started: () async { - final result = await AIEventGetLocalAIChatState().send(); - _handleResult(emit, result); - }, - toggle: () async { - emit( - state.copyWith( - pageIndicator: const LocalAIChatToggleStateIndicator.loading(), - ), - ); - unawaited( - AIEventToggleLocalAIChat().send().then( - (result) { - if (!isClosed) { - add(LocalAIChatToggleEvent.handleResult(result)); - } - }, - ), - ); - }, - handleResult: (result) { - _handleResult(emit, result); - }, - ); - } - - void _handleResult( - Emitter emit, - FlowyResult result, - ) { - result.fold( - (localAI) { - emit( - state.copyWith( - pageIndicator: - LocalAIChatToggleStateIndicator.ready(localAI.enabled), - ), - ); - }, - (err) { - emit( - state.copyWith( - pageIndicator: LocalAIChatToggleStateIndicator.error(err), - ), - ); - }, - ); - } -} - -@freezed -class LocalAIChatToggleEvent with _$LocalAIChatToggleEvent { - const factory LocalAIChatToggleEvent.started() = _Started; - const factory LocalAIChatToggleEvent.toggle() = _Toggle; - const factory LocalAIChatToggleEvent.handleResult( - FlowyResult result, - ) = _HandleResult; -} - -@freezed -class LocalAIChatToggleState with _$LocalAIChatToggleState { - const factory LocalAIChatToggleState({ - @Default(LocalAIChatToggleStateIndicator.loading()) - LocalAIChatToggleStateIndicator pageIndicator, - }) = _LocalAIChatToggleState; -} - -@freezed -class LocalAIChatToggleStateIndicator with _$LocalAIChatToggleStateIndicator { - const factory LocalAIChatToggleStateIndicator.error(FlowyError error) = - _OnError; - const factory LocalAIChatToggleStateIndicator.ready(bool isEnabled) = _Ready; - const factory LocalAIChatToggleStateIndicator.loading() = _Loading; -} diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/ai/local_ai_on_boarding_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/ai/local_ai_on_boarding_bloc.dart index 2c1bf34a87..3bb26a182b 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/ai/local_ai_on_boarding_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/settings/ai/local_ai_on_boarding_bloc.dart @@ -26,7 +26,7 @@ class LocalAIOnBoardingBloc _dispatch(); } - Future _onPaymentSuccessful() async { + void _onPaymentSuccessful() { if (isClosed) { return; } diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/ai/local_llm_listener.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/ai/local_llm_listener.dart index a7778d7d99..99c90faeb5 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/ai/local_llm_listener.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/settings/ai/local_llm_listener.dart @@ -8,11 +8,11 @@ import 'package:appflowy_backend/protobuf/flowy-notification/subject.pb.dart'; import 'package:appflowy_backend/rust_stream.dart'; import 'package:appflowy_result/appflowy_result.dart'; -typedef PluginStateCallback = void Function(LocalAIPluginStatePB state); -typedef LocalAIChatCallback = void Function(LocalAIChatPB chatState); +typedef PluginStateCallback = void Function(LocalAIPB state); +typedef PluginResourceCallback = void Function(LackOfAIResourcePB data); -class LocalLLMListener { - LocalLLMListener() { +class LocalAIStateListener { + LocalAIStateListener() { _parser = ChatNotificationParser(id: "appflowy_ai_plugin", callback: _callback); _subscription = RustStreamReceiver.listen( @@ -24,15 +24,14 @@ class LocalLLMListener { ChatNotificationParser? _parser; PluginStateCallback? stateCallback; - LocalAIChatCallback? chatStateCallback; - void Function()? finishStreamingCallback; + PluginResourceCallback? resourceCallback; void start({ PluginStateCallback? stateCallback, - LocalAIChatCallback? chatStateCallback, + PluginResourceCallback? resourceCallback, }) { this.stateCallback = stateCallback; - this.chatStateCallback = chatStateCallback; + this.resourceCallback = resourceCallback; } void _callback( @@ -41,11 +40,11 @@ class LocalLLMListener { ) { result.map((r) { switch (ty) { - case ChatNotification.UpdateChatPluginState: - stateCallback?.call(LocalAIPluginStatePB.fromBuffer(r)); + case ChatNotification.UpdateLocalAIState: + stateCallback?.call(LocalAIPB.fromBuffer(r)); break; - case ChatNotification.UpdateLocalChatAI: - chatStateCallback?.call(LocalAIChatPB.fromBuffer(r)); + case ChatNotification.LocalAIResourceUpdated: + resourceCallback?.call(LackOfAIResourcePB.fromBuffer(r)); break; default: break; diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/ai/ollama_setting_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/ai/ollama_setting_bloc.dart new file mode 100644 index 0000000000..f5c4209028 --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/application/settings/ai/ollama_setting_bloc.dart @@ -0,0 +1,220 @@ +import 'dart:async'; + +import 'package:appflowy_backend/dispatch/dispatch.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart'; +import 'package:appflowy_result/appflowy_result.dart'; +import 'package:bloc/bloc.dart'; +import 'package:collection/collection.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:equatable/equatable.dart'; + +part 'ollama_setting_bloc.freezed.dart'; + +class OllamaSettingBloc extends Bloc { + OllamaSettingBloc() : super(const OllamaSettingState()) { + on(_handleEvent); + } + + Future _handleEvent( + OllamaSettingEvent event, + Emitter emit, + ) async { + event.when( + started: () { + AIEventGetLocalAISetting().send().fold( + (setting) { + if (!isClosed) { + add(OllamaSettingEvent.didLoadSetting(setting)); + } + }, + Log.error, + ); + }, + didLoadSetting: (setting) => _updateSetting(setting, emit), + updateSetting: (setting) => _updateSetting(setting, emit), + onEdit: (content, settingType) { + final updatedSubmittedItems = state.submittedItems + .map( + (item) => item.settingType == settingType + ? SubmittedItem( + content: content, + settingType: item.settingType, + ) + : item, + ) + .toList(); + + // Convert both lists to maps: {settingType: content} + final updatedMap = { + for (final item in updatedSubmittedItems) + item.settingType: item.content, + }; + + final inputMap = { + for (final item in state.inputItems) item.settingType: item.content, + }; + + // Compare maps instead of lists + final isEdited = !const MapEquality() + .equals(updatedMap, inputMap); + + emit( + state.copyWith( + submittedItems: updatedSubmittedItems, + isEdited: isEdited, + ), + ); + }, + submit: () { + final setting = LocalAISettingPB(); + final settingUpdaters = { + SettingType.serverUrl: (value) => setting.serverUrl = value, + SettingType.chatModel: (value) => setting.chatModelName = value, + SettingType.embeddingModel: (value) => + setting.embeddingModelName = value, + }; + + for (final item in state.submittedItems) { + settingUpdaters[item.settingType]?.call(item.content); + } + add(OllamaSettingEvent.updateSetting(setting)); + AIEventUpdateLocalAISetting(setting).send().fold( + (_) => Log.info('AI setting updated successfully'), + (err) => Log.error("update ai setting failed: $err"), + ); + }, + ); + } + + void _updateSetting( + LocalAISettingPB setting, + Emitter emit, + ) { + emit( + state.copyWith( + setting: setting, + inputItems: _createInputItems(setting), + submittedItems: _createSubmittedItems(setting), + isEdited: false, // Reset to false when the setting is loaded/updated. + ), + ); + } + + List _createInputItems(LocalAISettingPB setting) => [ + SettingItem( + content: setting.serverUrl, + hintText: 'http://localhost:11434', + settingType: SettingType.serverUrl, + ), + SettingItem( + content: setting.chatModelName, + hintText: 'llama3.1', + settingType: SettingType.chatModel, + ), + SettingItem( + content: setting.embeddingModelName, + hintText: 'nomic-embed-text', + settingType: SettingType.embeddingModel, + ), + ]; + + List _createSubmittedItems(LocalAISettingPB setting) => [ + SubmittedItem( + content: setting.serverUrl, + settingType: SettingType.serverUrl, + ), + SubmittedItem( + content: setting.chatModelName, + settingType: SettingType.chatModel, + ), + SubmittedItem( + content: setting.embeddingModelName, + settingType: SettingType.embeddingModel, + ), + ]; +} + +// Create an enum for setting type. +enum SettingType { + serverUrl, + chatModel, + embeddingModel; // semicolon needed after the enum values + + String get title { + switch (this) { + case SettingType.serverUrl: + return 'Ollama server url'; + case SettingType.chatModel: + return 'Chat model name'; + case SettingType.embeddingModel: + return 'Embedding model name'; + } + } +} + +class SettingItem extends Equatable { + const SettingItem({ + required this.content, + required this.hintText, + required this.settingType, + }); + final String content; + final String hintText; + final SettingType settingType; + @override + List get props => [content, settingType]; +} + +class SubmittedItem extends Equatable { + const SubmittedItem({ + required this.content, + required this.settingType, + }); + final String content; + final SettingType settingType; + + @override + List get props => [content, settingType]; +} + +@freezed +class OllamaSettingEvent with _$OllamaSettingEvent { + const factory OllamaSettingEvent.started() = _Started; + const factory OllamaSettingEvent.didLoadSetting(LocalAISettingPB setting) = + _DidLoadSetting; + const factory OllamaSettingEvent.updateSetting(LocalAISettingPB setting) = + _UpdateSetting; + const factory OllamaSettingEvent.onEdit( + String content, + SettingType settingType, + ) = _OnEdit; + const factory OllamaSettingEvent.submit() = _OnSubmit; +} + +@freezed +class OllamaSettingState with _$OllamaSettingState { + const factory OllamaSettingState({ + LocalAISettingPB? setting, + @Default([ + SettingItem( + content: 'http://localhost:11434', + hintText: 'http://localhost:11434', + settingType: SettingType.serverUrl, + ), + SettingItem( + content: 'llama3.1', + hintText: 'llama3.1', + settingType: SettingType.chatModel, + ), + SettingItem( + content: 'nomic-embed-text', + hintText: 'nomic-embed-text', + settingType: SettingType.embeddingModel, + ), + ]) + List inputItems, + @Default([]) List submittedItems, + @Default(false) bool isEdited, + }) = _PluginStateState; +} diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/ai/plugin_state_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/ai/plugin_state_bloc.dart deleted file mode 100644 index 4f24309bde..0000000000 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/ai/plugin_state_bloc.dart +++ /dev/null @@ -1,138 +0,0 @@ -import 'dart:async'; - -import 'package:appflowy/core/helpers/url_launcher.dart'; -import 'package:appflowy/workspace/application/settings/ai/local_llm_listener.dart'; -import 'package:appflowy_backend/dispatch/dispatch.dart'; -import 'package:appflowy_backend/log.dart'; -import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart'; -import 'package:bloc/bloc.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:url_launcher/url_launcher.dart' show launchUrl; - -part 'plugin_state_bloc.freezed.dart'; - -class PluginStateBloc extends Bloc { - PluginStateBloc() - : listener = LocalLLMListener(), - super( - const PluginStateState( - action: PluginStateAction.init(), - ), - ) { - listener.start( - stateCallback: (pluginState) { - if (!isClosed) { - add(PluginStateEvent.updateState(pluginState)); - } - }, - ); - - on(_handleEvent); - } - - final LocalLLMListener listener; - - @override - Future close() async { - await listener.stop(); - return super.close(); - } - - Future _handleEvent( - PluginStateEvent event, - Emitter emit, - ) async { - await event.when( - started: () async { - final result = await AIEventGetLocalAIPluginState().send(); - result.fold( - (pluginState) { - if (!isClosed) { - add(PluginStateEvent.updateState(pluginState)); - } - }, - (err) => Log.error(err.toString()), - ); - }, - updateState: (LocalAIPluginStatePB pluginState) { - // if the offline ai is not started, ask user to start it - if (pluginState.offlineAiReady) { - // Chech state of the plugin - switch (pluginState.state) { - case RunningStatePB.Connecting: - emit( - const PluginStateState( - action: PluginStateAction.loadingPlugin(), - ), - ); - case RunningStatePB.Running: - emit(const PluginStateState(action: PluginStateAction.ready())); - break; - default: - emit( - state.copyWith(action: const PluginStateAction.restartPlugin()), - ); - break; - } - } else { - emit( - const PluginStateState( - action: PluginStateAction.startAIOfflineApp(), - ), - ); - } - }, - restartLocalAI: () async { - emit( - const PluginStateState(action: PluginStateAction.loadingPlugin()), - ); - unawaited(AIEventRestartLocalAIChat().send()); - }, - openModelDirectory: () async { - final result = await AIEventGetModelStorageDirectory().send(); - result.fold( - (data) { - afLaunchUri(Uri.file(data.filePath)); - }, - (err) => Log.error(err.toString()), - ); - }, - downloadOfflineAIApp: () async { - final result = await AIEventGetOfflineAIAppLink().send(); - await result.fold( - (app) async { - await launchUrl(Uri.parse(app.link)); - }, - (err) {}, - ); - }, - ); - } -} - -@freezed -class PluginStateEvent with _$PluginStateEvent { - const factory PluginStateEvent.started() = _Started; - const factory PluginStateEvent.updateState(LocalAIPluginStatePB pluginState) = - _UpdatePluginState; - const factory PluginStateEvent.restartLocalAI() = _RestartLocalAI; - const factory PluginStateEvent.openModelDirectory() = - _OpenModelStorageDirectory; - const factory PluginStateEvent.downloadOfflineAIApp() = _DownloadOfflineAIApp; -} - -@freezed -class PluginStateState with _$PluginStateState { - const factory PluginStateState({ - required PluginStateAction action, - }) = _PluginStateState; -} - -@freezed -class PluginStateAction with _$PluginStateAction { - const factory PluginStateAction.init() = _Init; - const factory PluginStateAction.loadingPlugin() = _LoadingPlugin; - const factory PluginStateAction.ready() = _Ready; - const factory PluginStateAction.restartPlugin() = _RestartPlugin; - const factory PluginStateAction.startAIOfflineApp() = _StartAIOfflineApp; -} diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/ai/settings_ai_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/ai/settings_ai_bloc.dart index 91ac63944c..0141283765 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/ai/settings_ai_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/settings/ai/settings_ai_bloc.dart @@ -1,7 +1,8 @@ +import 'package:appflowy/plugins/ai_chat/application/ai_model_switch_listener.dart'; import 'package:appflowy/user/application/user_listener.dart'; -import 'package:appflowy/user/application/user_service.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart'; import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; import 'package:appflowy_result/appflowy_result.dart'; @@ -10,60 +11,55 @@ import 'package:freezed_annotation/freezed_annotation.dart'; part 'settings_ai_bloc.freezed.dart'; +const String aiModelsGlobalActiveModel = "ai_models_global_active_model"; + class SettingsAIBloc extends Bloc { SettingsAIBloc( this.userProfile, this.workspaceId, - AFRolePB? currentWorkspaceMemberRole, ) : _userListener = UserListener(userProfile: userProfile), - _userService = UserBackendService(userId: userProfile.id), + _aiModelSwitchListener = + AIModelSwitchListener(objectId: aiModelsGlobalActiveModel), super( SettingsAIState( userProfile: userProfile, - currentWorkspaceMemberRole: currentWorkspaceMemberRole, ), ) { + _aiModelSwitchListener.start( + onUpdateSelectedModel: (model) { + if (!isClosed) { + _loadModelList(); + } + }, + ); _dispatch(); - - if (currentWorkspaceMemberRole == null) { - _userService.getWorkspaceMember().then((result) { - result.fold( - (member) { - if (!isClosed) { - add(SettingsAIEvent.refreshMember(member)); - } - }, - (err) { - Log.error(err); - }, - ); - }); - } } final UserListener _userListener; final UserProfilePB userProfile; - final UserBackendService _userService; final String workspaceId; + final AIModelSwitchListener _aiModelSwitchListener; @override Future close() async { await _userListener.stop(); + await _aiModelSwitchListener.stop(); return super.close(); } void _dispatch() { - on((event, emit) { - event.when( + on((event, emit) async { + await event.when( started: () { _userListener.start( onProfileUpdated: _onProfileUpdated, onUserWorkspaceSettingUpdated: (settings) { if (!isClosed) { - add(SettingsAIEvent.didLoadAISetting(settings)); + add(SettingsAIEvent.didLoadWorkspaceSetting(settings)); } }, ); + _loadModelList(); _loadUserWorkspaceSetting(); }, didReceiveUserProfile: (userProfile) { @@ -78,10 +74,18 @@ class SettingsAIBloc extends Bloc { !(state.aiSettings?.disableSearchIndexing ?? false), ); }, - selectModel: (AIModelPB model) { - _updateUserWorkspaceSetting(model: model); + selectModel: (AIModelPB model) async { + if (!model.isLocal) { + await _updateUserWorkspaceSetting(model: model.name); + } + await AIEventUpdateSelectedModel( + UpdateSelectedModelPB( + source: aiModelsGlobalActiveModel, + selectedModel: model, + ), + ).send(); }, - didLoadAISetting: (UseAISettingPB settings) { + didLoadWorkspaceSetting: (WorkspaceSettingsPB settings) { emit( state.copyWith( aiSettings: settings, @@ -89,17 +93,21 @@ class SettingsAIBloc extends Bloc { ), ); }, - refreshMember: (member) { - emit(state.copyWith(currentWorkspaceMemberRole: member.role)); + didLoadAvailableModels: (AvailableModelsPB models) { + emit( + state.copyWith( + availableModels: models, + ), + ); }, ); }); } - void _updateUserWorkspaceSetting({ + Future> _updateUserWorkspaceSetting({ bool? disableSearchIndexing, - AIModelPB? model, - }) { + String? model, + }) async { final payload = UpdateUserWorkspaceSettingPB( workspaceId: workspaceId, ); @@ -109,7 +117,12 @@ class SettingsAIBloc extends Bloc { if (model != null) { payload.aiModel = model; } - UserEventUpdateWorkspaceSetting(payload).send(); + final result = await UserEventUpdateWorkspaceSetting(payload).send(); + result.fold( + (ok) => Log.info('Update workspace setting success'), + (err) => Log.error('Update workspace setting failed: $err'), + ); + return result; } void _onProfileUpdated( @@ -120,12 +133,24 @@ class SettingsAIBloc extends Bloc { (err) => Log.error(err), ); + void _loadModelList() { + AIEventGetServerAvailableModels().send().then((result) { + result.fold((models) { + if (!isClosed) { + add(SettingsAIEvent.didLoadAvailableModels(models)); + } + }, (err) { + Log.error(err); + }); + }); + } + void _loadUserWorkspaceSetting() { final payload = UserWorkspaceIdPB(workspaceId: workspaceId); UserEventGetWorkspaceSetting(payload).send().then((result) { result.fold((settings) { if (!isClosed) { - add(SettingsAIEvent.didLoadAISetting(settings)); + add(SettingsAIEvent.didLoadWorkspaceSetting(settings)); } }, (err) { Log.error(err); @@ -137,27 +162,29 @@ class SettingsAIBloc extends Bloc { @freezed class SettingsAIEvent with _$SettingsAIEvent { const factory SettingsAIEvent.started() = _Started; - const factory SettingsAIEvent.didLoadAISetting( - UseAISettingPB settings, + const factory SettingsAIEvent.didLoadWorkspaceSetting( + WorkspaceSettingsPB settings, ) = _DidLoadWorkspaceSetting; const factory SettingsAIEvent.toggleAISearch() = _toggleAISearch; - const factory SettingsAIEvent.refreshMember(WorkspaceMemberPB member) = - _RefreshMember; const factory SettingsAIEvent.selectModel(AIModelPB model) = _SelectAIModel; const factory SettingsAIEvent.didReceiveUserProfile( UserProfilePB newUserProfile, ) = _DidReceiveUserProfile; + + const factory SettingsAIEvent.didLoadAvailableModels( + AvailableModelsPB models, + ) = _DidLoadAvailableModels; } @freezed class SettingsAIState with _$SettingsAIState { const factory SettingsAIState({ required UserProfilePB userProfile, - UseAISettingPB? aiSettings, - AFRolePB? currentWorkspaceMemberRole, + WorkspaceSettingsPB? aiSettings, + AvailableModelsPB? availableModels, @Default(true) bool enableSearchIndexing, }) = _SettingsAIState; } diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/appearance/appearance_cubit.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/appearance/appearance_cubit.dart index a034558110..99b9eaa2c9 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/appearance/appearance_cubit.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/settings/appearance/appearance_cubit.dart @@ -2,11 +2,13 @@ import 'dart:async'; import 'package:appflowy/core/config/kv.dart'; import 'package:appflowy/core/config/kv_keys.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/user/application/user_settings_service.dart'; import 'package:appflowy/util/color_to_hex_string.dart'; import 'package:appflowy/workspace/application/appearance_defaults.dart'; import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-user/date_time.pbenum.dart'; import 'package:appflowy_backend/protobuf/flowy-user/user_setting.pb.dart'; @@ -17,6 +19,7 @@ import 'package:flowy_infra/theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:universal_platform/universal_platform.dart'; part 'appearance_cubit.freezed.dart'; @@ -97,7 +100,19 @@ class AppearanceSettingsCubit extends Cubit { Future setTheme(String themeName) async { _appearanceSettings.theme = themeName; unawaited(_saveAppearanceSettings()); - emit(state.copyWith(appTheme: await AppTheme.fromName(themeName))); + try { + final theme = await AppTheme.fromName(themeName); + emit(state.copyWith(appTheme: theme)); + } catch (e) { + Log.error("Error setting theme: $e"); + if (UniversalPlatform.isMacOS) { + showToastNotification( + message: + LocaleKeys.settings_workspacePage_theme_failedToLoadThemes.tr(), + type: ToastificationType.error, + ); + } + } } /// Reset the current user selected theme back to the default @@ -129,9 +144,8 @@ class AppearanceSettingsCubit extends Cubit { emit(state.copyWith(layoutDirection: layoutDirection)); } - void setTextDirection(AppFlowyTextDirection? textDirection) { - _appearanceSettings.textDirection = - textDirection?.toTextDirectionPB() ?? TextDirectionPB.FALLBACK; + void setTextDirection(AppFlowyTextDirection textDirection) { + _appearanceSettings.textDirection = textDirection.toTextDirectionPB(); _saveAppearanceSettings(); emit(state.copyWith(textDirection: textDirection)); } @@ -310,7 +324,6 @@ ThemeModePB _themeModeToPB(ThemeMode themeMode) { case ThemeMode.dark: return ThemeModePB.Dark; case ThemeMode.system: - default: return ThemeModePB.System; } } @@ -336,7 +349,7 @@ enum AppFlowyTextDirection { rtl, auto; - static AppFlowyTextDirection? fromTextDirectionPB( + static AppFlowyTextDirection fromTextDirectionPB( TextDirectionPB? textDirectionPB, ) { switch (textDirectionPB) { @@ -347,7 +360,7 @@ enum AppFlowyTextDirection { case TextDirectionPB.AUTO: return AppFlowyTextDirection.auto; default: - return null; + return AppFlowyTextDirection.ltr; } } @@ -359,8 +372,6 @@ enum AppFlowyTextDirection { return TextDirectionPB.RTL; case AppFlowyTextDirection.auto: return TextDirectionPB.AUTO; - default: - return TextDirectionPB.FALLBACK; } } } @@ -374,7 +385,7 @@ class AppearanceSettingsState with _$AppearanceSettingsState { required ThemeMode themeMode, required String font, required LayoutDirection layoutDirection, - required AppFlowyTextDirection? textDirection, + required AppFlowyTextDirection textDirection, required bool enableRtlToolbarItems, required Locale locale, required bool isMenuCollapsed, @@ -424,6 +435,7 @@ class AppearanceSettingsState with _$AppearanceSettingsState { } ThemeData get lightTheme => _getThemeData(Brightness.light); + ThemeData get darkTheme => _getThemeData(Brightness.dark); ThemeData _getThemeData(Brightness brightness) { diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/appearance/desktop_appearance.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/appearance/desktop_appearance.dart index e1ea22a6eb..c1e539cf58 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/appearance/desktop_appearance.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/settings/appearance/desktop_appearance.dart @@ -14,9 +14,10 @@ class DesktopAppearance extends BaseAppearance { ) { assert(codeFontFamily.isNotEmpty); - final theme = brightness == Brightness.light - ? appTheme.lightTheme - : appTheme.darkTheme; + fontFamily = fontFamily.isEmpty ? defaultFontFamily : fontFamily; + + final isLight = brightness == Brightness.light; + final theme = isLight ? appTheme.lightTheme : appTheme.darkTheme; final colorScheme = ColorScheme( brightness: brightness, @@ -150,6 +151,7 @@ class DesktopAppearance extends BaseAppearance { scrollbarColor: theme.scrollbarColor, scrollbarHoverColor: theme.scrollbarHoverColor, lightIconColor: theme.lightIconColor, + toolbarHoverColor: theme.toolbarHoverColor, ), ], ); diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/appearance/mobile_appearance.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/appearance/mobile_appearance.dart index 63235ba217..46eddd53ab 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/appearance/mobile_appearance.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/settings/appearance/mobile_appearance.dart @@ -28,13 +28,12 @@ class MobileAppearance extends BaseAppearance { fontWeight: FontWeight.w400, ); + final isLight = brightness == Brightness.light; final codeFontStyle = getFontStyle(fontFamily: codeFontFamily); - final theme = brightness == Brightness.light - ? appTheme.lightTheme - : appTheme.darkTheme; + final theme = isLight ? appTheme.lightTheme : appTheme.darkTheme; - final colorTheme = brightness == Brightness.light + final colorTheme = isLight ? ColorScheme( brightness: brightness, primary: _primaryColor, @@ -49,7 +48,7 @@ class MobileAppearance extends BaseAppearance { error: const Color(0xffFB006D), onError: const Color(0xffFB006D), outline: const Color(0xffe3e3e3), - outlineVariant: const Color(0xffCBD5E0).withOpacity(0.24), + outlineVariant: const Color(0xffCBD5E0).withValues(alpha: 0.24), //Snack bar surface: Colors.white, onSurface: _onSurfaceColor, // text/body color @@ -71,13 +70,9 @@ class MobileAppearance extends BaseAppearance { onSurface: const Color(0xffC5C6C7), // text/body color surfaceContainerHighest: theme.sidebarBg, ); - final hintColor = brightness == Brightness.light - ? const Color(0x991F2329) - : _hintColorInDarkMode; - final onBackground = - brightness == Brightness.light ? _onBackgroundColor : Colors.white; - final background = - brightness == Brightness.light ? Colors.white : const Color(0xff121212); + final hintColor = isLight ? const Color(0x991F2329) : _hintColorInDarkMode; + final onBackground = isLight ? _onBackgroundColor : Colors.white; + final background = isLight ? Colors.white : const Color(0xff121212); return ThemeData( useMaterial3: false, @@ -280,6 +275,7 @@ class MobileAppearance extends BaseAppearance { scrollbarColor: theme.scrollbarColor, scrollbarHoverColor: theme.scrollbarHoverColor, lightIconColor: theme.lightIconColor, + toolbarHoverColor: theme.toolbarHoverColor, ), ToolbarColorExtension.fromBrightness(brightness), ], diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/appflowy_cloud_urls_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/appflowy_cloud_urls_bloc.dart index 998e6d632f..5652904180 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/appflowy_cloud_urls_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/settings/appflowy_cloud_urls_bloc.dart @@ -24,6 +24,15 @@ class AppFlowyCloudURLsBloc ), ); }, + updateBaseWebDomain: (url) { + emit( + state.copyWith( + updatedBaseWebDomain: url, + urlError: null, + showRestartHint: url.isNotEmpty, + ), + ); + }, confirmUpdate: () async { if (state.updatedServerUrl.isEmpty) { emit( @@ -35,13 +44,27 @@ class AppFlowyCloudURLsBloc ), ); } else { - validateUrl(state.updatedServerUrl).fold( + bool isSuccess = false; + + await validateUrl(state.updatedServerUrl).fold( (url) async { await useSelfHostedAppFlowyCloudWithURL(url); - add(const AppFlowyCloudURLsEvent.didSaveConfig()); + isSuccess = true; }, - (err) => emit(state.copyWith(urlError: err)), + (err) async => emit(state.copyWith(urlError: err)), ); + + await validateUrl(state.updatedBaseWebDomain).fold( + (url) async { + await useBaseWebDomain(url); + isSuccess = true; + }, + (err) async => emit(state.copyWith(urlError: err)), + ); + + if (isSuccess) { + add(const AppFlowyCloudURLsEvent.didSaveConfig()); + } } }, didSaveConfig: () { @@ -62,6 +85,8 @@ class AppFlowyCloudURLsEvent with _$AppFlowyCloudURLsEvent { const factory AppFlowyCloudURLsEvent.initial() = _Initial; const factory AppFlowyCloudURLsEvent.updateServerUrl(String text) = _ServerUrl; + const factory AppFlowyCloudURLsEvent.updateBaseWebDomain(String text) = + _UpdateBaseWebDomain; const factory AppFlowyCloudURLsEvent.confirmUpdate() = _UpdateConfig; const factory AppFlowyCloudURLsEvent.didSaveConfig() = _DidSaveConfig; } @@ -71,6 +96,7 @@ class AppFlowyCloudURLsState with _$AppFlowyCloudURLsState { const factory AppFlowyCloudURLsState({ required AppFlowyCloudConfiguration config, required String updatedServerUrl, + required String updatedBaseWebDomain, required String? urlError, required bool restartApp, required bool showRestartHint, @@ -81,6 +107,8 @@ class AppFlowyCloudURLsState with _$AppFlowyCloudURLsState { urlError: null, updatedServerUrl: getIt().appflowyCloudConfig.base_url, + updatedBaseWebDomain: + getIt().appflowyCloudConfig.base_web_domain, showRestartHint: getIt() .appflowyCloudConfig .base_url diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/billing/settings_billing_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/billing/settings_billing_bloc.dart index ab324df87f..df880891e9 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/billing/settings_billing_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/settings/billing/settings_billing_bloc.dart @@ -29,7 +29,7 @@ class SettingsBillingBloc required Int64 userId, }) : super(const _Initial()) { _userService = UserBackendService(userId: userId); - _service = WorkspaceService(workspaceId: workspaceId); + _service = WorkspaceService(workspaceId: workspaceId, userId: userId); _successListenable = getIt(); _successListenable.addListener(_onPaymentSuccessful); diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/date_time/date_format_ext.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/date_time/date_format_ext.dart index 8d2a65c029..76fec2ecfc 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/date_time/date_format_ext.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/settings/date_time/date_format_ext.dart @@ -8,7 +8,14 @@ const _friendlyFmt = 'MMM dd, y'; const _dmyFmt = 'dd/MM/y'; extension DateFormatter on UserDateFormatPB { - DateFormat get toFormat => DateFormat(_toFormat[this] ?? _friendlyFmt); + DateFormat get toFormat { + try { + return DateFormat(_toFormat[this] ?? _friendlyFmt); + } catch (_) { + // fallback to en-US + return DateFormat(_toFormat[this] ?? _friendlyFmt, 'en-US'); + } + } String formatDate( DateTime date, diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/plan/settings_plan_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/plan/settings_plan_bloc.dart index f7512a834e..26975b00ff 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/plan/settings_plan_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/settings/plan/settings_plan_bloc.dart @@ -23,7 +23,10 @@ class SettingsPlanBloc extends Bloc { required this.workspaceId, required Int64 userId, }) : super(const _Initial()) { - _service = WorkspaceService(workspaceId: workspaceId); + _service = WorkspaceService( + workspaceId: workspaceId, + userId: userId, + ); _userService = UserBackendService(userId: userId); _successListenable = getIt(); _successListenable.addListener(_onPaymentSuccessful); @@ -43,7 +46,7 @@ class SettingsPlanBloc extends Bloc { FlowyError? error; final usageResult = snapshots.first.fold( - (s) => s as WorkspaceUsagePB, + (s) => s as WorkspaceUsagePB?, (f) { error = f; return null; @@ -148,7 +151,7 @@ class SettingsPlanBloc extends Bloc { usage.freeze(); final newUsage = usage.rebuild((value) { - if (!newInfo.hasAIMax && !newInfo.hasAIOnDevice) { + if (!newInfo.hasAIMax) { value.aiResponsesUnlimited = false; } diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/settings_dialog_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/settings_dialog_bloc.dart index 0578d9808b..83588f0079 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/settings_dialog_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/settings/settings_dialog_bloc.dart @@ -2,7 +2,6 @@ import 'package:appflowy/user/application/user_listener.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; -import 'package:appflowy_backend/protobuf/flowy-user/auth.pbenum.dart'; import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-user/workspace.pb.dart'; import 'package:appflowy_result/appflowy_result.dart'; @@ -91,8 +90,8 @@ class SettingsDialogBloc AFRolePB? currentWorkspaceMemberRole, ]) async { if ([ - AuthenticatorPB.Local, - ].contains(userProfile.authenticator)) { + AuthTypePB.Local, + ].contains(userProfile.workspaceAuthType)) { return false; } diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/shortcuts/settings_shortcuts_service.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/shortcuts/settings_shortcuts_service.dart index 25b51c9a81..af95d5af5a 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/settings/shortcuts/settings_shortcuts_service.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/settings/shortcuts/settings_shortcuts_service.dart @@ -53,8 +53,8 @@ class SettingsShortcutService { } } - /// Extracts shortcuts from the saved json file. The shortcuts in the saved file consist of [List]. - /// This list needs to be converted to List. This function is intended to facilitate the same. + // Extracts shortcuts from the saved json file. The shortcuts in the saved file consist of [List]. + // This list needs to be converted to List. This function is intended to facilitate the same. List getShortcutsFromJson(String savedJson) { final shortcuts = EditorShortcuts.fromJson(jsonDecode(savedJson)); return shortcuts.commandShortcuts; diff --git a/frontend/appflowy_flutter/lib/workspace/application/sidebar/billing/sidebar_plan_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/sidebar/billing/sidebar_plan_bloc.dart index 1737494530..56d6ae8cc8 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/sidebar/billing/sidebar_plan_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/sidebar/billing/sidebar_plan_bloc.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/workspace/application/settings/file_storage/file_storage_listener.dart'; import 'package:appflowy/workspace/application/subscription_success_listenable/subscription_success_listenable.dart'; +import 'package:appflowy/workspace/application/workspace/workspace_service.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart'; import 'package:appflowy_backend/dispatch/error.dart'; import 'package:appflowy_backend/log.dart'; @@ -182,17 +183,24 @@ class SidebarPlanBloc extends Bloc { ); } - void _checkWorkspaceUsage() { - if (state.workspaceId != null) { - final payload = UserWorkspaceIdPB(workspaceId: state.workspaceId!); - UserEventGetWorkspaceUsage(payload).send().then((result) { - result.onSuccess( - (usage) { - add(SidebarPlanEvent.updateWorkspaceUsage(usage)); - }, - ); - }); + Future _checkWorkspaceUsage() async { + if (state.workspaceId == null || state.userProfile == null) { + return; } + + await WorkspaceService( + workspaceId: state.workspaceId!, + userId: state.userProfile!.id, + ).getWorkspaceUsage().then((result) { + result.fold( + (usage) { + if (!isClosed && usage != null) { + add(SidebarPlanEvent.updateWorkspaceUsage(usage)); + } + }, + (error) => Log.error("Failed to get workspace usage: $error"), + ); + }); } } diff --git a/frontend/appflowy_flutter/lib/workspace/application/sidebar/space/space_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/sidebar/space/space_bloc.dart index 0f1dc4e987..9de0c582cd 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/sidebar/space/space_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/sidebar/space/space_bloc.dart @@ -76,12 +76,6 @@ class SpaceBloc extends Bloc { final (spaces, publicViews, privateViews) = await _getSpaces(); - final shouldShowUpgradeDialog = await this.shouldShowUpgradeDialog( - spaces: spaces, - publicViews: publicViews, - privateViews: privateViews, - ); - final currentSpace = await _getLastOpenedSpace(spaces); final isExpanded = await _getSpaceExpandStatus(currentSpace); emit( @@ -89,17 +83,11 @@ class SpaceBloc extends Bloc { spaces: spaces, currentSpace: currentSpace, isExpanded: isExpanded, - shouldShowUpgradeDialog: shouldShowUpgradeDialog, + shouldShowUpgradeDialog: false, isInitialized: true, ), ); - if (shouldShowUpgradeDialog && !integrationMode().isTest) { - if (!isClosed) { - add(const SpaceEvent.migrate()); - } - } - if (openFirstPage) { if (currentSpace != null) { if (!isClosed) { @@ -331,16 +319,6 @@ class SpaceBloc extends Bloc { final (spaces, _, _) = await _getSpaces(); final currentSpace = await _getLastOpenedSpace(spaces); - Log.info( - 'receive space update, current space: ${currentSpace?.name}(${currentSpace?.id})', - ); - - for (var i = 0; i < spaces.length; i++) { - Log.info( - 'receive space update[$i]: ${spaces[i].name}(${spaces[i].id})', - ); - } - emit( state.copyWith( spaces: spaces, @@ -496,8 +474,10 @@ class SpaceBloc extends Bloc { } void _initial(UserProfilePB userProfile, String workspaceId) { - Log.info('initial(or reset) space bloc: $workspaceId, ${userProfile.id}'); - _workspaceService = WorkspaceService(workspaceId: workspaceId); + _workspaceService = WorkspaceService( + workspaceId: workspaceId, + userId: userProfile.id, + ); this.userProfile = userProfile; this.workspaceId = workspaceId; @@ -507,7 +487,6 @@ class SpaceBloc extends Bloc { workspaceId: workspaceId, )..start( sectionChanged: (result) async { - Log.info('did receive section views changed'); if (isClosed) { return; } diff --git a/frontend/appflowy_flutter/lib/workspace/application/tabs/tabs_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/tabs/tabs_bloc.dart index a437c9747c..f27539cddd 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/tabs/tabs_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/tabs/tabs_bloc.dart @@ -1,10 +1,19 @@ +import 'dart:convert'; + +import 'package:appflowy/core/config/kv.dart'; +import 'package:appflowy/core/config/kv_keys.dart'; +import 'package:appflowy/plugins/blank/blank.dart'; import 'package:appflowy/plugins/util.dart'; import 'package:appflowy/startup/plugin/plugin.dart'; import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/util/expand_views.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; +import 'package:appflowy/workspace/application/view/view_service.dart'; import 'package:appflowy/workspace/presentation/home/home_stack.dart'; import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart'; +import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +import 'package:appflowy_result/appflowy_result.dart'; import 'package:bloc/bloc.dart'; import 'package:collection/collection.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -57,15 +66,24 @@ class TabsBloc extends Bloc { _setLatestOpenView(); }, openTab: (Plugin plugin, ViewPB view) { - state.currentPageManager.hideSecondaryPlugin(); + state.currentPageManager + ..hideSecondaryPlugin() + ..setSecondaryPlugin(BlankPagePlugin()); emit(state.openView(plugin)); _setLatestOpenView(view); }, openPlugin: (Plugin plugin, ViewPB? view, bool setLatest) { - state.currentPageManager.hideSecondaryPlugin(); + state.currentPageManager + ..hideSecondaryPlugin() + ..setSecondaryPlugin(BlankPagePlugin()); emit(state.openPlugin(plugin: plugin, setLatest: setLatest)); if (setLatest) { + // the space view should be filtered out. + if (view != null && view.isSpace) { + return; + } _setLatestOpenView(view); + if (view != null) _expandAncestors(view); } }, closeOtherTabs: (String pluginId) { @@ -154,7 +172,9 @@ class TabsBloc extends Bloc { } }, openSecondaryPlugin: (plugin, view) { - state.currentPageManager.setSecondaryPlugin(plugin); + state.currentPageManager + ..setSecondaryPlugin(plugin) + ..showSecondaryPlugin(); }, closeSecondaryPlugin: () { final pageManager = state.currentPageManager; @@ -162,12 +182,9 @@ class TabsBloc extends Bloc { }, expandSecondaryPlugin: () { final pageManager = state.currentPageManager; - pageManager.setPlugin( - pageManager.secondaryNotifier.plugin, - true, - false, - ); - pageManager.hideSecondaryPlugin(); + pageManager + ..hideSecondaryPlugin() + ..expandSecondaryPlugin(); _setLatestOpenView(); }, switchWorkspace: (workspaceId) { @@ -205,6 +222,32 @@ class TabsBloc extends Bloc { } } + Future _expandAncestors(ViewPB view) async { + final viewExpanderRegistry = getIt.get(); + if (viewExpanderRegistry.isViewExpanded(view.parentViewId)) return; + final value = await getIt().get(KVKeys.expandedViews); + try { + final Map expandedViews = value == null ? {} : jsonDecode(value); + final List ancestors = + await ViewBackendService.getViewAncestors(view.id) + .fold((s) => s.items.map((e) => e.id).toList(), (f) => []); + ViewExpander? viewExpander; + for (final id in ancestors) { + expandedViews[id] = true; + final expander = viewExpanderRegistry.getExpander(id); + if (expander == null) continue; + if (!expander.isViewExpanded && viewExpander == null) { + viewExpander = expander; + } + } + await getIt() + .set(KVKeys.expandedViews, jsonEncode(expandedViews)); + viewExpander?.expand(); + } catch (e) { + Log.error('expandAncestors error', e); + } + } + int _adjustCurrentIndex({ required int currentIndex, required int tabIndex, @@ -242,26 +285,37 @@ class TabsBloc extends Bloc { @freezed class TabsEvent with _$TabsEvent { const factory TabsEvent.moveTab() = _MoveTab; + const factory TabsEvent.closeTab(String pluginId) = _CloseTab; + const factory TabsEvent.closeOtherTabs(String pluginId) = _CloseOtherTabs; + const factory TabsEvent.closeCurrentTab() = _CloseCurrentTab; + const factory TabsEvent.selectTab(int index) = _SelectTab; + const factory TabsEvent.togglePin(String pluginId) = _TogglePin; + const factory TabsEvent.openTab({ required Plugin plugin, required ViewPB view, }) = _OpenTab; + const factory TabsEvent.openPlugin({ required Plugin plugin, ViewPB? view, @Default(true) bool setLatest, }) = _OpenPlugin; + const factory TabsEvent.openSecondaryPlugin({ required Plugin plugin, ViewPB? view, }) = _OpenSecondaryPlugin; + const factory TabsEvent.closeSecondaryPlugin() = _CloseSecondaryPlugin; + const factory TabsEvent.expandSecondaryPlugin() = _ExpandSecondaryPlugin; + const factory TabsEvent.switchWorkspace(String workspaceId) = _SwitchWorkspace; } @@ -274,8 +328,11 @@ class TabsState { final int currentIndex; final List _pageManagers; + int get pages => _pageManagers.length; + PageManager get currentPageManager => _pageManagers[currentIndex]; + List get pageManagers => _pageManagers; bool get isAllPinned => _pageManagers.every((pm) => pm.isPinned); diff --git a/frontend/appflowy_flutter/lib/workspace/application/user/settings_user_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/user/settings_user_bloc.dart index 56faa9f8d8..2f62177661 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/user/settings_user_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/user/settings_user_bloc.dart @@ -54,17 +54,27 @@ class SettingsUserViewBloc extends Bloc { ); }); }, - removeUserIcon: () { - // Empty Icon URL = No icon - _userService.updateUserProfile(iconUrl: "").then((result) { + updateUserEmail: (String email) { + _userService.updateUserProfile(email: email).then((result) { result.fold( (l) => null, (err) => Log.error(err), ); }); }, - updateUserEmail: (String email) { - _userService.updateUserProfile(email: email).then((result) { + updateUserPassword: (String oldPassword, String newPassword) { + _userService + .updateUserProfile(password: newPassword) + .then((result) { + result.fold( + (l) => null, + (err) => Log.error(err), + ); + }); + }, + removeUserIcon: () { + // Empty Icon URL = No icon + _userService.updateUserProfile(iconUrl: "").then((result) { result.fold( (l) => null, (err) => Log.error(err), @@ -104,10 +114,19 @@ class SettingsUserViewBloc extends Bloc { @freezed class SettingsUserEvent with _$SettingsUserEvent { const factory SettingsUserEvent.initial() = _Initial; - const factory SettingsUserEvent.updateUserName(String name) = _UpdateUserName; - const factory SettingsUserEvent.updateUserEmail(String email) = _UpdateEmail; - const factory SettingsUserEvent.updateUserIcon({required String iconUrl}) = - _UpdateUserIcon; + const factory SettingsUserEvent.updateUserName({ + required String name, + }) = _UpdateUserName; + const factory SettingsUserEvent.updateUserEmail({ + required String email, + }) = _UpdateEmail; + const factory SettingsUserEvent.updateUserIcon({ + required String iconUrl, + }) = _UpdateUserIcon; + const factory SettingsUserEvent.updateUserPassword({ + required String oldPassword, + required String newPassword, + }) = _UpdateUserPassword; const factory SettingsUserEvent.removeUserIcon() = _RemoveUserIcon; const factory SettingsUserEvent.didReceiveUserProfile( UserProfilePB newUserProfile, diff --git a/frontend/appflowy_flutter/lib/workspace/application/user/user_workspace_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/user/user_workspace_bloc.dart index 7f32a86d1c..d14f258462 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/user/user_workspace_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/user/user_workspace_bloc.dart @@ -44,7 +44,7 @@ class UserWorkspaceBloc extends Bloc { final currentWorkspace = result.$1; final workspaces = result.$2; final isCollabWorkspaceOn = - userProfile.authenticator == AuthenticatorPB.AppFlowyCloud && + userProfile.userAuthType == AuthTypePB.Server && FeatureFlag.collaborativeWorkspace.isOn; Log.info( 'init workspace, current workspace: ${currentWorkspace?.workspaceId}, ' @@ -52,7 +52,10 @@ class UserWorkspaceBloc extends Bloc { ); if (currentWorkspace != null && result.$3 == true) { Log.info('init open workspace: ${currentWorkspace.workspaceId}'); - await _userService.openWorkspace(currentWorkspace.workspaceId); + await _userService.openWorkspace( + currentWorkspace.workspaceId, + currentWorkspace.workspaceAuthType, + ); } emit( @@ -86,10 +89,15 @@ class UserWorkspaceBloc extends Bloc { Log.info( 'fetch workspaces: try to open workspace: ${currentWorkspace.workspaceId}', ); - add(OpenWorkspace(currentWorkspace.workspaceId)); + add( + OpenWorkspace( + currentWorkspace.workspaceId, + currentWorkspace.workspaceAuthType, + ), + ); } }, - createWorkspace: (name) async { + createWorkspace: (name, authType) async { emit( state.copyWith( actionResult: const UserWorkspaceActionResult( @@ -99,7 +107,10 @@ class UserWorkspaceBloc extends Bloc { ), ), ); - final result = await _userService.createUserWorkspace(name); + final result = await _userService.createUserWorkspace( + name, + authType, + ); final workspaces = result.fold( (s) => [...state.workspaces, s], (e) => state.workspaces, @@ -118,7 +129,12 @@ class UserWorkspaceBloc extends Bloc { result ..onSuccess((s) { Log.info('create workspace success: $s'); - add(OpenWorkspace(s.workspaceId)); + add( + OpenWorkspace( + s.workspaceId, + s.workspaceAuthType, + ), + ); }) ..onFailure((f) { Log.error('create workspace error: $f'); @@ -171,7 +187,12 @@ class UserWorkspaceBloc extends Bloc { Log.info('delete workspace success: $workspaceId'); // if the current workspace is deleted, open the first workspace if (state.currentWorkspace?.workspaceId == workspaceId) { - add(OpenWorkspace(workspaces.first.workspaceId)); + add( + OpenWorkspace( + workspaces.first.workspaceId, + workspaces.first.workspaceAuthType, + ), + ); } }) ..onFailure((f) { @@ -179,7 +200,12 @@ class UserWorkspaceBloc extends Bloc { // if the workspace is deleted but return an error, we need to // open the first workspace if (!containsDeletedWorkspace) { - add(OpenWorkspace(workspaces.first.workspaceId)); + add( + OpenWorkspace( + workspaces.first.workspaceId, + workspaces.first.workspaceAuthType, + ), + ); } }); emit( @@ -193,7 +219,7 @@ class UserWorkspaceBloc extends Bloc { ), ); }, - openWorkspace: (workspaceId) async { + openWorkspace: (workspaceId, authType) async { emit( state.copyWith( actionResult: const UserWorkspaceActionResult( @@ -203,7 +229,10 @@ class UserWorkspaceBloc extends Bloc { ), ), ); - final result = await _userService.openWorkspace(workspaceId); + final result = await _userService.openWorkspace( + workspaceId, + authType, + ); final currentWorkspace = result.fold( (s) => state.workspaces.firstWhereOrNull( (e) => e.workspaceId == workspaceId, @@ -337,7 +366,12 @@ class UserWorkspaceBloc extends Bloc { Log.info('leave workspace success: $workspaceId'); // if leaving the current workspace, open the first workspace if (state.currentWorkspace?.workspaceId == workspaceId) { - add(OpenWorkspace(workspaces.first.workspaceId)); + add( + OpenWorkspace( + workspaces.first.workspaceId, + workspaces.first.workspaceAuthType, + ), + ); } }) ..onFailure((f) { @@ -441,12 +475,16 @@ class UserWorkspaceBloc extends Bloc { class UserWorkspaceEvent with _$UserWorkspaceEvent { const factory UserWorkspaceEvent.initial() = Initial; const factory UserWorkspaceEvent.fetchWorkspaces() = FetchWorkspaces; - const factory UserWorkspaceEvent.createWorkspace(String name) = - CreateWorkspace; + const factory UserWorkspaceEvent.createWorkspace( + String name, + AuthTypePB authType, + ) = CreateWorkspace; const factory UserWorkspaceEvent.deleteWorkspace(String workspaceId) = DeleteWorkspace; - const factory UserWorkspaceEvent.openWorkspace(String workspaceId) = - OpenWorkspace; + const factory UserWorkspaceEvent.openWorkspace( + String workspaceId, + AuthTypePB authType, + ) = OpenWorkspace; const factory UserWorkspaceEvent.renameWorkspace( String workspaceId, String name, diff --git a/frontend/appflowy_flutter/lib/workspace/application/view/view_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/view/view_bloc.dart index 30a32bbd2d..553317f4e4 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/view/view_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/view/view_bloc.dart @@ -3,8 +3,10 @@ import 'dart:convert'; import 'package:appflowy/core/config/kv.dart'; import 'package:appflowy/core/config/kv_keys.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/util/expand_views.dart'; import 'package:appflowy/workspace/application/favorite/favorite_listener.dart'; import 'package:appflowy/workspace/application/recent/cached_recent_service.dart'; import 'package:appflowy/workspace/application/view/view_listener.dart'; @@ -15,6 +17,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_result/appflowy_result.dart'; import 'package:collection/collection.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:protobuf/protobuf.dart'; @@ -22,12 +25,22 @@ import 'package:protobuf/protobuf.dart'; part 'view_bloc.freezed.dart'; class ViewBloc extends Bloc { - ViewBloc({required this.view, this.shouldLoadChildViews = true}) - : viewBackendSvc = ViewBackendService(), + ViewBloc({ + required this.view, + this.shouldLoadChildViews = true, + this.engagedInExpanding = false, + }) : viewBackendSvc = ViewBackendService(), listener = ViewListener(viewId: view.id), favoriteListener = FavoriteListener(), super(ViewState.init(view)) { _dispatch(); + if (engagedInExpanding) { + expander = ViewExpander( + () => state.isExpanded, + () => add(const ViewEvent.setIsExpanded(true)), + ); + getIt().register(view.id, expander); + } } final ViewPB view; @@ -35,11 +48,16 @@ class ViewBloc extends Bloc { final ViewListener listener; final FavoriteListener favoriteListener; final bool shouldLoadChildViews; + final bool engagedInExpanding; + late ViewExpander expander; @override Future close() async { await listener.stop(); await favoriteListener.stop(); + if (engagedInExpanding) { + getIt().unregister(view.id, expander); + } return super.close(); } @@ -174,6 +192,7 @@ class ViewBloc extends Bloc { openAfterDuplicate: true, syncAfterDuplicate: true, includeChildren: true, + suffix: ' (${LocaleKeys.menuAppHeader_pageNameSuffix.tr()})', ); emit( result.fold( @@ -243,7 +262,7 @@ class ViewBloc extends Bloc { }, updateIcon: (value) async { await ViewBackendService.updateViewIcon( - viewId: view.id, + view: view, viewIcon: view.icon.toEmojiIconData(), ); }, @@ -385,7 +404,7 @@ class ViewBloc extends Bloc { }); } - if (update.updateChildViews.isNotEmpty) { + if (update.updateChildViews.isNotEmpty && update.parentViewId.isNotEmpty) { final view = await ViewBackendService.getView(update.parentViewId); final childViews = view.fold((l) => l.childViews, (r) => []); bool isSameOrder = true; diff --git a/frontend/appflowy_flutter/lib/workspace/application/view/view_ext.dart b/frontend/appflowy_flutter/lib/workspace/application/view/view_ext.dart index 375d8f4534..fcd991fcf9 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/view/view_ext.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/view/view_ext.dart @@ -70,6 +70,13 @@ extension ViewExtension on ViewPB { String get nameOrDefault => name.isEmpty ? LocaleKeys.menuAppHeader_defaultNewPageName.tr() : name; + bool get isDocument => pluginType == PluginType.document; + bool get isDatabase => [ + PluginType.grid, + PluginType.board, + PluginType.calendar, + ].contains(pluginType); + Widget defaultIcon({Size? size}) => FlowySvg( switch (layout) { ViewLayoutPB.Board => FlowySvgs.icon_board_s, @@ -327,6 +334,7 @@ extension ViewLayoutExtension on ViewLayoutPB { bool get shrinkWrappable => switch (this) { ViewLayoutPB.Grid => true, + ViewLayoutPB.Board => true, _ => false, }; diff --git a/frontend/appflowy_flutter/lib/workspace/application/view/view_lock_status_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/view/view_lock_status_bloc.dart new file mode 100644 index 0000000000..251131d849 --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/application/view/view_lock_status_bloc.dart @@ -0,0 +1,123 @@ +import 'dart:async'; + +import 'package:appflowy/workspace/application/view/view_listener.dart'; +import 'package:appflowy/workspace/application/view/view_service.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:protobuf/protobuf.dart'; + +part 'view_lock_status_bloc.freezed.dart'; + +class ViewLockStatusBloc + extends Bloc { + ViewLockStatusBloc({ + required this.view, + }) : viewBackendSvc = ViewBackendService(), + listener = ViewListener(viewId: view.id), + super(ViewLockStatusState.init(view)) { + on( + (event, emit) async { + await event.when( + initial: () async { + listener.start( + onViewUpdated: (view) async { + add(ViewLockStatusEvent.updateLockStatus(view.isLocked)); + }, + ); + + final result = await ViewBackendService.getView(view.id); + final latestView = result.fold( + (view) => view, + (_) => view, + ); + emit( + state.copyWith( + view: latestView, + isLocked: latestView.isLocked, + isLoadingLockStatus: false, + ), + ); + }, + lock: () async { + final result = await ViewBackendService.lockView(view.id); + final isLocked = result.fold( + (_) => true, + (_) => false, + ); + add( + ViewLockStatusEvent.updateLockStatus( + isLocked, + ), + ); + }, + unlock: () async { + final result = await ViewBackendService.unlockView(view.id); + final isLocked = result.fold( + (_) => false, + (_) => true, + ); + add( + ViewLockStatusEvent.updateLockStatus( + isLocked, + lockCounter: state.lockCounter + 1, + ), + ); + }, + updateLockStatus: (isLocked, lockCounter) { + state.view.freeze(); + final updatedView = state.view.rebuild( + (update) => update.isLocked = isLocked, + ); + emit( + state.copyWith( + view: updatedView, + isLocked: isLocked, + lockCounter: lockCounter ?? state.lockCounter, + ), + ); + }, + ); + }, + ); + } + + final ViewPB view; + final ViewBackendService viewBackendSvc; + final ViewListener listener; + + @override + Future close() async { + await listener.stop(); + + return super.close(); + } +} + +@freezed +class ViewLockStatusEvent with _$ViewLockStatusEvent { + const factory ViewLockStatusEvent.initial() = Initial; + const factory ViewLockStatusEvent.lock() = Lock; + const factory ViewLockStatusEvent.unlock() = Unlock; + const factory ViewLockStatusEvent.updateLockStatus( + bool isLocked, { + int? lockCounter, + }) = UpdateLockStatus; +} + +@freezed +class ViewLockStatusState with _$ViewLockStatusState { + const factory ViewLockStatusState({ + required ViewPB view, + required bool isLocked, + required int lockCounter, + @Default(true) bool isLoadingLockStatus, + }) = _ViewLockStatusState; + + factory ViewLockStatusState.init(ViewPB view) => ViewLockStatusState( + view: view, + isLocked: false, + lockCounter: 0, + ); +} diff --git a/frontend/appflowy_flutter/lib/workspace/application/view/view_service.dart b/frontend/appflowy_flutter/lib/workspace/application/view/view_service.dart index d20aed8c1b..ea74f1861e 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/view/view_service.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/view/view_service.dart @@ -4,6 +4,8 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/me import 'package:appflowy/plugins/trash/application/trash_service.dart'; import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_backend/protobuf/flowy-document/entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; import 'package:appflowy_result/appflowy_result.dart'; @@ -109,6 +111,12 @@ class ViewBackendService { static Future, FlowyError>> getChildViews({ required String viewId, }) { + if (viewId.isEmpty) { + return Future.value( + FlowyResult, FlowyError>.success([]), + ); + } + final payload = ViewIdPB.create()..value = viewId; return FolderEventGetView(payload).send().then((result) { @@ -190,14 +198,25 @@ class ViewBackendService { } static Future> updateViewIcon({ - required String viewId, + required ViewPB view, required EmojiIconData viewIcon, }) { + final viewId = view.id; + final oldIcon = view.icon.toEmojiIconData(); final icon = viewIcon.toViewIcon(); final payload = UpdateViewIconPayloadPB.create() ..viewId = viewId ..icon = icon; - + if (oldIcon.type == FlowyIconType.custom && + viewIcon.emoji != oldIcon.emoji) { + DocumentEventDeleteFile( + DeleteFilePB(url: oldIcon.emoji), + ).send().onFailure((e) { + Log.error( + 'updateViewIcon error while deleting :${oldIcon.emoji}, error: ${e.msg}, ${e.code}', + ); + }); + } return FolderEventUpdateViewIcon(payload).send(); } @@ -249,6 +268,9 @@ class ViewBackendService { static Future> getView( String viewId, ) async { + if (viewId.isEmpty) { + Log.error('ViewId is empty'); + } final payload = ViewIdPB.create()..value = viewId; return FolderEventGetView(payload).send(); } @@ -392,4 +414,14 @@ class ViewBackendService { return (publishedPages.isNotEmpty, publishedPages); } + + static Future> lockView(String viewId) async { + final payload = ViewIdPB()..value = viewId; + return FolderEventLockView(payload).send(); + } + + static Future> unlockView(String viewId) async { + final payload = ViewIdPB()..value = viewId; + return FolderEventUnlockView(payload).send(); + } } diff --git a/frontend/appflowy_flutter/lib/workspace/application/view_info/view_info_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/view_info/view_info_bloc.dart index b24c4f6a9a..27540622ba 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/view_info/view_info_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/view_info/view_info_bloc.dart @@ -1,4 +1,5 @@ import 'package:appflowy/util/int64_extension.dart'; +import 'package:appflowy/util/string_extension.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:bloc/bloc.dart'; @@ -11,7 +12,12 @@ class ViewInfoBloc extends Bloc { on((event, emit) { event.when( started: () { - emit(state.copyWith(createdAt: view.createTime.toDateTime())); + emit( + state.copyWith( + createdAt: view.createTime.toDateTime(), + titleCounters: view.name.getCounter(), + ), + ); }, unregisterEditorState: () { _clearWordCountService(); @@ -36,6 +42,13 @@ class ViewInfoBloc extends Bloc { ), ); }, + titleChanged: (s) { + emit( + state.copyWith( + titleCounters: s.getCounter(), + ), + ); + }, ); }); } @@ -71,17 +84,21 @@ class ViewInfoEvent with _$ViewInfoEvent { }) = _RegisterEditorState; const factory ViewInfoEvent.wordCountChanged() = _WordCountChanged; + + const factory ViewInfoEvent.titleChanged(String title) = _TitleChanged; } @freezed class ViewInfoState with _$ViewInfoState { const factory ViewInfoState({ required Counters? documentCounters, + required Counters? titleCounters, required DateTime? createdAt, }) = _ViewInfoState; factory ViewInfoState.initial() => const ViewInfoState( documentCounters: null, + titleCounters: null, createdAt: null, ); } diff --git a/frontend/appflowy_flutter/lib/workspace/application/workspace/workspace_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/workspace/workspace_bloc.dart index d8d5db45b4..ed06f16c8f 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/workspace/workspace_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/workspace/workspace_bloc.dart @@ -2,6 +2,7 @@ import 'package:appflowy/user/application/user_service.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-user/workspace.pb.dart'; import 'package:appflowy_result/appflowy_result.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; @@ -64,7 +65,8 @@ class WorkspaceBloc extends Bloc { String desc, Emitter emit, ) async { - final result = await userService.createWorkspace(name, desc); + final result = + await userService.createUserWorkspace(name, AuthTypePB.Server); emit( result.fold( (workspace) { diff --git a/frontend/appflowy_flutter/lib/workspace/application/workspace/workspace_service.dart b/frontend/appflowy_flutter/lib/workspace/application/workspace/workspace_service.dart index b958e5cd30..ae6220994e 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/workspace/workspace_service.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/workspace/workspace_service.dart @@ -1,15 +1,18 @@ import 'dart:async'; +import 'package:appflowy/shared/af_role_pb_extension.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-user/workspace.pb.dart'; import 'package:appflowy_result/appflowy_result.dart'; +import 'package:fixnum/fixnum.dart' as fixnum; class WorkspaceService { - WorkspaceService({required this.workspaceId}); + WorkspaceService({required this.workspaceId, required this.userId}); final String workspaceId; + final fixnum.Int64 userId; Future> createView({ required String name, @@ -82,7 +85,18 @@ class WorkspaceService { return FolderEventMoveView(payload).send(); } - Future> getWorkspaceUsage() { + Future> getWorkspaceUsage() async { + final request = WorkspaceMemberIdPB()..uid = userId; + final result = await UserEventGetMemberInfo(request).send(); + final isOwner = result.fold( + (member) => member.role.isOwner, + (_) => false, + ); + + if (!isOwner) { + return FlowyResult.success(null); + } + final payload = UserWorkspaceIdPB(workspaceId: workspaceId); return UserEventGetWorkspaceUsage(payload).send(); } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/command_palette.dart b/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/command_palette.dart index 8eb7765c3a..648712bd15 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/command_palette.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/command_palette.dart @@ -4,7 +4,6 @@ import 'package:appflowy/workspace/presentation/command_palette/widgets/recent_v import 'package:appflowy/workspace/presentation/command_palette/widgets/search_field.dart'; import 'package:appflowy/workspace/presentation/command_palette/widgets/search_results_list.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -135,13 +134,17 @@ class CommandPaletteModal extends StatelessWidget { builder: (context, state) => FlowyDialog( alignment: Alignment.topCenter, insetPadding: const EdgeInsets.only(top: 100), - constraints: const BoxConstraints(maxHeight: 420, maxWidth: 510), + constraints: const BoxConstraints( + maxHeight: 600, + maxWidth: 800, + minHeight: 600, + ), expandHeight: false, child: shortcutBuilder( + // Change mainAxisSize to max so Expanded works correctly. Column( - mainAxisSize: MainAxisSize.min, children: [ - SearchField(query: state.query, isLoading: state.isLoading), + SearchField(query: state.query, isLoading: state.searching), if (state.query?.isEmpty ?? true) ...[ const Divider(height: 0), Flexible( @@ -150,23 +153,26 @@ class CommandPaletteModal extends StatelessWidget { ), ), ], - if (state.results.isNotEmpty && + if (state.combinedResponseItems.isNotEmpty && (state.query?.isNotEmpty ?? false)) ...[ const Divider(height: 0), Flexible( - child: SearchResultsList( + child: SearchResultList( trash: state.trash, - results: state.results, + resultItems: state.combinedResponseItems.values.toList(), + resultSummaries: state.resultSummaries, ), ), - ] else if ((state.query?.isNotEmpty ?? false) && - !state.isLoading) ...[ - const _NoResultsHint(), + ] + // When there are no results and the query is not empty and not loading, + // show the no results message, centered in the available space. + else if ((state.query?.isNotEmpty ?? false) && + !state.searching) ...[ + const Divider(height: 0), + Expanded( + child: const _NoResultsHint(), + ), ], - _CommandPaletteFooter( - shouldShow: state.results.isNotEmpty && - (state.query?.isNotEmpty ?? false), - ), ], ), ), @@ -175,57 +181,16 @@ class CommandPaletteModal extends StatelessWidget { } } +/// Updated _NoResultsHint now centers its content. class _NoResultsHint extends StatelessWidget { const _NoResultsHint(); @override Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Divider(height: 0), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: FlowyText.regular( - LocaleKeys.commandPalette_noResultsHint.tr(), - textAlign: TextAlign.left, - ), - ), - ], - ); - } -} - -class _CommandPaletteFooter extends StatelessWidget { - const _CommandPaletteFooter({required this.shouldShow}); - - final bool shouldShow; - - @override - Widget build(BuildContext context) { - if (!shouldShow) { - return const SizedBox.shrink(); - } - - return Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4), - decoration: BoxDecoration( - color: Theme.of(context).cardColor, - border: Border(top: BorderSide(color: Theme.of(context).dividerColor)), - ), - child: Row( - children: [ - Container( - padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 1), - decoration: BoxDecoration( - color: AFThemeExtension.of(context).lightGreyHover, - borderRadius: BorderRadius.circular(4), - ), - child: const FlowyText.semibold('TAB', fontSize: 10), - ), - const HSpace(4), - FlowyText(LocaleKeys.commandPalette_navigateHint.tr(), fontSize: 11), - ], + return Center( + child: FlowyText.regular( + LocaleKeys.commandPalette_noResultsHint.tr(), + textAlign: TextAlign.center, ), ); } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/widgets/recent_view_tile.dart b/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/widgets/recent_view_tile.dart deleted file mode 100644 index c4bc5aa845..0000000000 --- a/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/widgets/recent_view_tile.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'package:appflowy/workspace/application/view/view_ext.dart'; -import 'package:flutter/material.dart'; - -import 'package:appflowy/startup/startup.dart'; -import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart'; -import 'package:appflowy/workspace/application/action_navigation/navigation_action.dart'; -import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; -import 'package:flowy_infra_ui/style_widget/text.dart'; -import 'package:flowy_infra_ui/widget/spacing.dart'; - -class RecentViewTile extends StatelessWidget { - const RecentViewTile({ - super.key, - required this.icon, - required this.view, - required this.onSelected, - }); - - final Widget icon; - final ViewPB view; - final VoidCallback onSelected; - - @override - Widget build(BuildContext context) { - return ListTile( - dense: true, - title: Row( - children: [ - icon, - const HSpace(6), - FlowyText(view.nameOrDefault), - ], - ), - focusColor: Theme.of(context).colorScheme.primary.withOpacity(0.1), - hoverColor: Theme.of(context).colorScheme.primary.withOpacity(0.1), - onTap: () { - onSelected(); - - getIt().add( - ActionNavigationEvent.performAction( - action: NavigationAction(objectId: view.id), - ), - ); - }, - ); - } -} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/widgets/recent_views_list.dart b/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/widgets/recent_views_list.dart index b0f87005d2..3bc160ee81 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/widgets/recent_views_list.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/widgets/recent_views_list.dart @@ -4,7 +4,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emo import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; import 'package:appflowy/workspace/application/recent/recent_views_bloc.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; -import 'package:appflowy/workspace/presentation/command_palette/widgets/recent_view_tile.dart'; +import 'package:appflowy/workspace/presentation/command_palette/widgets/search_recent_view_cell.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; @@ -52,7 +52,7 @@ class RecentViewsList extends StatelessWidget { ) : FlowySvg(view.iconData, size: const Size.square(20)); - return RecentViewTile( + return SearchRecentViewCell( icon: SizedBox(width: 24, child: icon), view: view, onSelected: onSelected, diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/widgets/search_field.dart b/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/widgets/search_field.dart index c18024a909..1586ab0a7e 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/widgets/search_field.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/widgets/search_field.dart @@ -7,7 +7,6 @@ import 'package:appflowy/workspace/application/command_palette/command_palette_b import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/theme_extension.dart'; -import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/style_widget/text_field.dart'; import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; @@ -25,28 +24,31 @@ class SearchField extends StatefulWidget { class _SearchFieldState extends State { late final FocusNode focusNode; - late final controller = TextEditingController(text: widget.query); + late final TextEditingController controller; @override void initState() { super.initState(); - focusNode = FocusNode( - onKeyEvent: (node, event) { - if (node.hasFocus && - event is KeyDownEvent && - event.logicalKey == LogicalKeyboardKey.arrowDown) { - node.nextFocus(); - return KeyEventResult.handled; - } - - return KeyEventResult.ignored; - }, - ); + controller = TextEditingController(text: widget.query); + focusNode = FocusNode(onKeyEvent: _handleKeyEvent); focusNode.requestFocus(); - controller.selection = TextSelection( - baseOffset: 0, - extentOffset: controller.text.length, - ); + // Update the text selection after the first frame + WidgetsBinding.instance.addPostFrameCallback((_) { + controller.selection = TextSelection( + baseOffset: 0, + extentOffset: controller.text.length, + ); + }); + } + + KeyEventResult _handleKeyEvent(FocusNode node, KeyEvent event) { + if (node.hasFocus && + event is KeyDownEvent && + event.logicalKey == LogicalKeyboardKey.arrowDown) { + node.nextFocus(); + return KeyEventResult.handled; + } + return KeyEventResult.ignored; } @override @@ -56,21 +58,83 @@ class _SearchFieldState extends State { super.dispose(); } + Widget _buildSuffixIcon(BuildContext context) { + return ValueListenableBuilder( + valueListenable: controller, + builder: (context, value, _) { + final hasText = value.text.trim().isNotEmpty; + final clearIcon = Container( + padding: const EdgeInsets.all(1), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AFThemeExtension.of(context).lightGreyHover, + ), + child: const FlowySvg( + FlowySvgs.close_s, + size: Size.square(16), + ), + ); + return AnimatedOpacity( + opacity: hasText ? 1.0 : 0.0, + duration: const Duration(milliseconds: 200), + child: hasText + ? FlowyTooltip( + message: LocaleKeys.commandPalette_clearSearchTooltip.tr(), + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: _clearSearch, + child: clearIcon, + ), + ), + ) + : clearIcon, + ); + }, + ); + } + @override Widget build(BuildContext context) { + // Cache theme and text styles + final theme = Theme.of(context); + final textStyle = theme.textTheme.bodySmall?.copyWith(fontSize: 14); + final hintStyle = theme.textTheme.bodySmall?.copyWith( + fontSize: 14, + color: theme.hintColor, + ); + + // Choose the leading icon based on loading state + final Widget leadingIcon = widget.isLoading + ? FlowyTooltip( + message: LocaleKeys.commandPalette_loadingTooltip.tr(), + child: const SizedBox( + width: 20, + height: 20, + child: Padding( + padding: EdgeInsets.all(3.0), + child: CircularProgressIndicator(strokeWidth: 2.0), + ), + ), + ) + : SizedBox( + width: 20, + height: 20, + child: FlowySvg( + FlowySvgs.search_m, + color: theme.hintColor, + ), + ); + return Row( children: [ const HSpace(12), - FlowySvg( - FlowySvgs.search_m, - color: Theme.of(context).hintColor, - ), + leadingIcon, Expanded( child: FlowyTextField( focusNode: focusNode, controller: controller, - textStyle: - Theme.of(context).textTheme.bodySmall?.copyWith(fontSize: 14), + textStyle: textStyle, decoration: InputDecoration( constraints: const BoxConstraints(maxHeight: 48), contentPadding: const EdgeInsets.symmetric(horizontal: 12), @@ -80,72 +144,14 @@ class _SearchFieldState extends State { ), isDense: false, hintText: LocaleKeys.commandPalette_placeholder.tr(), - hintStyle: Theme.of(context).textTheme.bodySmall?.copyWith( - fontSize: 14, - color: Theme.of(context).hintColor, - ), - errorStyle: Theme.of(context) - .textTheme - .bodySmall! - .copyWith(color: Theme.of(context).colorScheme.error), + hintStyle: hintStyle, + errorStyle: theme.textTheme.bodySmall! + .copyWith(color: theme.colorScheme.error), suffix: Row( mainAxisSize: MainAxisSize.min, children: [ - AnimatedOpacity( - opacity: controller.text.trim().isNotEmpty ? 1 : 0, - duration: const Duration(milliseconds: 200), - child: Builder( - builder: (context) { - final icon = Container( - padding: const EdgeInsets.all(1), - decoration: BoxDecoration( - shape: BoxShape.circle, - color: AFThemeExtension.of(context).lightGreyHover, - ), - child: const FlowySvg( - FlowySvgs.close_s, - size: Size.square(16), - ), - ); - if (controller.text.isEmpty) { - return icon; - } - - return FlowyTooltip( - message: - LocaleKeys.commandPalette_clearSearchTooltip.tr(), - child: MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: controller.text.trim().isNotEmpty - ? _clearSearch - : null, - child: icon, - ), - ), - ); - }, - ), - ), + _buildSuffixIcon(context), const HSpace(8), - FlowyTooltip( - message: LocaleKeys.commandPalette_betaTooltip.tr(), - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 5, - vertical: 2, - ), - decoration: BoxDecoration( - color: AFThemeExtension.of(context).lightGreyHover, - borderRadius: BorderRadius.circular(4), - ), - child: FlowyText.semibold( - LocaleKeys.commandPalette_betaLabel.tr(), - fontSize: 11, - lineHeight: 1.2, - ), - ), - ), ], ), counterText: "", @@ -155,9 +161,7 @@ class _SearchFieldState extends State { ), errorBorder: OutlineInputBorder( borderRadius: Corners.s8Border, - borderSide: BorderSide( - color: Theme.of(context).colorScheme.error, - ), + borderSide: BorderSide(color: theme.colorScheme.error), ), ), onChanged: (value) => context @@ -165,17 +169,6 @@ class _SearchFieldState extends State { .add(CommandPaletteEvent.searchChanged(search: value)), ), ), - if (widget.isLoading) ...[ - FlowyTooltip( - message: LocaleKeys.commandPalette_loadingTooltip.tr(), - child: const SizedBox( - width: 16, - height: 16, - child: CircularProgressIndicator(strokeWidth: 2.5), - ), - ), - const HSpace(12), - ], ], ); } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/widgets/search_recent_view_cell.dart b/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/widgets/search_recent_view_cell.dart new file mode 100644 index 0000000000..a803f9b44c --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/widgets/search_recent_view_cell.dart @@ -0,0 +1,51 @@ +import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart'; +import 'package:appflowy/workspace/application/action_navigation/navigation_action.dart'; +import 'package:appflowy/workspace/application/view/view_ext.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flowy_infra_ui/widget/spacing.dart'; +import 'package:flutter/material.dart'; + +class SearchRecentViewCell extends StatelessWidget { + const SearchRecentViewCell({ + super.key, + required this.icon, + required this.view, + required this.onSelected, + }); + + final Widget icon; + final ViewPB view; + final VoidCallback onSelected; + + @override + Widget build(BuildContext context) { + return ListTile( + dense: true, + title: Row( + children: [ + icon, + const HSpace(6), + Expanded( + child: FlowyText( + view.nameOrDefault, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + focusColor: Theme.of(context).colorScheme.primary.withValues(alpha: 0.1), + hoverColor: Theme.of(context).colorScheme.primary.withValues(alpha: 0.1), + onTap: () { + onSelected(); + + getIt().add( + ActionNavigationEvent.performAction( + action: NavigationAction(objectId: view.id), + ), + ); + }, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/widgets/search_result_cell.dart b/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/widgets/search_result_cell.dart new file mode 100644 index 0000000000..2485da4a69 --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/widgets/search_result_cell.dart @@ -0,0 +1,235 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/util/string_extension.dart'; +import 'package:appflowy/workspace/application/command_palette/command_palette_bloc.dart'; +import 'package:appflowy/workspace/application/command_palette/search_result_ext.dart'; +import 'package:appflowy/workspace/application/command_palette/search_result_list_bloc.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/theme_extension.dart'; +import 'package:flowy_infra_ui/style_widget/hover.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flowy_infra_ui/widget/spacing.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class SearchResultCell extends StatefulWidget { + const SearchResultCell({ + super.key, + required this.item, + this.isTrashed = false, + this.isHovered = false, + }); + + final SearchResultItem item; + final bool isTrashed; + final bool isHovered; + + @override + State createState() => _SearchResultCellState(); +} + +class _SearchResultCellState extends State { + bool _hasFocus = false; + final focusNode = FocusNode(); + + @override + void dispose() { + focusNode.dispose(); + super.dispose(); + } + + /// Helper to handle the selection action. + void _handleSelection() { + context.read().add( + SearchResultListEvent.openPage(pageId: widget.item.id), + ); + } + + /// Helper to clean up preview text. + String _cleanPreview(String preview) { + return preview.replaceAll('\n', ' ').trim(); + } + + @override + Widget build(BuildContext context) { + final title = widget.item.displayName.orDefault( + LocaleKeys.menuAppHeader_defaultNewPageName.tr(), + ); + final icon = widget.item.icon.getIcon(); + final cleanedPreview = _cleanPreview(widget.item.content); + final hasPreview = cleanedPreview.isNotEmpty; + final trashHintText = + widget.isTrashed ? LocaleKeys.commandPalette_fromTrashHint.tr() : null; + + // Build the tile content based on preview availability. + Widget tileContent; + if (hasPreview) { + tileContent = Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + if (icon != null) ...[ + SizedBox(width: 24, child: icon), + const HSpace(6), + ], + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (widget.isTrashed) + FlowyText( + trashHintText!, + color: AFThemeExtension.of(context) + .textColor + .withAlpha(175), + fontSize: 10, + ), + FlowyText( + title, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ], + ), + const VSpace(4), + _DocumentPreview(preview: cleanedPreview), + ], + ); + } else { + tileContent = Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (icon != null) ...[ + SizedBox(width: 24, child: icon), + const HSpace(6), + ], + Flexible( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.isTrashed) + FlowyText( + trashHintText!, + color: + AFThemeExtension.of(context).textColor.withAlpha(175), + fontSize: 10, + ), + FlowyText( + title, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ], + ); + } + + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: _handleSelection, + child: Focus( + focusNode: focusNode, + onKeyEvent: (node, event) { + if (event is! KeyDownEvent) return KeyEventResult.ignored; + if (event.logicalKey == LogicalKeyboardKey.enter) { + _handleSelection(); + return KeyEventResult.handled; + } + return KeyEventResult.ignored; + }, + onFocusChange: (hasFocus) { + setState(() { + context.read().add( + SearchResultListEvent.onHoverResult( + item: widget.item, + userHovered: true, + ), + ); + _hasFocus = hasFocus; + }); + }, + child: FlowyHover( + onHover: (value) { + context.read().add( + SearchResultListEvent.onHoverResult( + item: widget.item, + userHovered: true, + ), + ); + }, + isSelected: () => _hasFocus || widget.isHovered, + style: HoverStyle( + borderRadius: BorderRadius.circular(8), + hoverColor: + Theme.of(context).colorScheme.primary.withValues(alpha: 0.1), + foregroundColorOnHover: AFThemeExtension.of(context).textColor, + ), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 6), + child: ConstrainedBox( + constraints: const BoxConstraints(minHeight: 30), + child: tileContent, + ), + ), + ), + ), + ); + } +} + +class _DocumentPreview extends StatelessWidget { + const _DocumentPreview({required this.preview}); + + final String preview; + + @override + Widget build(BuildContext context) { + // Combine the horizontal padding for clarity: + return Padding( + padding: const EdgeInsets.fromLTRB(30, 0, 16, 0), + child: FlowyText.regular( + preview, + color: Theme.of(context).hintColor, + fontSize: 12, + maxLines: 3, + overflow: TextOverflow.ellipsis, + ), + ); + } +} + +class SearchResultPreview extends StatelessWidget { + const SearchResultPreview({ + super.key, + required this.data, + }); + + final SearchResultItem data; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Opacity( + opacity: 0.5, + child: FlowyText( + LocaleKeys.commandPalette_pagePreview.tr(), + fontSize: 12, + ), + ), + const VSpace(6), + Expanded( + child: FlowyText( + data.content, + maxLines: 30, + ), + ), + ], + ); + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/widgets/search_result_tile.dart b/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/widgets/search_result_tile.dart deleted file mode 100644 index 0edcde3664..0000000000 --- a/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/widgets/search_result_tile.dart +++ /dev/null @@ -1,154 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/startup/startup.dart'; -import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart'; -import 'package:appflowy/workspace/application/action_navigation/navigation_action.dart'; -import 'package:appflowy/workspace/application/command_palette/search_result_ext.dart'; -import 'package:appflowy_backend/protobuf/flowy-search/result.pb.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/theme_extension.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'; - -class SearchResultTile extends StatefulWidget { - const SearchResultTile({ - super.key, - required this.result, - required this.onSelected, - this.isTrashed = false, - }); - - final SearchResultPB result; - final VoidCallback onSelected; - final bool isTrashed; - - @override - State createState() => _SearchResultTileState(); -} - -class _SearchResultTileState extends State { - bool _hasFocus = false; - - final focusNode = FocusNode(); - - @override - void dispose() { - focusNode.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final icon = widget.result.getIcon(); - final cleanedPreview = _cleanPreview(widget.result.preview); - - return GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () { - widget.onSelected(); - - getIt().add( - ActionNavigationEvent.performAction( - action: NavigationAction(objectId: widget.result.viewId), - ), - ); - }, - child: Focus( - onKeyEvent: (node, event) { - if (event is! KeyDownEvent) { - return KeyEventResult.ignored; - } - - if (event.logicalKey == LogicalKeyboardKey.enter) { - widget.onSelected(); - - getIt().add( - ActionNavigationEvent.performAction( - action: NavigationAction(objectId: widget.result.viewId), - ), - ); - return KeyEventResult.handled; - } - - return KeyEventResult.ignored; - }, - onFocusChange: (hasFocus) => setState(() => _hasFocus = hasFocus), - child: FlowyHover( - isSelected: () => _hasFocus, - style: HoverStyle( - hoverColor: Theme.of(context).colorScheme.primary.withOpacity(0.1), - foregroundColorOnHover: AFThemeExtension.of(context).textColor, - ), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - if (icon != null) ...[ - SizedBox(width: 24, child: icon), - const HSpace(6), - ], - Flexible( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (widget.isTrashed) ...[ - FlowyText( - LocaleKeys.commandPalette_fromTrashHint.tr(), - color: AFThemeExtension.of(context) - .textColor - .withAlpha(175), - fontSize: 10, - ), - ], - FlowyText( - widget.result.data, - overflow: TextOverflow.ellipsis, - ), - ], - ), - ), - ], - ), - if (cleanedPreview.isNotEmpty) ...[ - const VSpace(4), - _DocumentPreview(preview: cleanedPreview), - ], - ], - ), - ), - ), - ), - ); - } - - String _cleanPreview(String preview) { - return preview.replaceAll('\n', ' ').trim(); - } -} - -class _DocumentPreview extends StatelessWidget { - const _DocumentPreview({required this.preview}); - - final String preview; - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16) + - const EdgeInsets.only(left: 14), - child: FlowyText.regular( - preview, - color: Theme.of(context).hintColor, - fontSize: 12, - maxLines: 3, - overflow: TextOverflow.ellipsis, - ), - ); - } -} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/widgets/search_results_list.dart b/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/widgets/search_results_list.dart index ed9becf29e..d90888e3e9 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/widgets/search_results_list.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/widgets/search_results_list.dart @@ -1,47 +1,278 @@ +import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart'; +import 'package:appflowy/workspace/application/action_navigation/navigation_action.dart'; +import 'package:appflowy/workspace/application/command_palette/command_palette_bloc.dart'; +import 'package:appflowy/workspace/application/command_palette/search_result_list_bloc.dart'; import 'package:flutter/material.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/workspace/presentation/command_palette/widgets/search_result_tile.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/trash.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-search/result.pb.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_animate/flutter_animate.dart'; -class SearchResultsList extends StatelessWidget { - const SearchResultsList({ - super.key, +import 'search_result_cell.dart'; +import 'search_summary_cell.dart'; + +class SearchResultList extends StatefulWidget { + const SearchResultList({ required this.trash, - required this.results, + required this.resultItems, + required this.resultSummaries, + super.key, }); final List trash; - final List results; + final List resultItems; + final List resultSummaries; + + @override + State createState() => _SearchResultListState(); +} + +class _SearchResultListState extends State { + late final SearchResultListBloc bloc; + + @override + void initState() { + super.initState(); + bloc = SearchResultListBloc(); + } + + @override + void dispose() { + bloc.close(); + super.dispose(); + } + + Widget _buildSectionHeader(String title) => Padding( + padding: const EdgeInsets.symmetric(vertical: 8) + + const EdgeInsets.only(left: 8), + child: Opacity( + opacity: 0.6, + child: FlowyText(title, fontSize: 12), + ), + ); + + Widget _buildAIOverviewSection(BuildContext context) { + final state = context.read().state; + + if (state.generatingAIOverview) { + return Row( + children: [ + _buildSectionHeader(LocaleKeys.commandPalette_aiOverview.tr()), + const HSpace(10), + const AIOverviewIndicator(), + ], + ); + } + + if (widget.resultSummaries.isNotEmpty) { + if (!bloc.state.userHovered) { + WidgetsBinding.instance.addPostFrameCallback( + (_) { + bloc.add( + SearchResultListEvent.onHoverSummary( + summary: widget.resultSummaries[0], + userHovered: false, + ), + ); + }, + ); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildSectionHeader(LocaleKeys.commandPalette_aiOverview.tr()), + ListView.separated( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: widget.resultSummaries.length, + separatorBuilder: (_, __) => const Divider(height: 0), + itemBuilder: (_, index) => SearchSummaryCell( + summary: widget.resultSummaries[index], + isHovered: bloc.state.hoveredSummary != null, + ), + ), + ], + ); + } + + return const SizedBox.shrink(); + } + + Widget _buildResultsSection(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 8), + _buildSectionHeader(LocaleKeys.commandPalette_bestMatches.tr()), + ListView.separated( + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: widget.resultItems.length, + separatorBuilder: (_, __) => const Divider(height: 0), + itemBuilder: (_, index) { + final item = widget.resultItems[index]; + return SearchResultCell( + item: item, + isTrashed: widget.trash.any((t) => t.id == item.id), + isHovered: bloc.state.hoveredResult?.id == item.id, + ); + }, + ), + ], + ); + } @override Widget build(BuildContext context) { - return ListView.separated( - shrinkWrap: true, - physics: const ClampingScrollPhysics(), - separatorBuilder: (_, __) => const Divider(height: 0), - itemCount: results.length + 1, - itemBuilder: (_, index) { - if (index == 0) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 8) + - const EdgeInsets.only(left: 16), - child: FlowyText( - LocaleKeys.commandPalette_bestMatches.tr(), - ), - ); - } + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 6), + child: BlocProvider.value( + value: bloc, + child: BlocListener( + listener: (context, state) { + if (state.openPageId != null) { + FlowyOverlay.pop(context); + getIt().add( + ActionNavigationEvent.performAction( + action: NavigationAction(objectId: state.openPageId!), + ), + ); + } + }, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Flexible( + flex: 7, + child: BlocBuilder( + buildWhen: (previous, current) => + previous.hoveredResult != current.hoveredResult || + previous.hoveredSummary != current.hoveredSummary, + builder: (context, state) { + return ListView( + shrinkWrap: true, + physics: const ClampingScrollPhysics(), + children: [ + _buildAIOverviewSection(context), + const VSpace(10), + if (widget.resultItems.isNotEmpty) + _buildResultsSection(context), + ], + ); + }, + ), + ), + const HSpace(10), + if (widget.resultItems + .any((item) => item.content.isNotEmpty)) ...[ + const VerticalDivider( + thickness: 1.0, + ), + Flexible( + flex: 3, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 16, + ), + child: const SearchCellPreview(), + ), + ), + ], + ], + ), + ), + ), + ); + } +} - final result = results[index - 1]; - return SearchResultTile( - result: result, - onSelected: () => FlowyOverlay.pop(context), - isTrashed: trash.any((t) => t.id == result.viewId), - ); +class SearchCellPreview extends StatelessWidget { + const SearchCellPreview({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state.hoveredSummary != null) { + return SearchSummaryPreview(summary: state.hoveredSummary!); + } else if (state.hoveredResult != null) { + return SearchResultPreview(data: state.hoveredResult!); + } + return const SizedBox.shrink(); }, ); } } + +class AIOverviewIndicator extends StatelessWidget { + const AIOverviewIndicator({ + super.key, + this.duration = const Duration(seconds: 1), + }); + + final Duration duration; + + @override + Widget build(BuildContext context) { + final slice = Duration(milliseconds: duration.inMilliseconds ~/ 5); + return SelectionContainer.disabled( + child: SizedBox( + height: 20, + width: 100, + child: SeparatedRow( + separatorBuilder: () => const HSpace(4), + children: [ + buildDot(const Color(0xFF9327FF)) + .animate(onPlay: (controller) => controller.repeat()) + .slideY(duration: slice, begin: 0, end: -1) + .then() + .slideY(begin: -1, end: 1) + .then() + .slideY(begin: 1, end: 0) + .then() + .slideY(duration: slice * 2, begin: 0, end: 0), + buildDot(const Color(0xFFFB006D)) + .animate(onPlay: (controller) => controller.repeat()) + .slideY(duration: slice, begin: 0, end: 0) + .then() + .slideY(begin: 0, end: -1) + .then() + .slideY(begin: -1, end: 1) + .then() + .slideY(begin: 1, end: 0) + .then() + .slideY(begin: 0, end: 0), + buildDot(const Color(0xFFFFCE00)) + .animate(onPlay: (controller) => controller.repeat()) + .slideY(duration: slice * 2, begin: 0, end: 0) + .then() + .slideY(duration: slice, begin: 0, end: -1) + .then() + .slideY(begin: -1, end: 1) + .then() + .slideY(begin: 1, end: 0), + ], + ), + ), + ); + } + + Widget buildDot(Color color) { + return SizedBox.square( + dimension: 4, + child: DecoratedBox( + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(2), + ), + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/widgets/search_summary_cell.dart b/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/widgets/search_summary_cell.dart new file mode 100644 index 0000000000..84b8f6646b --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/widgets/search_summary_cell.dart @@ -0,0 +1,137 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/ai_chat/presentation/message/ai_markdown_text.dart'; +import 'package:appflowy/workspace/application/command_palette/search_result_ext.dart'; +import 'package:appflowy/workspace/application/command_palette/search_result_list_bloc.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra/theme_extension.dart'; +import 'package:flowy_infra_ui/style_widget/hover.dart'; +import 'package:flutter/material.dart'; + +import 'package:appflowy_backend/protobuf/flowy-search/result.pb.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class SearchSummaryCell extends StatelessWidget { + const SearchSummaryCell({ + required this.summary, + required this.isHovered, + super.key, + }); + + final SearchSummaryPB summary; + final bool isHovered; + + @override + Widget build(BuildContext context) { + return FlowyHover( + isSelected: () => isHovered, + onHover: (value) { + context.read().add( + SearchResultListEvent.onHoverSummary( + summary: summary, + userHovered: true, + ), + ); + }, + style: HoverStyle( + borderRadius: BorderRadius.circular(8), + hoverColor: + Theme.of(context).colorScheme.primary.withValues(alpha: 0.1), + foregroundColorOnHover: AFThemeExtension.of(context).textColor, + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: FlowyText( + summary.content, + maxLines: 20, + ), + ), + ); + } +} + +class SearchSummaryPreview extends StatelessWidget { + const SearchSummaryPreview({ + required this.summary, + super.key, + }); + + final SearchSummaryPB summary; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (summary.highlights.isNotEmpty) ...[ + Opacity( + opacity: 0.5, + child: FlowyText( + LocaleKeys.commandPalette_aiOverviewMoreDetails.tr(), + fontSize: 12, + ), + ), + const VSpace(6), + SearchSummaryHighlight(text: summary.highlights), + const VSpace(36), + ], + + Opacity( + opacity: 0.5, + child: FlowyText( + LocaleKeys.commandPalette_aiOverviewSource.tr(), + fontSize: 12, + ), + ), + // Sources + const VSpace(6), + ...summary.sources.map((e) => SearchSummarySource(source: e)), + ], + ); + } +} + +class SearchSummaryHighlight extends StatelessWidget { + const SearchSummaryHighlight({ + required this.text, + super.key, + }); + + final String text; + + @override + Widget build(BuildContext context) { + return AIMarkdownText(markdown: text); + } +} + +class SearchSummarySource extends StatelessWidget { + const SearchSummarySource({ + required this.source, + super.key, + }); + + final SearchSourcePB source; + + @override + Widget build(BuildContext context) { + final icon = source.icon.getIcon(); + return FlowyTooltip( + message: LocaleKeys.commandPalette_clickToOpenPage.tr(), + child: SizedBox( + height: 30, + child: FlowyButton( + leftIcon: icon, + hoverColor: + Theme.of(context).colorScheme.primary.withValues(alpha: 0.1), + text: FlowyText(source.displayName), + onTap: () { + context.read().add( + SearchResultListEvent.openPage(pageId: source.id), + ); + }, + ), + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/desktop_home_screen.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/desktop_home_screen.dart index a8d768aa79..619ee4e229 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/desktop_home_screen.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/desktop_home_screen.dart @@ -52,8 +52,8 @@ class DesktopHomeScreen extends StatelessWidget { return _buildLoading(); } - final workspaceSetting = snapshots.data?[0].fold( - (workspaceSettingPB) => workspaceSettingPB as WorkspaceSettingPB, + final workspaceLatest = snapshots.data?[0].fold( + (workspaceLatestPB) => workspaceLatestPB as WorkspaceLatestPB, (error) => null, ); @@ -64,7 +64,7 @@ class DesktopHomeScreen extends StatelessWidget { // In the unlikely case either of the above is null, eg. // when a workspace is already open this can happen. - if (workspaceSetting == null || userProfile == null) { + if (workspaceLatest == null || userProfile == null) { return const WorkspaceFailedScreen(); } @@ -86,11 +86,11 @@ class DesktopHomeScreen extends StatelessWidget { BlocProvider.value(value: getIt()), BlocProvider( create: (_) => - HomeBloc(workspaceSetting)..add(const HomeEvent.initial()), + HomeBloc(workspaceLatest)..add(const HomeEvent.initial()), ), BlocProvider( create: (_) => HomeSettingBloc( - workspaceSetting, + workspaceLatest, context.read(), context.widthPx, )..add(const HomeSettingEvent.initial()), @@ -137,7 +137,7 @@ class DesktopHomeScreen extends StatelessWidget { child: _buildBody( context, userProfile, - workspaceSetting, + workspaceLatest, ), ), ), @@ -157,7 +157,7 @@ class DesktopHomeScreen extends StatelessWidget { Widget _buildBody( BuildContext context, UserProfilePB userProfile, - WorkspaceSettingPB workspaceSetting, + WorkspaceLatestPB workspaceSetting, ) { final layout = HomeLayout(context); final homeStack = HomeStack( @@ -190,7 +190,7 @@ class DesktopHomeScreen extends StatelessWidget { BuildContext context, { required HomeLayout layout, required UserProfilePB userProfile, - required WorkspaceSettingPB workspaceSetting, + required WorkspaceLatestPB workspaceSetting, }) { final homeMenu = HomeSideBar( userProfile: userProfile, diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/home_stack.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/home_stack.dart index d6fccee450..464394cd39 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/home_stack.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/home_stack.dart @@ -2,9 +2,6 @@ import 'dart:async'; import 'dart:io'; import 'dart:math'; -import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; - import 'package:appflowy/core/frameless_window.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; @@ -12,6 +9,7 @@ import 'package:appflowy/plugins/blank/blank.dart'; import 'package:appflowy/shared/window_title_bar.dart'; import 'package:appflowy/startup/plugin/plugin.dart'; import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/util/theme_extension.dart'; import 'package:appflowy/workspace/application/home/home_setting_bloc.dart'; import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart'; import 'package:appflowy/workspace/presentation/home/home_sizes.dart'; @@ -24,6 +22,8 @@ import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:provider/provider.dart'; import 'package:time/time.dart'; @@ -225,12 +225,17 @@ class SecondaryView extends StatefulWidget { class _SecondaryViewState extends State with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin { + final overlayController = OverlayPortalController(); + final layerLink = LayerLink(); + late final ValueNotifier widthNotifier; late final AnimationController animationController; late Animation widthAnimation; late final Animation offsetAnimation; + late bool hasSecondaryView; + CurvedAnimation get curveAnimation => CurvedAnimation( parent: animationController, curve: Curves.easeOut, @@ -260,16 +265,166 @@ class _SecondaryViewState extends State begin: const Offset(1.0, 0.0), end: Offset.zero, ).animate(curveAnimation); + + widget.pageManager.secondaryNotifier.addListener(onSecondaryViewChanged); + onSecondaryViewChanged(); + + overlayController.show(); } @override void dispose() { widget.pageManager.showSecondaryPluginNotifier .removeListener(onShowSecondaryChanged); + widget.pageManager.secondaryNotifier.removeListener(onSecondaryViewChanged); widthNotifier.dispose(); super.dispose(); } + @override + Widget build(BuildContext context) { + super.build(context); + final isLightMode = Theme.of(context).isLightMode; + return OverlayPortal( + controller: overlayController, + overlayChildBuilder: (context) { + return ValueListenableBuilder( + valueListenable: widget.pageManager.showSecondaryPluginNotifier, + builder: (context, isShowing, child) { + return CompositedTransformFollower( + link: layerLink, + followerAnchor: Alignment.topRight, + offset: const Offset(0.0, 120.0), + child: Align( + alignment: AlignmentDirectional.topEnd, + child: AnimatedSwitcher( + duration: 150.milliseconds, + transitionBuilder: (child, animation) { + return NonClippingSizeTransition( + sizeFactor: animation, + axis: Axis.horizontal, + axisAlignment: -1, + child: child, + ); + }, + child: isShowing || !hasSecondaryView + ? const SizedBox.shrink() + : GestureDetector( + onTap: () => widget.pageManager + .showSecondaryPluginNotifier.value = true, + child: Container( + height: 36, + width: 36, + decoration: BoxDecoration( + borderRadius: getBorderRadius(), + color: Theme.of(context).colorScheme.surface, + boxShadow: [ + BoxShadow( + offset: const Offset(0, 4), + blurRadius: 20, + color: isLightMode + ? const Color(0x1F1F2329) + : Theme.of(context) + .shadowColor + .withValues(alpha: 0.08), + ), + ], + ), + child: FlowyHover( + style: HoverStyle( + borderRadius: getBorderRadius(), + border: getBorder(context), + ), + child: const Center( + child: FlowySvg( + FlowySvgs.rename_s, + size: Size.square(16.0), + ), + ), + ), + ), + ), + ), + ), + ); + }, + ); + }, + child: CompositedTransformTarget( + link: layerLink, + child: Container( + color: Theme.of(context).colorScheme.surface, + child: FocusTraversalGroup( + child: ValueListenableBuilder( + valueListenable: widthNotifier, + builder: (context, value, child) { + return AnimatedBuilder( + animation: Listenable.merge([ + widthAnimation, + offsetAnimation, + ]), + builder: (context, child) { + return Container( + width: widthAnimation.value, + alignment: Alignment( + offsetAnimation.value.dx, + offsetAnimation.value.dy, + ), + child: OverflowBox( + alignment: AlignmentDirectional.centerStart, + maxWidth: value, + child: SecondaryViewResizer( + pageManager: widget.pageManager, + notifier: widthNotifier, + child: Column( + children: [ + widget.pageManager.stackSecondaryTopBar(value), + Expanded( + child: widget.pageManager + .stackSecondaryWidget(value), + ), + ], + ), + ), + ), + ); + }, + ); + }, + ), + ), + ), + ), + ); + } + + BoxBorder getBorder(BuildContext context) { + final isLightMode = Theme.of(context).isLightMode; + final borderSide = BorderSide( + color: isLightMode + ? const Color(0x141F2329) + : Theme.of(context).dividerColor, + ); + + return Border( + left: borderSide, + top: borderSide, + bottom: borderSide, + ); + } + + BorderRadius getBorderRadius() { + return const BorderRadius.only( + topLeft: Radius.circular(12.0), + bottomLeft: Radius.circular(12.0), + ); + } + + void onSecondaryViewChanged() { + hasSecondaryView = widget.pageManager.secondaryNotifier.plugin.pluginType != + PluginType.blank; + } + void onShowSecondaryChanged() async { if (widget.pageManager.showSecondaryPluginNotifier.value) { widthNotifier.value = max(450.0, widget.adaptedPercentageWidth); @@ -289,53 +444,6 @@ class _SecondaryViewState extends State ).animate(curveAnimation); } - @override - Widget build(BuildContext context) { - super.build(context); - return Container( - color: Theme.of(context).colorScheme.surface, - child: FocusTraversalGroup( - child: ValueListenableBuilder( - valueListenable: widthNotifier, - builder: (context, value, child) { - return AnimatedBuilder( - animation: Listenable.merge([ - widthAnimation, - offsetAnimation, - ]), - builder: (context, child) { - return Container( - width: widthAnimation.value, - alignment: Alignment( - offsetAnimation.value.dx, - offsetAnimation.value.dy, - ), - child: OverflowBox( - alignment: AlignmentDirectional.centerStart, - maxWidth: value, - child: SecondaryViewResizer( - pageManager: widget.pageManager, - notifier: widthNotifier, - child: Column( - children: [ - widget.pageManager.stackSecondaryTopBar(value), - Expanded( - child: - widget.pageManager.stackSecondaryWidget(value), - ), - ], - ), - ), - ), - ); - }, - ); - }, - ), - ), - ); - } - @override bool get wantKeepAlive => true; } @@ -513,15 +621,17 @@ class PageNotifier extends ChangeNotifier { ]) => _plugin.widgetBuilder.tabBarItem(pluginId, shortForm); - /// This is the only place where the plugin is set. - /// No need compare the old plugin with the new plugin. Just set it. - void setPlugin(Plugin newPlugin, bool setLatest) { - if (newPlugin.id != plugin.id) { + void setPlugin( + Plugin newPlugin, { + required bool setLatest, + bool disposeExisting = true, + }) { + if (newPlugin.id != plugin.id && disposeExisting) { _plugin.dispose(); } // Set the plugin view as the latest view. - if (setLatest) { + if (setLatest && newPlugin.id.isNotEmpty) { FolderEventSetLatestView(ViewIdPB(value: newPlugin.id)).send(); } @@ -552,12 +662,24 @@ class PageManager { if (init) { newPlugin.init(); } - _notifier.setPlugin(newPlugin, setLatest); + _notifier.setPlugin(newPlugin, setLatest: setLatest); } void setSecondaryPlugin(Plugin newPlugin) { newPlugin.init(); - _secondaryNotifier.setPlugin(newPlugin, false); + _secondaryNotifier.setPlugin(newPlugin, setLatest: false); + } + + void expandSecondaryPlugin() { + _notifier.setPlugin(_secondaryNotifier.plugin, setLatest: true); + _secondaryNotifier.setPlugin( + BlankPagePlugin(), + setLatest: false, + disposeExisting: false, + ); + } + + void showSecondaryPlugin() { showSecondaryPluginNotifier.value = true; } @@ -585,6 +707,10 @@ class PageManager { value: _notifier, child: Consumer( builder: (_, notifier, __) { + if (notifier.plugin.pluginType == PluginType.blank) { + return const BlankPage(); + } + return FadingIndexedStack( index: getIt().indexOf(notifier.plugin.pluginType), children: getIt().supportPluginTypes.map( @@ -725,7 +851,7 @@ class HomeSecondaryTopBar extends StatelessWidget { Widget build(BuildContext context) { return Container( decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, + color: Theme.of(context).colorScheme.surfaceContainerHighest, ), height: HomeSizes.topBarHeight + HomeInsets.topBarTitleVerticalPadding, child: Padding( @@ -779,3 +905,125 @@ class HomeSecondaryTopBar extends StatelessWidget { ); } } + +/// A version of Flutter's built in SizeTransition widget that clips the child +/// more sparingly than the original. +class NonClippingSizeTransition extends AnimatedWidget { + const NonClippingSizeTransition({ + super.key, + this.axis = Axis.vertical, + required Animation sizeFactor, + this.axisAlignment = 0.0, + this.fixedCrossAxisSizeFactor, + this.child, + }) : assert( + fixedCrossAxisSizeFactor == null || fixedCrossAxisSizeFactor >= 0.0, + ), + super(listenable: sizeFactor); + + /// [Axis.horizontal] if [sizeFactor] modifies the width, otherwise + /// [Axis.vertical]. + final Axis axis; + + /// The animation that controls the (clipped) size of the child. + /// + /// The width or height (depending on the [axis] value) of this widget will be + /// its intrinsic width or height multiplied by [sizeFactor]'s value at the + /// current point in the animation. + /// + /// If the value of [sizeFactor] is less than one, the child will be clipped + /// in the appropriate axis. + Animation get sizeFactor => listenable as Animation; + + /// Describes how to align the child along the axis that [sizeFactor] is + /// modifying. + /// + /// A value of -1.0 indicates the top when [axis] is [Axis.vertical], and the + /// start when [axis] is [Axis.horizontal]. The start is on the left when the + /// text direction in effect is [TextDirection.ltr] and on the right when it + /// is [TextDirection.rtl]. + /// + /// A value of 1.0 indicates the bottom or end, depending upon the [axis]. + /// + /// A value of 0.0 (the default) indicates the center for either [axis] value. + final double axisAlignment; + + /// The factor by which to multiply the cross axis size of the child. + /// + /// If the value of [fixedCrossAxisSizeFactor] is less than one, the child + /// will be clipped along the appropriate axis. + /// + /// If `null` (the default), the cross axis size is as large as the parent. + final double? fixedCrossAxisSizeFactor; + + /// The widget below this widget in the tree. + /// + /// {@macro flutter.widgets.ProxyWidget.child} + final Widget? child; + + @override + Widget build(BuildContext context) { + final AlignmentDirectional alignment; + final Edge edge; + if (axis == Axis.vertical) { + alignment = AlignmentDirectional(-1.0, axisAlignment); + edge = switch (axisAlignment) { -1.0 => Edge.bottom, _ => Edge.top }; + } else { + alignment = AlignmentDirectional(axisAlignment, -1.0); + edge = switch (axisAlignment) { -1.0 => Edge.right, _ => Edge.left }; + } + return ClipRect( + clipper: EdgeRectClipper(edge: edge, margin: 20), + child: Align( + alignment: alignment, + heightFactor: axis == Axis.vertical + ? max(sizeFactor.value, 0.0) + : fixedCrossAxisSizeFactor, + widthFactor: axis == Axis.horizontal + ? max(sizeFactor.value, 0.0) + : fixedCrossAxisSizeFactor, + child: child, + ), + ); + } +} + +class EdgeRectClipper extends CustomClipper { + const EdgeRectClipper({ + required this.edge, + required this.margin, + }); + + final Edge edge; + final double margin; + + @override + Rect getClip(Size size) { + return switch (edge) { + Edge.left => + Rect.fromLTRB(0.0, -margin, size.width + margin, size.height + margin), + Edge.right => + Rect.fromLTRB(-margin, -margin, size.width, size.height + margin), + Edge.top => + Rect.fromLTRB(-margin, 0.0, size.width + margin, size.height + margin), + Edge.bottom => Rect.fromLTRB(-margin, -margin, size.width, size.height), + }; + } + + @override + bool shouldReclip(covariant CustomClipper oldClipper) => false; +} + +enum Edge { + left, + top, + right, + bottom; + + bool get isHorizontal => switch (this) { + left || right => true, + _ => false, + }; + + bool get isVertical => !isHorizontal; +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/favorites/favorite_folder.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/favorites/favorite_folder.dart index 7751c0782b..ca0773bf72 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/favorites/favorite_folder.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/favorites/favorite_folder.dart @@ -3,6 +3,7 @@ import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart'; import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart'; import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart'; +import 'package:appflowy/workspace/application/view/view_bloc.dart'; import 'package:appflowy/workspace/presentation/home/home_sizes.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/favorites/favorite_menu.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/favorites/favorite_more_actions.dart'; @@ -54,8 +55,7 @@ class _FavoriteFolderState extends State { .read() .add(const FolderEvent.expandOrUnExpand()), ), - // pages - ..._buildViews(context, state, isHovered), + buildReorderListView(context, state), if (state.isExpanded) ...[ // more button const VSpace(2), @@ -69,51 +69,91 @@ class _FavoriteFolderState extends State { ); } - Iterable _buildViews( + Widget buildReorderListView( BuildContext context, FolderState state, - ValueNotifier isHovered, ) { - if (!state.isExpanded) { - return []; + if (!state.isExpanded) return const SizedBox.shrink(); + + final favoriteBloc = context.read(); + final pinnedViews = + favoriteBloc.state.pinnedViews.map((e) => e.item).toList(); + + if (pinnedViews.isEmpty) return const SizedBox.shrink(); + if (pinnedViews.length == 1) { + return buildViewItem(pinnedViews.first); } - final pinnedViews = - context.read().state.pinnedViews.map((e) => e.item); - - return pinnedViews.map( - (view) => ViewItem( - key: ValueKey('${FolderSpaceType.favorite.name} ${view.id}'), - spaceType: FolderSpaceType.favorite, - isDraggable: false, - isFirstChild: view.id == widget.views.first.id, - isFeedback: false, - view: view, - enableRightClickContext: true, - leftPadding: HomeSpaceViewSizes.leftPadding, - leftIconBuilder: (_, __) => - const HSpace(HomeSpaceViewSizes.leftPadding), - level: 0, - isHovered: isHovered, - rightIconsBuilder: (context, view) => [ - FavoriteMoreActions(view: view), - const HSpace(8.0), - FavoritePinAction(view: view), - const HSpace(4.0), - ], - shouldRenderChildren: false, - shouldLoadChildViews: false, - onTertiarySelected: (_, view) => context.read().openTab(view), - onSelected: (_, view) { - if (HardwareKeyboard.instance.isControlPressed) { - context.read().openTab(view); - } - - context.read().openPlugin(view); + return Theme( + data: Theme.of(context).copyWith( + canvasColor: Colors.transparent, + shadowColor: Colors.transparent, + ), + child: ReorderableListView.builder( + shrinkWrap: true, + buildDefaultDragHandles: false, + itemCount: pinnedViews.length, + padding: EdgeInsets.zero, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, i) { + final view = pinnedViews[i]; + return ReorderableDragStartListener( + key: ValueKey(view.id), + index: i, + child: DecoratedBox( + decoration: const BoxDecoration(color: Colors.transparent), + child: buildViewItem(view), + ), + ); + }, + onReorder: (oldIndex, newIndex) { + favoriteBloc.add(FavoriteEvent.reorder(oldIndex, newIndex)); }, ), ); } + + Widget buildViewItem(ViewPB view) { + return ViewItem( + key: ValueKey('${FolderSpaceType.favorite.name} ${view.id}'), + spaceType: FolderSpaceType.favorite, + isDraggable: false, + isFirstChild: view.id == widget.views.first.id, + isFeedback: false, + view: view, + enableRightClickContext: true, + leftPadding: HomeSpaceViewSizes.leftPadding, + leftIconBuilder: (_, __) => const HSpace(HomeSpaceViewSizes.leftPadding), + level: 0, + isHovered: isHovered, + rightIconsBuilder: (context, view) => [ + Listener( + child: FavoriteMoreActions(view: view), + onPointerDown: (e) { + context.read().add(const ViewEvent.setIsEditing(true)); + }, + ), + const HSpace(8.0), + Listener( + child: FavoritePinAction(view: view), + onPointerDown: (e) { + context.read().add(const ViewEvent.setIsEditing(true)); + }, + ), + const HSpace(4.0), + ], + shouldRenderChildren: false, + shouldLoadChildViews: false, + onTertiarySelected: (_, view) => context.read().openTab(view), + onSelected: (_, view) { + if (HardwareKeyboard.instance.isControlPressed) { + context.read().openTab(view); + } + + context.read().openPlugin(view); + }, + ); + } } class FavoriteHeader extends StatelessWidget { diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/folder/_section_folder.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/folder/_section_folder.dart index 0b383cc5a1..a8717e28bc 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/folder/_section_folder.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/folder/_section_folder.dart @@ -103,6 +103,7 @@ class _SectionFolderState extends State { (view) => ViewItem( key: ValueKey('${widget.spaceType.name} ${view.id}'), spaceType: widget.spaceType, + engagedInExpanding: true, isFirstChild: view.id == widget.views.first.id, view: view, level: 0, diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/footer/sidebar_footer.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/footer/sidebar_footer.dart index 57168b40a5..f8c3a30488 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/footer/sidebar_footer.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/footer/sidebar_footer.dart @@ -60,7 +60,7 @@ class SidebarTemplateButton extends StatelessWidget { FlowySvgs.icon_template_s, ), text: LocaleKeys.template_label.tr(), - onTap: () => afLaunchUrlString('https://appflowy.io/templates'), + onTap: () => afLaunchUrlString('https://appflowy.com/templates'), ); } } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/footer/sidebar_toast.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/footer/sidebar_toast.dart index 6d2b93be45..05e6d46957 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/footer/sidebar_toast.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/footer/sidebar_toast.dart @@ -105,9 +105,9 @@ class SidebarToast extends StatelessWidget { if (role.isOwner) { showSettingsDialog( context, - userProfile, - userWorkspaceBloc, - SettingsPage.plan, + userProfile: userProfile, + userWorkspaceBloc: userWorkspaceBloc, + initPage: SettingsPage.plan, ); } else { final String message; @@ -174,8 +174,8 @@ class _PlanIndicatorState extends State { begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ - const Color(0xFF8032FF).withOpacity(.1), - const Color(0xFFEF35FF).withOpacity(.1), + const Color(0xFF8032FF).withValues(alpha: .1), + const Color(0xFFEF35FF).withValues(alpha: .1), ], ); diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/footer/sidebar_upgarde_application_button.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/footer/sidebar_upgarde_application_button.dart new file mode 100644 index 0000000000..abe3ffd354 --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/footer/sidebar_upgarde_application_button.dart @@ -0,0 +1,110 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/util/theme_extension.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; + +class SidebarUpgradeApplicationButton extends StatelessWidget { + const SidebarUpgradeApplicationButton({ + super.key, + required this.onUpdateButtonTap, + required this.onCloseButtonTap, + }); + + final VoidCallback onUpdateButtonTap; + final VoidCallback onCloseButtonTap; + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: context.sidebarUpgradeButtonBackground, + borderRadius: BorderRadius.circular(16), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // title + _buildTitle(), + const VSpace(2), + // description + _buildDescription(), + const VSpace(10), + // update button + _buildUpdateButton(), + ], + ), + ); + } + + Widget _buildTitle() { + return Row( + children: [ + const FlowySvg( + FlowySvgs.sidebar_upgrade_version_s, + blendMode: null, + ), + const HSpace(6), + FlowyText.medium( + LocaleKeys.autoUpdate_bannerUpdateTitle.tr(), + fontSize: 14, + figmaLineHeight: 18, + ), + const Spacer(), + FlowyButton( + useIntrinsicWidth: true, + text: const FlowySvg(FlowySvgs.upgrade_close_s), + onTap: onCloseButtonTap, + ), + ], + ); + } + + Widget _buildDescription() { + return Opacity( + opacity: 0.7, + child: FlowyText( + LocaleKeys.autoUpdate_bannerUpdateDescription.tr(), + fontSize: 13, + figmaLineHeight: 16, + maxLines: null, + ), + ); + } + + Widget _buildUpdateButton() { + return MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: onUpdateButtonTap, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 6, + ), + decoration: ShapeDecoration( + color: const Color(0xFFA44AFD), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(9), + ), + ), + child: FlowyText.medium( + LocaleKeys.autoUpdate_settingsUpdateButton.tr(), + color: Colors.white, + fontSize: 12.0, + figmaLineHeight: 15.0, + ), + ), + ), + ); + } +} + +extension on BuildContext { + Color get sidebarUpgradeButtonBackground => Theme.of(this).isLightMode + ? const Color(0xB2EBE4FF) + : const Color(0xB239275B); +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/header/sidebar_top_menu.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/header/sidebar_top_menu.dart index 559c189925..67930c336a 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/header/sidebar_top_menu.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/header/sidebar_top_menu.dart @@ -50,8 +50,8 @@ class SidebarTopMenu extends StatelessWidget { } final svgData = Theme.of(context).brightness == Brightness.dark - ? FlowySvgs.flowy_logo_dark_mode_xl - : FlowySvgs.flowy_logo_text_xl; + ? FlowySvgs.app_logo_with_text_dark_xl + : FlowySvgs.app_logo_with_text_light_xl; return Padding( padding: const EdgeInsets.only(top: 12.0, left: 8), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/import/import_panel.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/import/import_panel.dart index 6a23c7def9..716002e917 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/import/import_panel.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/import/import_panel.dart @@ -204,8 +204,6 @@ class _ImportPanelState extends State { ..importType = ImportTypePB.AFDatabase, ); break; - default: - break; } } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/shared/sidebar_setting.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/shared/sidebar_setting.dart index 84a76cfe83..0bd5dafe91 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/shared/sidebar_setting.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/shared/sidebar_setting.dart @@ -2,6 +2,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart'; import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/user/application/password/password_bloc.dart'; import 'package:appflowy/workspace/application/settings/settings_dialog_bloc.dart'; import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; import 'package:appflowy/workspace/presentation/home/af_focus_manager.dart'; @@ -33,7 +34,7 @@ HotKeyItem openSettingsHotKey( ), keyDownHandler: (_) { if (_settingsDialogKey.currentContext == null) { - showSettingsDialog(context, userProfile); + showSettingsDialog(context, userProfile: userProfile); } else { Navigator.of(context, rootNavigator: true) .popUntil((route) => route.isFirst); @@ -57,37 +58,55 @@ class UserSettingButton extends StatefulWidget { class _UserSettingButtonState extends State { late UserWorkspaceBloc _userWorkspaceBloc; + late PasswordBloc _passwordBloc; @override void initState() { super.initState(); + _userWorkspaceBloc = context.read(); + _passwordBloc = PasswordBloc(widget.userProfile) + ..add(PasswordEvent.init()) + ..add(PasswordEvent.checkHasPassword()); } @override void didChangeDependencies() { _userWorkspaceBloc = context.read(); + super.didChangeDependencies(); } + @override + void dispose() { + _passwordBloc.close(); + + super.dispose(); + } + @override Widget build(BuildContext context) { return SizedBox.square( dimension: 24.0, child: FlowyTooltip( message: LocaleKeys.settings_menu_open.tr(), - child: FlowyButton( - onTap: () => showSettingsDialog( - context, - widget.userProfile, - _userWorkspaceBloc, - ), - margin: EdgeInsets.zero, - text: FlowySvg( - FlowySvgs.settings_s, - color: - widget.isHover ? Theme.of(context).colorScheme.onSurface : null, - opacity: 0.7, + child: BlocProvider.value( + value: _passwordBloc, + child: FlowyButton( + onTap: () => showSettingsDialog( + context, + userProfile: widget.userProfile, + userWorkspaceBloc: _userWorkspaceBloc, + passwordBloc: _passwordBloc, + ), + margin: EdgeInsets.zero, + text: FlowySvg( + FlowySvgs.settings_s, + color: widget.isHover + ? Theme.of(context).colorScheme.onSurface + : null, + opacity: 0.7, + ), ), ), ), @@ -96,21 +115,33 @@ class _UserSettingButtonState extends State { } void showSettingsDialog( - BuildContext context, - UserProfilePB userProfile, [ - UserWorkspaceBloc? bloc, + BuildContext context, { + required UserProfilePB userProfile, + UserWorkspaceBloc? userWorkspaceBloc, + PasswordBloc? passwordBloc, SettingsPage? initPage, -]) { +}) { AFFocusManager.maybeOf(context)?.notifyLoseFocus(); showDialog( context: context, builder: (dialogContext) => MultiBlocProvider( key: _settingsDialogKey, providers: [ + passwordBloc != null + ? BlocProvider.value( + value: passwordBloc, + ) + : BlocProvider( + create: (context) => PasswordBloc(userProfile) + ..add(PasswordEvent.init()) + ..add(PasswordEvent.checkHasPassword()), + ), BlocProvider.value( value: BlocProvider.of(dialogContext), ), - BlocProvider.value(value: bloc ?? context.read()), + BlocProvider.value( + value: userWorkspaceBloc ?? context.read(), + ), ], child: SettingsDialog( userProfile, diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar.dart index 1a396b26dd..9c19184217 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar.dart @@ -4,9 +4,12 @@ import 'dart:io'; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/blank/blank.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart'; +import 'package:appflowy/plugins/document/presentation/editor_notification.dart'; import 'package:appflowy/shared/feature_flags.dart'; +import 'package:appflowy/shared/loading.dart'; +import 'package:appflowy/shared/version_checker/version_checker.dart'; import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/startup/tasks/device_info_task.dart'; import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart'; import 'package:appflowy/workspace/application/action_navigation/navigation_action.dart'; import 'package:appflowy/workspace/application/command_palette/command_palette_bloc.dart'; @@ -22,6 +25,7 @@ import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy/workspace/presentation/command_palette/command_palette.dart'; import 'package:appflowy/workspace/presentation/home/home_sizes.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/footer/sidebar_footer.dart'; +import 'package:appflowy/workspace/presentation/home/menu/sidebar/footer/sidebar_upgarde_application_button.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/header/sidebar_top_menu.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/header/sidebar_user.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/sidebar_folder.dart'; @@ -56,7 +60,7 @@ class HomeSideBar extends StatelessWidget { final UserProfilePB userProfile; - final WorkspaceSettingPB workspaceSetting; + final WorkspaceLatestPB workspaceSetting; @override Widget build(BuildContext context) { @@ -260,6 +264,9 @@ class _SidebarState extends State<_Sidebar> { final _isHovered = ValueNotifier(false); final _scrollOffset = ValueNotifier(0); + // mute the update button during the current application lifecycle. + final _muteUpdateButton = ValueNotifier(false); + @override void initState() { super.initState(); @@ -346,6 +353,7 @@ class _SidebarState extends State<_Sidebar> { const VSpace(8), _renderUpgradeSpaceButton(menuHorizontalInset), + _buildUpgradeApplicationButton(menuHorizontalInset), const VSpace(8), Padding( @@ -431,6 +439,42 @@ class _SidebarState extends State<_Sidebar> { ); } + Widget _buildUpgradeApplicationButton(EdgeInsets menuHorizontalInset) { + return ValueListenableBuilder( + valueListenable: _muteUpdateButton, + builder: (_, mute, child) { + if (mute) { + return const SizedBox.shrink(); + } + + return ValueListenableBuilder( + valueListenable: ApplicationInfo.latestVersionNotifier, + builder: (_, latestVersion, child) { + if (!ApplicationInfo.isUpdateAvailable) { + return const SizedBox.shrink(); + } + + return Padding( + padding: menuHorizontalInset + + const EdgeInsets.only( + left: 4.0, + right: 4.0, + ), + child: SidebarUpgradeApplicationButton( + onUpdateButtonTap: () { + versionChecker.checkForUpdate(); + }, + onCloseButtonTap: () { + _muteUpdateButton.value = true; + }, + ), + ); + }, + ); + }, + ); + } + void _onScrollChanged() { setState(() => _isScrolling = true); @@ -469,7 +513,11 @@ class _SidebarSearchButton extends StatelessWidget { ], ), child: FlowyButton( - onTap: () => CommandPalette.of(context).toggle(), + onTap: () { + // exit editing mode when doing search to avoid the toolbar showing up + EditorNotification.exitEditing().post(); + CommandPalette.of(context).toggle(); + }, leftIcon: const FlowySvg(FlowySvgs.search_s), iconPadding: 12.0, margin: const EdgeInsets.only(left: 8.0), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/shared_widget.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/shared_widget.dart index 7afb4a6298..95130b029e 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/shared_widget.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/shared_widget.dart @@ -13,6 +13,7 @@ import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; @@ -173,42 +174,53 @@ class SpaceCancelOrConfirmButton extends StatelessWidget { required this.onConfirm, required this.confirmButtonName, this.confirmButtonColor, + this.confirmButtonBuilder, }); final VoidCallback onCancel; final VoidCallback onConfirm; final String confirmButtonName; final Color? confirmButtonColor; - + final WidgetBuilder? confirmButtonBuilder; @override Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); return Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - OutlinedRoundedButton( + AFOutlinedTextButton.normal( text: LocaleKeys.button_cancel.tr(), + textStyle: theme.textStyle.body.standard( + color: theme.textColorScheme.primary, + ), onTap: onCancel, ), const HSpace(12.0), - DecoratedBox( - decoration: ShapeDecoration( - color: confirmButtonColor ?? Theme.of(context).colorScheme.primary, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), + if (confirmButtonBuilder != null) ...[ + confirmButtonBuilder!(context), + ] else ...[ + DecoratedBox( + decoration: ShapeDecoration( + color: + confirmButtonColor ?? Theme.of(context).colorScheme.primary, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: FlowyButton( + useIntrinsicWidth: true, + margin: + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 9.0), + radius: BorderRadius.circular(8), + text: FlowyText.regular( + confirmButtonName, + lineHeight: 1.0, + color: Theme.of(context).colorScheme.onPrimary, + ), + onTap: onConfirm, ), ), - child: FlowyButton( - useIntrinsicWidth: true, - margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 9.0), - radius: BorderRadius.circular(8), - text: FlowyText.regular( - confirmButtonName, - lineHeight: 1.0, - color: Theme.of(context).colorScheme.onPrimary, - ), - onTap: onConfirm, - ), - ), + ], ], ); } @@ -249,17 +261,11 @@ enum ConfirmPopupStyle { class ConfirmPopupColor { static Color titleColor(BuildContext context) { - if (Theme.of(context).isLightMode) { - return const Color(0xFF171717).withOpacity(0.8); - } - return const Color(0xFFffffff).withOpacity(0.8); + return AppFlowyTheme.of(context).textColorScheme.primary; } static Color descriptionColor(BuildContext context) { - if (Theme.of(context).isLightMode) { - return const Color(0xFF171717).withOpacity(0.7); - } - return const Color(0xFFffffff).withOpacity(0.7); + return AppFlowyTheme.of(context).textColorScheme.primary; } } @@ -273,8 +279,11 @@ class ConfirmPopup extends StatefulWidget { this.onCancel, this.confirmLabel, this.confirmButtonColor, + this.confirmButtonBuilder, this.child, this.closeOnAction = true, + this.showCloseButton = true, + this.enableKeyboardListener = true, }); final String title; @@ -303,6 +312,20 @@ class ConfirmPopup extends StatefulWidget { /// final bool closeOnAction; + /// Show close button. + /// Defaults to true. + /// + final bool showCloseButton; + + /// Enable keyboard listener. + /// Defaults to true. + /// + final bool enableKeyboardListener; + + /// Allows to build a custom confirm button. + /// + final WidgetBuilder? confirmButtonBuilder; + @override State createState() => _ConfirmPopupState(); } @@ -316,14 +339,16 @@ class _ConfirmPopupState extends State { focusNode: focusNode, autofocus: true, onKeyEvent: (event) { - if (event is KeyDownEvent && - event.logicalKey == LogicalKeyboardKey.escape) { - Navigator.of(context).pop(); - } else if (event is KeyUpEvent && - event.logicalKey == LogicalKeyboardKey.enter) { - widget.onConfirm(); - if (widget.closeOnAction) { + if (widget.enableKeyboardListener) { + if (event is KeyDownEvent && + event.logicalKey == LogicalKeyboardKey.escape) { Navigator.of(context).pop(); + } else if (event is KeyUpEvent && + event.logicalKey == LogicalKeyboardKey.enter) { + widget.onConfirm(); + if (widget.closeOnAction) { + Navigator.of(context).pop(); + } } } }, @@ -354,28 +379,30 @@ class _ConfirmPopupState extends State { } Widget _buildTitle() { + final theme = AppFlowyTheme.of(context); return Row( children: [ Expanded( - child: FlowyText( + child: Text( widget.title, - fontSize: 16.0, - figmaLineHeight: 22.0, - fontWeight: FontWeight.w500, + style: theme.textStyle.heading4.prominent( + color: ConfirmPopupColor.titleColor(context), + ), overflow: TextOverflow.ellipsis, - color: ConfirmPopupColor.titleColor(context), ), ), const HSpace(6.0), - FlowyButton( - margin: const EdgeInsets.all(3), - useIntrinsicWidth: true, - text: const FlowySvg( - FlowySvgs.upgrade_close_s, - size: Size.square(18.0), + if (widget.showCloseButton) ...[ + AFGhostButton.normal( + size: AFButtonSize.s, + padding: EdgeInsets.all(theme.spacing.xs), + onTap: () => Navigator.of(context).pop(), + builder: (context, isHovering, disabled) => FlowySvg( + FlowySvgs.password_close_m, + size: const Size.square(20), + ), ), - onTap: () => Navigator.of(context).pop(), - ), + ], ], ); } @@ -385,18 +412,24 @@ class _ConfirmPopupState extends State { return const SizedBox.shrink(); } - return FlowyText.regular( + final theme = AppFlowyTheme.of(context); + + return Text( widget.description, - fontSize: 16.0, - color: ConfirmPopupColor.descriptionColor(context), + style: theme.textStyle.body.standard( + color: ConfirmPopupColor.descriptionColor(context), + ), maxLines: 5, - figmaLineHeight: 22.0, ); } Widget _buildStyledButton(BuildContext context) { switch (widget.style) { case ConfirmPopupStyle.onlyOk: + if (widget.confirmButtonBuilder != null) { + return widget.confirmButtonBuilder!(context); + } + return SpaceOkButton( onConfirm: () { widget.onConfirm(); @@ -424,6 +457,7 @@ class _ConfirmPopupState extends State { widget.confirmLabel ?? LocaleKeys.space_delete.tr(), confirmButtonColor: widget.confirmButtonColor ?? Theme.of(context).colorScheme.error, + confirmButtonBuilder: widget.confirmButtonBuilder, ); } } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/sidebar_space_header.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/sidebar_space_header.dart index 03b96c9607..cf4a2aa5b1 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/sidebar_space_header.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/space/sidebar_space_header.dart @@ -173,21 +173,22 @@ class _SidebarSpaceHeaderState extends State { await _showRenameDialog(); break; case SpaceMoreActionType.changeIcon: - final result = data as EmojiIconData; - if (data.type == FlowyIconType.icon) { - try { - final iconsData = IconsData.fromJson(jsonDecode(result.emoji)); - context.read().add( - SpaceEvent.changeIcon( - icon: '${iconsData.groupName}/${iconsData.iconName}', - iconColor: iconsData.color, - ), - ); - } on FormatException catch (e) { - context - .read() - .add(const SpaceEvent.changeIcon(icon: '')); - Log.warn('SidebarSpaceHeader changeIcon error:$e'); + if (data is SelectedEmojiIconResult) { + if (data.type == FlowyIconType.icon) { + try { + final iconsData = IconsData.fromJson(jsonDecode(data.emoji)); + context.read().add( + SpaceEvent.changeIcon( + icon: '${iconsData.groupName}/${iconsData.iconName}', + iconColor: iconsData.color, + ), + ); + } on FormatException catch (e) { + context + .read() + .add(const SpaceEvent.changeIcon(icon: '')); + Log.warn('SidebarSpaceHeader changeIcon error:$e'); + } } } break; diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart index ee5bc3fab3..44f558fc17 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_actions.dart @@ -18,15 +18,23 @@ enum WorkspaceMoreAction { divider, } -class WorkspaceMoreActionList extends StatelessWidget { +class WorkspaceMoreActionList extends StatefulWidget { const WorkspaceMoreActionList({ super.key, required this.workspace, - required this.isShowingMoreActions, + required this.popoverMutex, }); final UserWorkspacePB workspace; - final ValueNotifier isShowingMoreActions; + final PopoverMutex popoverMutex; + + @override + State createState() => + _WorkspaceMoreActionListState(); +} + +class _WorkspaceMoreActionListState extends State { + bool isPopoverOpen = false; @override Widget build(BuildContext context) { @@ -45,16 +53,22 @@ class WorkspaceMoreActionList extends StatelessWidget { return PopoverActionList<_WorkspaceMoreActionWrapper>( direction: PopoverDirection.bottomWithLeftAligned, actions: actions - .map((e) => _WorkspaceMoreActionWrapper(e, workspace)) + .map( + (action) => _WorkspaceMoreActionWrapper( + action, + widget.workspace, + () => PopoverContainer.of(context).closeAll(), + ), + ) .toList(), + mutex: widget.popoverMutex, constraints: const BoxConstraints(minWidth: 220), animationDuration: Durations.short3, slideDistance: 2, beginScaleFactor: 1.0, beginOpacity: 0.8, - onClosed: () { - isShowingMoreActions.value = false; - }, + onClosed: () => isPopoverOpen = false, + asBarrier: true, buildChild: (controller) { return SizedBox.square( dimension: 24.0, @@ -64,11 +78,10 @@ class WorkspaceMoreActionList extends StatelessWidget { FlowySvgs.workspace_three_dots_s, ), onTap: () { - if (!isShowingMoreActions.value) { + if (!isPopoverOpen) { controller.show(); + isPopoverOpen = true; } - - isShowingMoreActions.value = true; }, ), ); @@ -79,10 +92,15 @@ class WorkspaceMoreActionList extends StatelessWidget { } class _WorkspaceMoreActionWrapper extends CustomActionCell { - _WorkspaceMoreActionWrapper(this.inner, this.workspace); + _WorkspaceMoreActionWrapper( + this.inner, + this.workspace, + this.closeWorkspaceMenu, + ); final WorkspaceMoreAction inner; final UserWorkspacePB workspace; + final VoidCallback closeWorkspaceMenu; @override Widget buildWithContext( @@ -117,6 +135,7 @@ class _WorkspaceMoreActionWrapper extends CustomActionCell { margin: const EdgeInsets.all(6), onTap: () async { PopoverContainer.of(context).closeAll(); + closeWorkspaceMenu(); final workspaceBloc = context.read(); switch (inner) { diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart index 12dec42026..1f9f4b03b8 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart @@ -96,9 +96,9 @@ class _WorkspaceIconState extends State { margin: const EdgeInsets.all(0), popupBuilder: (_) => FlowyIconEmojiPicker( tabs: const [PickerTabType.emoji], - onSelectedEmoji: (result) { - widget.onSelected(result); - controller.close(); + onSelectedEmoji: (r) { + widget.onSelected(r.data); + if (!r.keepOpen) controller.close(); }, ), child: MouseRegion( diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart index cfb86b8832..4ff5ccbf67 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart @@ -43,13 +43,7 @@ class WorkspacesMenu extends StatefulWidget { } class _WorkspacesMenuState extends State { - final ValueNotifier isShowingMoreActions = ValueNotifier(false); - - @override - void dispose() { - isShowingMoreActions.dispose(); - super.dispose(); - } + final popoverMutex = PopoverMutex(); @override Widget build(BuildContext context) { @@ -59,7 +53,7 @@ class _WorkspacesMenuState extends State { children: [ // user email Padding( - padding: const EdgeInsets.symmetric(horizontal: 4.0), + padding: const EdgeInsets.only(left: 10.0, top: 6.0, right: 10.0), child: Row( children: [ Expanded( @@ -71,18 +65,21 @@ class _WorkspacesMenuState extends State { ), ), const HSpace(4.0), - const _WorkspaceMoreButton(), + WorkspaceMoreButton( + popoverMutex: popoverMutex, + ), const HSpace(8.0), ], ), ), const Padding( - padding: EdgeInsets.symmetric(vertical: 8.0), + padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 6.0), child: Divider(height: 1.0), ), // workspace list Flexible( child: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 6.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -93,7 +90,7 @@ class _WorkspacesMenuState extends State { userProfile: widget.userProfile, isSelected: workspace.workspaceId == widget.currentWorkspace.workspaceId, - isShowingMoreActions: isShowingMoreActions, + popoverMutex: popoverMutex, ), const VSpace(6.0), ], @@ -102,13 +99,19 @@ class _WorkspacesMenuState extends State { ), ), // add new workspace - const _CreateWorkspaceButton(), - const VSpace(6.0), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 6.0), + child: _CreateWorkspaceButton(), + ), if (UniversalPlatform.isDesktop) ...[ - const _ImportNotionButton(), - const VSpace(6.0), + const Padding( + padding: EdgeInsets.only(left: 6.0, top: 6.0, right: 6.0), + child: _ImportNotionButton(), + ), ], + + const VSpace(6.0), ], ); } @@ -132,13 +135,13 @@ class WorkspaceMenuItem extends StatefulWidget { required this.workspace, required this.userProfile, required this.isSelected, - required this.isShowingMoreActions, + required this.popoverMutex, }); final UserProfilePB userProfile; final UserWorkspacePB workspace; final bool isSelected; - final ValueNotifier isShowingMoreActions; + final PopoverMutex popoverMutex; @override State createState() => _WorkspaceMenuItemState(); @@ -230,7 +233,7 @@ class _WorkspaceMenuItemState extends State { }, child: WorkspaceMoreActionList( workspace: widget.workspace, - isShowingMoreActions: widget.isShowingMoreActions, + popoverMutex: widget.popoverMutex, ), ), const HSpace(8.0), @@ -303,9 +306,12 @@ class _WorkspaceInfo extends StatelessWidget { // Persist and close other tabs when switching workspace, restore tabs for new workspace getIt().add(TabsEvent.switchWorkspace(workspace.workspaceId)); - context - .read() - .add(UserWorkspaceEvent.openWorkspace(workspace.workspaceId)); + context.read().add( + UserWorkspaceEvent.openWorkspace( + workspace.workspaceId, + workspace.workspaceAuthType, + ), + ); PopoverContainer.of(context).closeAll(); } @@ -367,7 +373,7 @@ class _CreateWorkspaceButton extends StatelessWidget { decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), border: Border.all( - color: const Color(0x01717171).withOpacity(0.12), + color: const Color(0x01717171).withValues(alpha: 0.12), width: 0.8, ), ), @@ -380,7 +386,12 @@ class _CreateWorkspaceButton extends StatelessWidget { final workspaceBloc = context.read(); await CreateWorkspaceDialog( onConfirm: (name) { - workspaceBloc.add(UserWorkspaceEvent.createWorkspace(name)); + workspaceBloc.add( + UserWorkspaceEvent.createWorkspace( + name, + AuthTypePB.Server, + ), + ); }, ).show(context); } @@ -394,40 +405,35 @@ class _ImportNotionButton extends StatelessWidget { Widget build(BuildContext context) { return SizedBox( height: 40, - child: Stack( - alignment: Alignment.centerRight, - children: [ - FlowyButton( - key: importNotionButtonKey, - onTap: () { - _showImportNotinoDialog(context); + child: FlowyButton( + key: importNotionButtonKey, + onTap: () { + _showImportNotinoDialog(context); + }, + margin: const EdgeInsets.symmetric(horizontal: 4.0), + text: Row( + children: [ + _buildLeftIcon(context), + const HSpace(8.0), + FlowyText.regular( + LocaleKeys.workspace_importFromNotion.tr(), + ), + ], + ), + rightIcon: FlowyTooltip( + message: LocaleKeys.workspace_learnMore.tr(), + preferBelow: true, + child: FlowyIconButton( + icon: const FlowySvg( + FlowySvgs.information_s, + ), + onPressed: () { + afLaunchUrlString( + 'https://docs.appflowy.io/docs/guides/import-from-notion', + ); }, - margin: const EdgeInsets.symmetric(horizontal: 4.0), - text: Row( - children: [ - _buildLeftIcon(context), - const HSpace(8.0), - FlowyText.regular( - LocaleKeys.workspace_importFromNotion.tr(), - ), - ], - ), ), - FlowyTooltip( - message: LocaleKeys.workspace_learnMore.tr(), - preferBelow: true, - child: FlowyIconButton( - icon: const FlowySvg( - FlowySvgs.information_s, - ), - onPressed: () { - afLaunchUrlString( - 'https://docs.appflowy.io/docs/guides/import-from-notion', - ); - }, - ), - ), - ], + ), ), ); } @@ -440,7 +446,7 @@ class _ImportNotionButton extends StatelessWidget { decoration: BoxDecoration( borderRadius: BorderRadius.circular(12), border: Border.all( - color: const Color(0x01717171).withOpacity(0.12), + color: const Color(0x01717171).withValues(alpha: 0.12), width: 0.8, ), ), @@ -478,14 +484,22 @@ class _ImportNotionButton extends StatelessWidget { } } -class _WorkspaceMoreButton extends StatelessWidget { - const _WorkspaceMoreButton(); +@visibleForTesting +class WorkspaceMoreButton extends StatelessWidget { + const WorkspaceMoreButton({ + super.key, + required this.popoverMutex, + }); + + final PopoverMutex popoverMutex; @override Widget build(BuildContext context) { return AppFlowyPopover( direction: PopoverDirection.bottomWithLeftAligned, offset: const Offset(0, 6), + mutex: popoverMutex, + asBarrier: true, popupBuilder: (_) => FlowyButton( margin: const EdgeInsets.symmetric(horizontal: 6.0, vertical: 7.0), leftIcon: const FlowySvg(FlowySvgs.workspace_logout_s), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart index c3480a94bc..50ea9d83c7 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart @@ -1,6 +1,6 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart'; +import 'package:appflowy/shared/loading.dart'; import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/sidebar_setting.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart'; @@ -169,7 +169,6 @@ class _SidebarWorkspaceState extends State { if (message != null) { showToastNotification( - context, message: message, type: result.fold( (_) => ToastificationType.success, @@ -207,6 +206,7 @@ class _SidebarSwitchWorkspaceButtonState direction: PopoverDirection.bottomWithCenterAligned, offset: const Offset(0, 5), constraints: const BoxConstraints(maxWidth: 300, maxHeight: 600), + margin: EdgeInsets.zero, animationDuration: Durations.short3, beginScaleFactor: 1.0, beginOpacity: 0.8, diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/draggable_view_item.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/draggable_view_item.dart index 2b27024cff..c604fae432 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/draggable_view_item.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/draggable_view_item.dart @@ -111,7 +111,8 @@ class _DraggableViewItemState extends State { decoration: BoxDecoration( borderRadius: BorderRadius.circular(6.0), color: position == DraggableHoverPosition.center - ? widget.centerHighlightColor ?? hoverColor.withOpacity(0.5) + ? widget.centerHighlightColor ?? + hoverColor.withValues(alpha: 0.5) : Colors.transparent, ), child: widget.child, @@ -150,7 +151,10 @@ class _DraggableViewItemState extends State { borderRadius: BorderRadius.circular(4.0), color: position == DraggableHoverPosition.center ? widget.centerHighlightColor ?? - Theme.of(context).colorScheme.secondary.withOpacity(0.5) + Theme.of(context) + .colorScheme + .secondary + .withValues(alpha: 0.5) : Colors.transparent, ), child: widget.child, diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_action_type.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_action_type.dart index fe2e5def48..d4f91b67d9 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_action_type.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_action_type.dart @@ -17,6 +17,14 @@ enum ViewMoreActionType { divider, lastModified, created, + lockPage; + + static const disableInLockedView = [ + delete, + rename, + moveTo, + changeIcon, + ]; } extension ViewMoreActionTypeExtension on ViewMoreActionType { @@ -42,6 +50,8 @@ extension ViewMoreActionTypeExtension on ViewMoreActionType { return LocaleKeys.disclosureAction_changeIcon.tr(); case ViewMoreActionType.collapseAllPages: return LocaleKeys.disclosureAction_collapseAllPages.tr(); + case ViewMoreActionType.lockPage: + return LocaleKeys.disclosureAction_lockPage.tr(); case ViewMoreActionType.divider: case ViewMoreActionType.lastModified: case ViewMoreActionType.created: @@ -69,6 +79,8 @@ extension ViewMoreActionTypeExtension on ViewMoreActionType { return FlowySvgs.change_icon_s; case ViewMoreActionType.collapseAllPages: return FlowySvgs.collapse_all_page_s; + case ViewMoreActionType.lockPage: + return FlowySvgs.lock_page_s; case ViewMoreActionType.divider: case ViewMoreActionType.lastModified: case ViewMoreActionType.copyLink: @@ -92,6 +104,7 @@ extension ViewMoreActionTypeExtension on ViewMoreActionType { case ViewMoreActionType.delete: case ViewMoreActionType.lastModified: case ViewMoreActionType.created: + case ViewMoreActionType.lockPage: return const SizedBox.shrink(); } } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_item.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_item.dart index d6b5dfe297..22182f7429 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_item.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_item.dart @@ -4,6 +4,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart'; import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/startup/plugin/plugin.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart'; @@ -20,6 +21,7 @@ import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type. import 'package:appflowy/workspace/presentation/home/menu/view/view_add_button.dart'; import 'package:appflowy/workspace/presentation/home/menu/view/view_more_action_button.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; +import 'package:appflowy/workspace/presentation/widgets/more_view_actions/widgets/lock_page_action.dart'; import 'package:appflowy/workspace/presentation/widgets/rename_view_popover.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; @@ -69,6 +71,7 @@ class ViewItem extends StatelessWidget { this.extendBuilder, this.disableSelectedStatus, this.shouldIgnoreView, + this.engagedInExpanding = false, this.enableRightClickContext = false, }); @@ -136,12 +139,17 @@ class ViewItem extends StatelessWidget { /// final bool enableRightClickContext; + /// to record the ViewBlock which is expanded or collapsed + final bool engagedInExpanding; + @override Widget build(BuildContext context) { return BlocProvider( - create: (_) => - ViewBloc(view: view, shouldLoadChildViews: shouldLoadChildViews) - ..add(const ViewEvent.initial()), + create: (_) => ViewBloc( + view: view, + shouldLoadChildViews: shouldLoadChildViews, + engagedInExpanding: engagedInExpanding, + )..add(const ViewEvent.initial()), child: BlocConsumer( listenWhen: (p, c) => c.lastCreatedView != null && @@ -183,6 +191,7 @@ class ViewItem extends StatelessWidget { isExpandedNotifier: isExpandedNotifier, extendBuilder: extendBuilder, shouldIgnoreView: shouldIgnoreView, + engagedInExpanding: engagedInExpanding, ); if (shouldIgnoreView?.call(view) == IgnoreViewType.disable) { @@ -235,6 +244,7 @@ class InnerViewItem extends StatefulWidget { this.isExpandedNotifier, required this.extendBuilder, this.disableSelectedStatus, + this.engagedInExpanding = false, required this.shouldIgnoreView, }); @@ -270,6 +280,7 @@ class InnerViewItem extends StatefulWidget { final PropertyValueNotifier? isExpandedNotifier; final List Function(ViewPB view)? extendBuilder; final IgnoreViewType Function(ViewPB view)? shouldIgnoreView; + final bool engagedInExpanding; @override State createState() => _InnerViewItemState(); @@ -345,6 +356,7 @@ class _InnerViewItemState extends State { rightIconsBuilder: widget.rightIconsBuilder, extendBuilder: widget.extendBuilder, shouldIgnoreView: widget.shouldIgnoreView, + engagedInExpanding: widget.engagedInExpanding, ); }).toList(); @@ -604,7 +616,7 @@ class _SingleInnerViewItemState extends State { offset: const Offset(0, 5), direction: PopoverDirection.bottomWithLeftAligned, popupBuilder: (_) => RenameViewPopover( - viewId: widget.view.id, + view: widget.view, name: widget.view.name, emoji: widget.view.icon.toEmojiIconData(), popoverController: popoverController, @@ -620,10 +632,14 @@ class _SingleInnerViewItemState extends State { Widget _buildViewIconButton() { final iconData = widget.view.icon.toEmojiIconData(); final icon = iconData.isNotEmpty - ? RawEmojiIconWidget(emoji: iconData, emojiSize: 16.0) + ? RawEmojiIconWidget( + emoji: iconData, + emojiSize: 16.0, + lineHeight: 18.0 / 16.0, + ) : Opacity(opacity: 0.6, child: widget.view.defaultIcon()); - return AppFlowyPopover( + final Widget child = AppFlowyPopover( offset: const Offset(20, 0), controller: controller, direction: PopoverDirection.rightWithCenterAligned, @@ -641,16 +657,31 @@ class _SingleInnerViewItemState extends State { popupBuilder: (context) { isIconPickerOpened = true; return FlowyIconEmojiPicker( - onSelectedEmoji: (result) { + initialType: iconData.type.toPickerTabType(), + tabs: const [ + PickerTabType.emoji, + PickerTabType.icon, + PickerTabType.custom, + ], + documentId: widget.view.id, + onSelectedEmoji: (r) { ViewBackendService.updateViewIcon( - viewId: widget.view.id, - viewIcon: result, + view: widget.view, + viewIcon: r.data, ); - controller.close(); + if (!r.keepOpen) controller.close(); }, ); }, ); + + if (widget.view.isLocked) { + return LockPageButtonWrapper( + child: child, + ); + } + + return child; } // > button or · button @@ -770,13 +801,12 @@ class _SingleInnerViewItemState extends State { context.read().add(const ViewEvent.collapseAllPages()); break; case ViewMoreActionType.changeIcon: - if (data is! EmojiIconData) { + if (data is! SelectedEmojiIconResult) { return; } - final result = data; await ViewBackendService.updateViewIcon( - viewId: widget.view.id, - viewIcon: result, + view: widget.view, + viewIcon: data.data, ); break; case ViewMoreActionType.moveTo: diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_more_action_button.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_more_action_button.dart index ba9a946cc2..5b531c2f28 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_more_action_button.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_more_action_button.dart @@ -1,9 +1,11 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart'; import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/move_to/move_page_menu.dart'; import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart'; +import 'package:appflowy/workspace/presentation/widgets/more_view_actions/widgets/lock_page_action.dart'; import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; @@ -53,15 +55,22 @@ class ViewMoreActionPopover extends StatelessWidget { List _buildActionTypeWrappers() { final actionTypes = _buildActionTypes(); - return actionTypes - .map( - (e) => ViewMoreActionTypeWrapper(e, view, (controller, data) { - onEditing(false); - onAction(e, data); - controller.close(); - }), - ) - .toList(); + return actionTypes.map( + (e) { + final actionWrapper = + ViewMoreActionTypeWrapper(e, view, (controller, data) { + onEditing(false); + onAction(e, data); + bool enableClose = true; + if (data is SelectedEmojiIconResult) { + if (data.keepOpen) enableClose = false; + } + if (enableClose) controller.close(); + }); + + return actionWrapper; + }, + ).toList(); } List _buildActionTypes() { @@ -139,19 +148,30 @@ class ViewMoreActionTypeWrapper extends CustomActionCell { PopoverController controller, PopoverMutex? mutex, ) { + Widget child; + if (inner == ViewMoreActionType.divider) { - return _buildDivider(); + child = _buildDivider(); } else if (inner == ViewMoreActionType.lastModified) { - return _buildLastModified(context); + child = _buildLastModified(context); } else if (inner == ViewMoreActionType.created) { - return _buildCreated(context); + child = _buildCreated(context); } else if (inner == ViewMoreActionType.changeIcon) { - return _buildEmojiActionButton(context, controller); + child = _buildEmojiActionButton(context, controller); } else if (inner == ViewMoreActionType.moveTo) { - return _buildMoveToActionButton(context, controller); + child = _buildMoveToActionButton(context, controller); + } else { + child = _buildNormalActionButton(context, controller); } - return _buildNormalActionButton(context, controller); + if (ViewMoreActionType.disableInLockedView.contains(inner) && + sourceView.isLocked) { + child = LockPageButtonWrapper( + child: child, + ); + } + + return child; } Widget _buildNormalActionButton( @@ -172,6 +192,13 @@ class ViewMoreActionTypeWrapper extends CustomActionCell { margin: const EdgeInsets.all(0), clickHandler: PopoverClickHandler.gestureDetector, popupBuilder: (_) => FlowyIconEmojiPicker( + tabs: const [ + PickerTabType.emoji, + PickerTabType.icon, + PickerTabType.custom, + ], + documentId: sourceView.id, + initialType: sourceView.icon.toEmojiIconData().type.toPickerTabType(), onSelectedEmoji: (result) => onTap(controller, result), ), child: child, @@ -184,7 +211,7 @@ class ViewMoreActionTypeWrapper extends CustomActionCell { ) { final userProfile = context.read().userProfile; // move to feature doesn't support in local mode - if (userProfile.authenticator != AuthenticatorPB.AppFlowyCloud) { + if (userProfile.workspaceAuthType != AuthTypePB.Server) { return const SizedBox.shrink(); } return BlocProvider.value( diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/about/app_version.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/about/app_version.dart new file mode 100644 index 0000000000..2125ea4b66 --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/about/app_version.dart @@ -0,0 +1,153 @@ +import 'package:appflowy/core/helpers/url_launcher.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/shared/version_checker/version_checker.dart'; +import 'package:appflowy/startup/tasks/device_info_task.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; + +class SettingsAppVersion extends StatelessWidget { + const SettingsAppVersion({ + super.key, + }); + + @override + Widget build(BuildContext context) { + return ApplicationInfo.isUpdateAvailable + ? const _UpdateAppSection() + : _buildIsUpToDate(context); + } + + Widget _buildIsUpToDate(BuildContext context) { + final theme = AppFlowyTheme.of(context); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + LocaleKeys.settings_accountPage_isUpToDate.tr(), + style: theme.textStyle.body.enhanced( + color: theme.textColorScheme.primary, + ), + ), + const VSpace(4), + Text( + LocaleKeys.settings_accountPage_officialVersion.tr( + namedArgs: { + 'version': ApplicationInfo.applicationVersion, + }, + ), + style: theme.textStyle.caption.standard( + color: theme.textColorScheme.secondary, + ), + ), + ], + ); + } +} + +class _UpdateAppSection extends StatelessWidget { + const _UpdateAppSection(); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded(child: _buildDescription(context)), + _buildUpdateButton(), + ], + ); + } + + Widget _buildUpdateButton() { + return PrimaryRoundedButton( + text: LocaleKeys.autoUpdate_settingsUpdateButton.tr(), + margin: const EdgeInsets.symmetric(horizontal: 24, vertical: 8), + fontWeight: FontWeight.w500, + radius: 8.0, + onTap: () { + Log.info('[AutoUpdater] Checking for updates'); + versionChecker.checkForUpdate(); + }, + ); + } + + Widget _buildDescription(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + _buildRedDot(), + const HSpace(6), + Flexible( + child: FlowyText.medium( + LocaleKeys.autoUpdate_settingsUpdateTitle.tr( + namedArgs: { + 'newVersion': ApplicationInfo.latestVersion, + }, + ), + figmaLineHeight: 17, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + const VSpace(4), + _buildCurrentVersionAndLatestVersion(context), + ], + ); + } + + Widget _buildCurrentVersionAndLatestVersion(BuildContext context) { + return Row( + children: [ + Flexible( + child: Opacity( + opacity: 0.7, + child: FlowyText.regular( + LocaleKeys.autoUpdate_settingsUpdateDescription.tr( + namedArgs: { + 'currentVersion': ApplicationInfo.applicationVersion, + 'newVersion': ApplicationInfo.latestVersion, + }, + ), + fontSize: 12, + figmaLineHeight: 13, + overflow: TextOverflow.ellipsis, + ), + ), + ), + const HSpace(6), + MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () { + afLaunchUrlString('https://www.appflowy.io/what-is-new'); + }, + child: FlowyText.regular( + LocaleKeys.autoUpdate_settingsUpdateWhatsNew.tr(), + decoration: TextDecoration.underline, + color: Theme.of(context).colorScheme.primary, + fontSize: 12, + figmaLineHeight: 13, + overflow: TextOverflow.ellipsis, + ), + ), + ), + ], + ); + } + + Widget _buildRedDot() { + return Container( + width: 8, + height: 8, + decoration: const BoxDecoration( + color: Color(0xFFFB006D), + shape: BoxShape.circle, + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/account/account_deletion.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/account/account_deletion.dart index 368cd19138..04d078ec0d 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/account/account_deletion.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/account/account_deletion.dart @@ -1,6 +1,6 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart'; +import 'package:appflowy/shared/loading.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/user/application/user_service.dart'; import 'package:appflowy/util/navigator_context_extension.dart'; @@ -8,13 +8,12 @@ import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_w import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_result/appflowy_result.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/size.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:universal_platform/universal_platform.dart'; -const _confirmText = 'DELETE MY ACCOUNT'; const _acceptableConfirmTexts = [ 'delete my account', 'deletemyaccount', @@ -44,43 +43,36 @@ class _AccountDeletionButtonState extends State { @override Widget build(BuildContext context) { - final textColor = Theme.of(context).brightness == Brightness.light - ? const Color(0xFF4F4F4F) - : const Color(0xFFB0B0B0); + final theme = AppFlowyTheme.of(context); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - FlowyText( + Text( LocaleKeys.button_deleteAccount.tr(), - fontSize: 14.0, - fontWeight: FontWeight.w500, - figmaLineHeight: 21.0, - color: textColor, + style: theme.textStyle.heading4.enhanced( + color: theme.textColorScheme.primary, + ), ), const VSpace(8), Row( children: [ - Flexible( - child: FlowyText.regular( + Expanded( + child: Text( LocaleKeys.newSettings_myAccount_deleteAccount_description.tr(), - fontSize: 12.0, - figmaLineHeight: 13.0, maxLines: 2, - color: textColor, + overflow: TextOverflow.ellipsis, + style: theme.textStyle.caption.standard( + color: theme.textColorScheme.secondary, + ), ), ), - const HSpace(32), - FlowyTextButton( - LocaleKeys.button_deleteAccount.tr(), - constraints: const BoxConstraints(minHeight: 32), - padding: const EdgeInsets.symmetric(horizontal: 26, vertical: 10), - fillColor: Colors.transparent, - radius: Corners.s8Border, - hoverColor: Theme.of(context).colorScheme.error.withOpacity(0.1), - fontColor: Theme.of(context).colorScheme.error, - fontSize: 12, - isDangerous: true, - onPressed: () { + AFOutlinedTextButton.destructive( + text: LocaleKeys.button_deleteAccount.tr(), + textStyle: theme.textStyle.body.standard( + color: theme.textColorScheme.error, + weight: FontWeight.w400, + ), + onTap: () { isCheckedNotifier.value = false; textEditingController.clear(); @@ -135,7 +127,8 @@ class _AccountDeletionDialog extends StatelessWidget { ), const VSpace(12.0), FlowyTextField( - hintText: _confirmText, + hintText: + LocaleKeys.newSettings_myAccount_deleteAccount_confirmHint3.tr(), controller: controller, ), const VSpace(16), @@ -176,7 +169,8 @@ class _AccountDeletionDialog extends StatelessWidget { bool _isConfirmTextValid(String text) { // don't convert the text to lower case or upper case, // just check if the text is in the list - return _acceptableConfirmTexts.contains(text); + return _acceptableConfirmTexts.contains(text) || + text == LocaleKeys.newSettings_myAccount_deleteAccount_confirmHint3.tr(); } Future deleteMyAccount( @@ -192,7 +186,6 @@ Future deleteMyAccount( if (!isChecked) { showToastNotification( - context, type: ToastificationType.warning, bottomPadding: bottomPadding, message: LocaleKeys @@ -207,7 +200,6 @@ Future deleteMyAccount( if (confirmText.isEmpty || !_isConfirmTextValid(confirmText)) { showToastNotification( - context, type: ToastificationType.warning, bottomPadding: bottomPadding, message: LocaleKeys @@ -225,7 +217,6 @@ Future deleteMyAccount( loading.stop(); showToastNotification( - context, message: LocaleKeys .newSettings_myAccount_deleteAccount_deleteAccountSuccess .tr(), @@ -244,7 +235,6 @@ Future deleteMyAccount( loading.stop(); showToastNotification( - context, type: ToastificationType.error, bottomPadding: bottomPadding, message: f.msg, diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/account/account_sign_in_out.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/account/account_sign_in_out.dart index 8d15c80181..78f1aaf16e 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/account/account_sign_in_out.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/account/account_sign_in_out.dart @@ -3,17 +3,55 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/user/application/auth/auth_service.dart'; +import 'package:appflowy/user/application/password/password_bloc.dart'; import 'package:appflowy/user/application/prelude.dart'; -import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/magic_link_sign_in_buttons.dart'; +import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/continue_with/continue_with_email_and_password.dart'; import 'package:appflowy/util/navigator_context_extension.dart'; +import 'package:appflowy/workspace/presentation/settings/pages/account/password/change_password.dart'; +import 'package:appflowy/workspace/presentation/settings/pages/account/password/setup_password.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/setting_third_party_login.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +class AccountSignInOutSection extends StatelessWidget { + const AccountSignInOutSection({ + super.key, + required this.userProfile, + required this.onAction, + this.signIn = true, + }); + + final UserProfilePB userProfile; + final VoidCallback onAction; + final bool signIn; + + @override + Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); + return Row( + children: [ + Text( + LocaleKeys.settings_accountPage_login_title.tr(), + style: theme.textStyle.body.enhanced( + color: theme.textColorScheme.primary, + ), + ), + const Spacer(), + AccountSignInOutButton( + userProfile: userProfile, + onAction: onAction, + signIn: signIn, + ), + ], + ); + } +} + class AccountSignInOutButton extends StatelessWidget { const AccountSignInOutButton({ super.key, @@ -28,13 +66,10 @@ class AccountSignInOutButton extends StatelessWidget { @override Widget build(BuildContext context) { - return PrimaryRoundedButton( + return AFFilledTextButton.primary( text: signIn ? LocaleKeys.settings_accountPage_login_loginLabel.tr() : LocaleKeys.settings_accountPage_login_logoutLabel.tr(), - margin: const EdgeInsets.symmetric(horizontal: 24, vertical: 16), - fontWeight: FontWeight.w600, - radius: 12.0, onTap: () => signIn ? _showSignInDialog(context) : _showLogoutDialog(context), ); @@ -44,9 +79,7 @@ class AccountSignInOutButton extends StatelessWidget { showConfirmDialog( context: context, title: LocaleKeys.settings_accountPage_login_logoutLabel.tr(), - description: userProfile.encryptionType == EncryptionTypePB.Symmetric - ? LocaleKeys.settings_menu_selfEncryptionLogoutPrompt.tr() - : LocaleKeys.settings_menu_logoutPrompt.tr(), + description: LocaleKeys.settings_menu_logoutPrompt.tr(), onConfirm: () async { await getIt().signOut(); onAction(); @@ -68,6 +101,94 @@ class AccountSignInOutButton extends StatelessWidget { } } +class ChangePasswordSection extends StatelessWidget { + const ChangePasswordSection({ + super.key, + required this.userProfile, + }); + + final UserProfilePB userProfile; + + @override + Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); + return BlocBuilder( + builder: (context, state) { + return Row( + children: [ + Text( + LocaleKeys.newSettings_myAccount_password_title.tr(), + style: theme.textStyle.body.enhanced( + color: theme.textColorScheme.primary, + ), + ), + const Spacer(), + state.hasPassword + ? AFFilledTextButton.primary( + text: LocaleKeys + .newSettings_myAccount_password_changePassword + .tr(), + onTap: () => _showChangePasswordDialog(context), + ) + : AFFilledTextButton.primary( + text: LocaleKeys + .newSettings_myAccount_password_setupPassword + .tr(), + onTap: () => _showSetPasswordDialog(context), + ), + ], + ); + }, + ); + } + + Future _showChangePasswordDialog(BuildContext context) async { + final theme = AppFlowyTheme.of(context); + await showDialog( + context: context, + builder: (_) => MultiBlocProvider( + providers: [ + BlocProvider.value( + value: context.read(), + ), + BlocProvider.value( + value: getIt(), + ), + ], + child: Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(theme.borderRadius.xl), + ), + child: ChangePasswordDialogContent( + userProfile: userProfile, + ), + ), + ), + ); + } + + Future _showSetPasswordDialog(BuildContext context) async { + await showDialog( + context: context, + builder: (_) => MultiBlocProvider( + providers: [ + BlocProvider.value( + value: context.read(), + ), + BlocProvider.value( + value: getIt(), + ), + ], + child: Dialog( + child: SetupPasswordDialogContent( + userProfile: userProfile, + ), + ), + ), + ); + } +} + class _SignInDialogContent extends StatelessWidget { const _SignInDialogContent(); @@ -83,7 +204,7 @@ class _SignInDialogContent extends StatelessWidget { const _DialogHeader(), const _DialogTitle(), const VSpace(16), - const SignInWithMagicLinkButtons(), + const ContinueWithEmailAndPassword(), if (isAuthEnabled) ...[ const VSpace(20), const _OrDivider(), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/account/account_user_profile.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/account/account_user_profile.dart index bd08501ae4..62a6232c4a 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/account/account_user_profile.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/account/account_user_profile.dart @@ -4,6 +4,7 @@ import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; import 'package:appflowy/workspace/application/user/settings_user_bloc.dart'; import 'package:appflowy/workspace/presentation/settings/shared/settings_input_field.dart'; import 'package:appflowy/workspace/presentation/widgets/user_avatar.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; @@ -96,27 +97,29 @@ class _AccountUserProfileState extends State { } Widget _buildNameDisplay() { + final theme = AppFlowyTheme.of(context); return Padding( padding: const EdgeInsets.only(top: 12), child: Row( mainAxisSize: MainAxisSize.min, children: [ Flexible( - child: FlowyText.medium( + child: Text( widget.name, overflow: TextOverflow.ellipsis, + style: theme.textStyle.body.standard( + color: theme.textColorScheme.primary, + ), ), ), const HSpace(4), - GestureDetector( - behavior: HitTestBehavior.opaque, + AFGhostButton.normal( + size: AFButtonSize.s, + padding: EdgeInsets.all(theme.spacing.xs), onTap: () => setState(() => isEditing = true), - child: const FlowyHover( - resetHoverOnRebuild: false, - child: Padding( - padding: EdgeInsets.all(4), - child: FlowySvg(FlowySvgs.edit_s), - ), + builder: (context, isHovering, disabled) => FlowySvg( + FlowySvgs.toolbar_link_edit_m, + size: const Size.square(20), ), ), ], diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/account/email/email_section.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/account/email/email_section.dart new file mode 100644 index 0000000000..d606f870ff --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/account/email/email_section.dart @@ -0,0 +1,38 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/widgets.dart'; + +class SettingsEmailSection extends StatelessWidget { + const SettingsEmailSection({ + super.key, + required this.userProfile, + }); + + final UserProfilePB userProfile; + + @override + Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + LocaleKeys.settings_accountPage_email_title.tr(), + style: theme.textStyle.body.enhanced( + color: theme.textColorScheme.primary, + ), + ), + VSpace(theme.spacing.s), + Text( + userProfile.email, + style: theme.textStyle.body.standard( + color: theme.textColorScheme.secondary, + ), + ), + ], + ); + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/account/password/change_password.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/account/password/change_password.dart new file mode 100644 index 0000000000..194254c869 --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/account/password/change_password.dart @@ -0,0 +1,330 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/user/application/password/password_bloc.dart'; +import 'package:appflowy/workspace/presentation/settings/pages/account/password/password_suffix_icon.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; +import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class ChangePasswordDialogContent extends StatefulWidget { + const ChangePasswordDialogContent({ + super.key, + required this.userProfile, + }); + + final UserProfilePB userProfile; + + @override + State createState() => + _ChangePasswordDialogContentState(); +} + +class _ChangePasswordDialogContentState + extends State { + final currentPasswordTextFieldKey = GlobalKey(); + final newPasswordTextFieldKey = GlobalKey(); + final confirmPasswordTextFieldKey = GlobalKey(); + + final currentPasswordController = TextEditingController(); + final newPasswordController = TextEditingController(); + final confirmPasswordController = TextEditingController(); + + final iconSize = 20.0; + + @override + void dispose() { + currentPasswordController.dispose(); + newPasswordController.dispose(); + confirmPasswordController.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); + return BlocListener( + listener: _onPasswordStateChanged, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), + constraints: const BoxConstraints(maxWidth: 400), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(theme.borderRadius.xl), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildTitle(context), + VSpace(theme.spacing.l), + ..._buildCurrentPasswordFields(context), + VSpace(theme.spacing.l), + ..._buildNewPasswordFields(context), + VSpace(theme.spacing.l), + ..._buildConfirmPasswordFields(context), + VSpace(theme.spacing.l), + _buildSubmitButton(context), + ], + ), + ), + ); + } + + Widget _buildTitle(BuildContext context) { + final theme = AppFlowyTheme.of(context); + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Change password', + style: theme.textStyle.heading4.prominent( + color: theme.textColorScheme.primary, + ), + ), + const Spacer(), + AFGhostButton.normal( + size: AFButtonSize.s, + padding: EdgeInsets.all(theme.spacing.xs), + onTap: () => Navigator.of(context).pop(), + builder: (context, isHovering, disabled) => FlowySvg( + FlowySvgs.password_close_m, + size: const Size.square(20), + ), + ), + ], + ); + } + + List _buildCurrentPasswordFields(BuildContext context) { + final theme = AppFlowyTheme.of(context); + return [ + Text( + LocaleKeys.newSettings_myAccount_password_currentPassword.tr(), + style: theme.textStyle.caption.enhanced( + color: theme.textColorScheme.secondary, + ), + ), + VSpace(theme.spacing.xs), + AFTextField( + key: currentPasswordTextFieldKey, + controller: currentPasswordController, + hintText: LocaleKeys + .newSettings_myAccount_password_hint_enterYourCurrentPassword + .tr(), + keyboardType: TextInputType.visiblePassword, + obscureText: true, + suffixIconConstraints: BoxConstraints.tightFor( + width: iconSize + theme.spacing.m, + height: iconSize, + ), + suffixIconBuilder: (context, isObscured) => PasswordSuffixIcon( + isObscured: isObscured, + onTap: () { + currentPasswordTextFieldKey.currentState?.syncObscured(!isObscured); + }, + ), + ), + ]; + } + + List _buildNewPasswordFields(BuildContext context) { + final theme = AppFlowyTheme.of(context); + return [ + Text( + LocaleKeys.newSettings_myAccount_password_newPassword.tr(), + style: theme.textStyle.caption.enhanced( + color: theme.textColorScheme.secondary, + ), + ), + VSpace(theme.spacing.xs), + AFTextField( + key: newPasswordTextFieldKey, + controller: newPasswordController, + hintText: LocaleKeys + .newSettings_myAccount_password_hint_enterYourNewPassword + .tr(), + keyboardType: TextInputType.visiblePassword, + obscureText: true, + suffixIconConstraints: BoxConstraints.tightFor( + width: iconSize + theme.spacing.m, + height: iconSize, + ), + suffixIconBuilder: (context, isObscured) => PasswordSuffixIcon( + isObscured: isObscured, + onTap: () { + newPasswordTextFieldKey.currentState?.syncObscured(!isObscured); + }, + ), + ), + ]; + } + + List _buildConfirmPasswordFields(BuildContext context) { + final theme = AppFlowyTheme.of(context); + return [ + Text( + LocaleKeys.newSettings_myAccount_password_confirmNewPassword.tr(), + style: theme.textStyle.caption.enhanced( + color: theme.textColorScheme.secondary, + ), + ), + VSpace(theme.spacing.xs), + AFTextField( + key: confirmPasswordTextFieldKey, + controller: confirmPasswordController, + hintText: LocaleKeys + .newSettings_myAccount_password_hint_confirmYourNewPassword + .tr(), + keyboardType: TextInputType.visiblePassword, + obscureText: true, + suffixIconConstraints: BoxConstraints.tightFor( + width: iconSize + theme.spacing.m, + height: iconSize, + ), + suffixIconBuilder: (context, isObscured) => PasswordSuffixIcon( + isObscured: isObscured, + onTap: () { + confirmPasswordTextFieldKey.currentState?.syncObscured(!isObscured); + }, + ), + ), + ]; + } + + Widget _buildSubmitButton(BuildContext context) { + final theme = AppFlowyTheme.of(context); + return Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + AFOutlinedTextButton.normal( + text: LocaleKeys.button_cancel.tr(), + textStyle: theme.textStyle.body.standard( + color: theme.textColorScheme.primary, + weight: FontWeight.w400, + ), + onTap: () => Navigator.of(context).pop(), + ), + const HSpace(16), + AFFilledTextButton.primary( + text: LocaleKeys.button_save.tr(), + textStyle: theme.textStyle.body.standard( + color: theme.textColorScheme.onFill, + weight: FontWeight.w400, + ), + onTap: () => _save(context), + ), + ], + ); + } + + void _save(BuildContext context) async { + _resetError(); + + final currentPassword = currentPasswordController.text; + final newPassword = newPasswordController.text; + final confirmPassword = confirmPasswordController.text; + + if (newPassword.isEmpty) { + newPasswordTextFieldKey.currentState?.syncError( + errorText: LocaleKeys + .newSettings_myAccount_password_error_newPasswordIsRequired + .tr(), + ); + return; + } + + if (confirmPassword.isEmpty) { + confirmPasswordTextFieldKey.currentState?.syncError( + errorText: LocaleKeys + .newSettings_myAccount_password_error_confirmPasswordIsRequired + .tr(), + ); + return; + } + + if (newPassword != confirmPassword) { + confirmPasswordTextFieldKey.currentState?.syncError( + errorText: LocaleKeys + .newSettings_myAccount_password_error_passwordsDoNotMatch + .tr(), + ); + return; + } + + if (newPassword == currentPassword) { + newPasswordTextFieldKey.currentState?.syncError( + errorText: LocaleKeys + .newSettings_myAccount_password_error_newPasswordIsSameAsCurrent + .tr(), + ); + return; + } + + // all the verification passed, save the new password + context.read().add( + PasswordEvent.changePassword( + oldPassword: currentPassword, + newPassword: newPassword, + ), + ); + } + + void _resetError() { + currentPasswordTextFieldKey.currentState?.clearError(); + newPasswordTextFieldKey.currentState?.clearError(); + confirmPasswordTextFieldKey.currentState?.clearError(); + } + + void _onPasswordStateChanged(BuildContext context, PasswordState state) { + bool hasError = false; + String message = ''; + String description = ''; + + final changePasswordResult = state.changePasswordResult; + final setPasswordResult = state.setupPasswordResult; + + if (changePasswordResult != null) { + changePasswordResult.fold( + (success) { + message = LocaleKeys + .newSettings_myAccount_password_toast_passwordUpdatedSuccessfully + .tr(); + }, + (error) { + hasError = true; + message = LocaleKeys + .newSettings_myAccount_password_toast_passwordUpdatedFailed + .tr(); + description = error.msg; + }, + ); + } else if (setPasswordResult != null) { + setPasswordResult.fold( + (success) { + message = LocaleKeys + .newSettings_myAccount_password_toast_passwordSetupSuccessfully + .tr(); + }, + (error) { + hasError = true; + message = LocaleKeys + .newSettings_myAccount_password_toast_passwordSetupFailed + .tr(); + description = error.msg; + }, + ); + } + + if (!state.isSubmitting && message.isNotEmpty) { + showToastNotification( + message: message, + description: description, + type: hasError ? ToastificationType.error : ToastificationType.success, + ); + } + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/account/password/password_suffix_icon.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/account/password/password_suffix_icon.dart new file mode 100644 index 0000000000..5417b1a0eb --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/account/password/password_suffix_icon.dart @@ -0,0 +1,30 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:flutter/material.dart'; + +class PasswordSuffixIcon extends StatelessWidget { + const PasswordSuffixIcon({ + super.key, + required this.isObscured, + required this.onTap, + }); + + final bool isObscured; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); + return Padding( + padding: EdgeInsets.only(right: theme.spacing.m), + child: GestureDetector( + onTap: onTap, + child: FlowySvg( + isObscured ? FlowySvgs.show_s : FlowySvgs.hide_s, + color: theme.textColorScheme.secondary, + size: const Size.square(20), + ), + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/account/password/setup_password.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/account/password/setup_password.dart new file mode 100644 index 0000000000..2fdfd8b934 --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/account/password/setup_password.dart @@ -0,0 +1,254 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/user/application/password/password_bloc.dart'; +import 'package:appflowy/workspace/presentation/settings/pages/account/password/password_suffix_icon.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; +import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class SetupPasswordDialogContent extends StatefulWidget { + const SetupPasswordDialogContent({ + super.key, + required this.userProfile, + }); + + final UserProfilePB userProfile; + + @override + State createState() => + _SetupPasswordDialogContentState(); +} + +class _SetupPasswordDialogContentState + extends State { + final passwordTextFieldKey = GlobalKey(); + final confirmPasswordTextFieldKey = GlobalKey(); + + final passwordController = TextEditingController(); + final confirmPasswordController = TextEditingController(); + + final iconSize = 20.0; + + @override + void dispose() { + passwordController.dispose(); + confirmPasswordController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); + return BlocListener( + listener: _onPasswordStateChanged, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), + constraints: const BoxConstraints(maxWidth: 400), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildTitle(context), + VSpace(theme.spacing.l), + ..._buildPasswordFields(context), + VSpace(theme.spacing.l), + ..._buildConfirmPasswordFields(context), + VSpace(theme.spacing.l), + _buildSubmitButton(context), + ], + ), + ), + ); + } + + Widget _buildTitle(BuildContext context) { + final theme = AppFlowyTheme.of(context); + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + LocaleKeys.newSettings_myAccount_password_setupPassword.tr(), + style: theme.textStyle.heading4.prominent( + color: theme.textColorScheme.primary, + ), + ), + const Spacer(), + AFGhostButton.normal( + size: AFButtonSize.s, + padding: EdgeInsets.all(theme.spacing.xs), + onTap: () => Navigator.of(context).pop(), + builder: (context, isHovering, disabled) => FlowySvg( + FlowySvgs.password_close_m, + size: const Size.square(20), + ), + ), + ], + ); + } + + List _buildPasswordFields(BuildContext context) { + final theme = AppFlowyTheme.of(context); + return [ + Text( + 'Password', + style: theme.textStyle.caption.enhanced( + color: theme.textColorScheme.secondary, + ), + ), + VSpace(theme.spacing.xs), + AFTextField( + key: passwordTextFieldKey, + controller: passwordController, + hintText: 'Enter your password', + keyboardType: TextInputType.visiblePassword, + obscureText: true, + suffixIconConstraints: BoxConstraints.tightFor( + width: iconSize + theme.spacing.m, + height: iconSize, + ), + suffixIconBuilder: (context, isObscured) => PasswordSuffixIcon( + isObscured: isObscured, + onTap: () { + passwordTextFieldKey.currentState?.syncObscured(!isObscured); + }, + ), + ), + ]; + } + + List _buildConfirmPasswordFields(BuildContext context) { + final theme = AppFlowyTheme.of(context); + return [ + Text( + 'Confirm password', + style: theme.textStyle.caption.enhanced( + color: theme.textColorScheme.secondary, + ), + ), + VSpace(theme.spacing.xs), + AFTextField( + key: confirmPasswordTextFieldKey, + controller: confirmPasswordController, + hintText: 'Confirm your password', + keyboardType: TextInputType.visiblePassword, + obscureText: true, + suffixIconConstraints: BoxConstraints.tightFor( + width: iconSize + theme.spacing.m, + height: iconSize, + ), + suffixIconBuilder: (context, isObscured) => PasswordSuffixIcon( + isObscured: isObscured, + onTap: () { + confirmPasswordTextFieldKey.currentState?.syncObscured(!isObscured); + }, + ), + ), + ]; + } + + Widget _buildSubmitButton(BuildContext context) { + final theme = AppFlowyTheme.of(context); + return Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + AFOutlinedTextButton.normal( + text: 'Cancel', + textStyle: theme.textStyle.body.standard( + color: theme.textColorScheme.primary, + weight: FontWeight.w400, + ), + onTap: () => Navigator.of(context).pop(), + ), + const HSpace(16), + AFFilledTextButton.primary( + text: 'Save', + textStyle: theme.textStyle.body.standard( + color: theme.textColorScheme.onFill, + weight: FontWeight.w400, + ), + onTap: () => _save(context), + ), + ], + ); + } + + void _save(BuildContext context) async { + _resetError(); + + final password = passwordController.text; + final confirmPassword = confirmPasswordController.text; + + if (password.isEmpty) { + passwordTextFieldKey.currentState?.syncError( + errorText: LocaleKeys + .newSettings_myAccount_password_error_newPasswordIsRequired + .tr(), + ); + return; + } + + if (confirmPassword.isEmpty) { + confirmPasswordTextFieldKey.currentState?.syncError( + errorText: LocaleKeys + .newSettings_myAccount_password_error_confirmPasswordIsRequired + .tr(), + ); + return; + } + + if (password != confirmPassword) { + confirmPasswordTextFieldKey.currentState?.syncError( + errorText: LocaleKeys + .newSettings_myAccount_password_error_passwordsDoNotMatch + .tr(), + ); + return; + } + + // all the verification passed, save the password + context.read().add( + PasswordEvent.setupPassword( + newPassword: password, + ), + ); + } + + void _resetError() { + passwordTextFieldKey.currentState?.clearError(); + confirmPasswordTextFieldKey.currentState?.clearError(); + } + + void _onPasswordStateChanged(BuildContext context, PasswordState state) { + bool hasError = false; + String message = ''; + String description = ''; + + final setPasswordResult = state.setupPasswordResult; + + if (setPasswordResult != null) { + setPasswordResult.fold( + (success) { + message = 'Password set'; + description = 'Your password has been set'; + }, + (error) { + hasError = true; + message = 'Failed to set password'; + description = error.msg; + }, + ); + } + + if (!state.isSubmitting && message.isNotEmpty) { + showToastNotification( + message: message, + description: description, + type: hasError ? ToastificationType.error : ToastificationType.success, + ); + } + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/downloading_model.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/downloading_model.dart deleted file mode 100644 index a7ed782aea..0000000000 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/downloading_model.dart +++ /dev/null @@ -1,119 +0,0 @@ -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/workspace/application/settings/ai/download_model_bloc.dart'; -import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/theme_extension.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:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:percent_indicator/linear_percent_indicator.dart'; - -class DownloadingIndicator extends StatelessWidget { - const DownloadingIndicator({ - required this.llmModel, - required this.onCancel, - required this.onFinish, - super.key, - }); - final LLMModelPB llmModel; - final VoidCallback onCancel; - final VoidCallback onFinish; - - @override - Widget build(BuildContext context) { - return BlocProvider( - create: (context) => - DownloadModelBloc(llmModel)..add(const DownloadModelEvent.started()), - child: BlocListener( - listener: (context, state) { - if (state.isFinish) { - onFinish(); - } - }, - child: DecoratedBox( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surfaceContainerHighest, - borderRadius: BorderRadius.circular(8), - ), - child: BlocBuilder( - builder: (context, state) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - DownloadingProgressBar(onCancel: onCancel), - if (state.bigFileDownloadPrompt != null) ...[ - const VSpace(2), - Opacity( - opacity: 0.6, - child: - FlowyText(state.bigFileDownloadPrompt!, fontSize: 11), - ), - ], - ], - ); - }, - ), - ), - ), - ); - } -} - -class DownloadingProgressBar extends StatelessWidget { - const DownloadingProgressBar({required this.onCancel, super.key}); - - final VoidCallback onCancel; - - @override - Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Opacity( - opacity: 0.6, - child: FlowyText( - "${LocaleKeys.settings_aiPage_keys_downloadingModel.tr()}: ${state.object}", - fontSize: 11, - ), - ), - IntrinsicHeight( - child: Row( - children: [ - Expanded( - child: LinearPercentIndicator( - lineHeight: 9.0, - percent: state.percent, - padding: EdgeInsets.zero, - progressColor: AFThemeExtension.of(context).success, - backgroundColor: - AFThemeExtension.of(context).progressBarBGColor, - barRadius: const Radius.circular(8), - trailing: FlowyText( - "${(state.percent * 100).toStringAsFixed(0)}%", - fontSize: 11, - color: AFThemeExtension.of(context).success, - ), - ), - ), - const HSpace(12), - FlowyButton( - useIntrinsicWidth: true, - text: FlowyText( - LocaleKeys.button_cancel.tr(), - fontSize: 11, - ), - onTap: onCancel, - ), - ], - ), - ), - ], - ); - }, - ); - } -} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/init_local_ai.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/init_local_ai.dart deleted file mode 100644 index d924b46825..0000000000 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/init_local_ai.dart +++ /dev/null @@ -1,78 +0,0 @@ -import 'package:appflowy/generated/flowy_svgs.g.dart'; -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/workspace/application/settings/ai/local_ai_chat_bloc.dart'; -import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra_ui/style_widget/text.dart'; -import 'package:flowy_infra_ui/widget/spacing.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -class InitLocalAIIndicator extends StatelessWidget { - const InitLocalAIIndicator({super.key}); - - @override - Widget build(BuildContext context) { - return DecoratedBox( - decoration: const BoxDecoration( - color: Color(0xFFEDF7ED), - borderRadius: BorderRadius.all( - Radius.circular(4), - ), - ), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4), - child: BlocBuilder( - builder: (context, state) { - switch (state.runningState) { - case RunningStatePB.Connecting: - case RunningStatePB.Connected: - return Row( - children: [ - const HSpace(8), - FlowyText( - LocaleKeys.settings_aiPage_keys_localAILoading.tr(), - fontSize: 11, - color: const Color(0xFF1E4620), - ), - ], - ); - case RunningStatePB.Running: - return SizedBox( - height: 30, - child: Row( - children: [ - const HSpace(8), - const FlowySvg( - FlowySvgs.download_success_s, - color: Color(0xFF2E7D32), - ), - const HSpace(6), - FlowyText( - LocaleKeys.settings_aiPage_keys_localAILoaded.tr(), - fontSize: 11, - color: const Color(0xFF1E4620), - ), - ], - ), - ); - case RunningStatePB.Stopped: - return Row( - children: [ - const HSpace(8), - FlowyText( - LocaleKeys.settings_aiPage_keys_localAIStopped.tr(), - fontSize: 11, - color: const Color(0xFFC62828), - ), - ], - ); - default: - return const SizedBox.shrink(); - } - }, - ), - ), - ); - } -} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/local_ai_chat_setting.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/local_ai_chat_setting.dart deleted file mode 100644 index 9cb3a17d88..0000000000 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/local_ai_chat_setting.dart +++ /dev/null @@ -1,370 +0,0 @@ -import 'package:appflowy/generated/flowy_svgs.g.dart'; -import 'package:appflowy/workspace/application/settings/ai/local_ai_chat_bloc.dart'; -import 'package:appflowy/workspace/application/settings/ai/local_ai_chat_toggle_bloc.dart'; -import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart'; -import 'package:appflowy/workspace/presentation/settings/pages/setting_ai_view/downloading_model.dart'; -import 'package:appflowy/workspace/presentation/settings/pages/setting_ai_view/init_local_ai.dart'; -import 'package:appflowy/workspace/presentation/settings/pages/setting_ai_view/plugin_state.dart'; -import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; -import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart'; -import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart'; -import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; -import 'package:expandable/expandable.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/material.dart'; - -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/workspace/presentation/settings/shared/af_dropdown_menu_entry.dart'; -import 'package:appflowy/workspace/presentation/settings/shared/settings_dropdown.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -class LocalAIChatSetting extends StatelessWidget { - const LocalAIChatSetting({super.key}); - - @override - Widget build(BuildContext context) { - return MultiBlocProvider( - providers: [ - BlocProvider(create: (context) => LocalAIChatSettingBloc()), - BlocProvider( - create: (context) => LocalAIChatToggleBloc() - ..add(const LocalAIChatToggleEvent.started()), - ), - ], - child: ExpandableNotifier( - child: BlocListener( - listener: (context, state) { - // Listen to the toggle state and expand the panel if the state is ready. - final controller = ExpandableController.of( - context, - required: true, - )!; - - // Neet to wrap with WidgetsBinding.instance.addPostFrameCallback otherwise the - // ExpandablePanel not expanded sometimes. Maybe because the ExpandablePanel is not - // built yet when the listener is called. - WidgetsBinding.instance.addPostFrameCallback( - (_) { - state.pageIndicator.when( - error: (_) => controller.expanded = false, - ready: (enabled) { - controller.expanded = enabled; - context.read().add( - const LocalAIChatSettingEvent.refreshAISetting(), - ); - }, - loading: () => controller.expanded = false, - ); - }, - debugLabel: 'LocalAI.showLocalAIChatSetting', - ); - }, - child: ExpandablePanel( - theme: const ExpandableThemeData( - headerAlignment: ExpandablePanelHeaderAlignment.center, - tapBodyToCollapse: false, - hasIcon: false, - tapBodyToExpand: false, - tapHeaderToExpand: false, - ), - header: const SizedBox.shrink(), - collapsed: const SizedBox.shrink(), - expanded: Padding( - padding: const EdgeInsets.symmetric(vertical: 6), - // child: _LocalLLMInfoWidget(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - BlocBuilder( - builder: (context, state) { - // If the progress indicator is startOfflineAIApp, then don't show the LLM model. - if (state.progressIndicator == - const LocalAIProgress.startOfflineAIApp()) { - return const SizedBox.shrink(); - } else { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: FlowyText.medium( - LocaleKeys.settings_aiPage_keys_llmModel.tr(), - fontSize: 14, - ), - ), - const Spacer(), - state.aiModelProgress.when( - init: () => const SizedBox.shrink(), - loading: () { - return const Expanded( - child: Row( - children: [ - Spacer(), - CircularProgressIndicator.adaptive(), - ], - ), - ); - }, - finish: (err) => (err == null) - ? const _SelectLocalModelDropdownMenu() - : const SizedBox.shrink(), - ), - ], - ); - } - }, - ), - const IntrinsicHeight(child: _LocalAIStateWidget()), - ], - ), - ), - ), - ), - ), - ); - } -} - -class LocalAIChatSettingHeader extends StatelessWidget { - const LocalAIChatSettingHeader({super.key}); - - @override - Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - return state.pageIndicator.when( - error: (error) { - return const SizedBox.shrink(); - }, - loading: () { - return Row( - children: [ - FlowyText( - LocaleKeys.settings_aiPage_keys_localAIStart.tr(), - ), - const Spacer(), - const CircularProgressIndicator.adaptive(), - const HSpace(8), - ], - ); - }, - ready: (isEnabled) { - return Row( - children: [ - const FlowyText('Enable Local AI Chat'), - const Spacer(), - Toggle( - value: isEnabled, - onChanged: (_) { - context - .read() - .add(const LocalAIChatToggleEvent.toggle()); - }, - ), - ], - ); - }, - ); - }, - ); - } -} - -class _SelectLocalModelDropdownMenu extends StatelessWidget { - const _SelectLocalModelDropdownMenu(); - - @override - Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - return Flexible( - child: SettingsDropdown( - key: const Key('_SelectLocalModelDropdownMenu'), - onChanged: (model) => context.read().add( - LocalAIChatSettingEvent.selectLLMConfig(model), - ), - selectedOption: state.selectedLLMModel!, - options: state.models - .map( - (llm) => buildDropdownMenuEntry( - context, - value: llm, - label: llm.chatModel, - ), - ) - .toList(), - ), - ); - }, - ); - } -} - -class _LocalAIStateWidget extends StatelessWidget { - const _LocalAIStateWidget(); - - @override - Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - final error = errorFromState(state); - if (error == null) { - // If the error is null, handle selected llm model. - if (state.progressIndicator != null) { - final child = state.progressIndicator!.when( - showDownload: ( - LocalModelResourcePB llmResource, - LLMModelPB llmModel, - ) => - _ShowDownloadIndicator( - llmResource: llmResource, - llmModel: llmModel, - ), - startDownloading: (llmModel) { - return DownloadingIndicator( - key: UniqueKey(), - llmModel: llmModel, - onFinish: () => context - .read() - .add(const LocalAIChatSettingEvent.finishDownload()), - onCancel: () => context - .read() - .add(const LocalAIChatSettingEvent.cancelDownload()), - ); - }, - finishDownload: () => const InitLocalAIIndicator(), - checkPluginState: () => const PluginStateIndicator(), - startOfflineAIApp: () => OpenOrDownloadOfflineAIApp( - onRetry: () { - context - .read() - .add(const LocalAIChatSettingEvent.refreshAISetting()); - }, - ), - ); - - return Padding( - padding: const EdgeInsets.only(top: 8), - child: child, - ); - } else { - return const SizedBox.shrink(); - } - } else { - return Opacity( - opacity: 0.5, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 6), - child: FlowyText( - error.msg, - maxLines: 10, - ), - ), - ); - } - }, - ); - } - - FlowyError? errorFromState(LocalAIChatSettingState state) { - final err = state.aiModelProgress.when( - loading: () => null, - finish: (err) => err, - init: () {}, - ); - - if (err == null) { - state.selectLLMState.when( - loading: () => null, - finish: (err) => err, - ); - } - - return err; - } -} - -void _showDownloadDialog( - BuildContext context, - LocalModelResourcePB llmResource, - LLMModelPB llmModel, -) { - if (llmResource.pendingResources.isEmpty) { - return; - } - - final res = llmResource.pendingResources.first; - String desc = ""; - switch (res.resType) { - case PendingResourceTypePB.AIModel: - desc = LocaleKeys.settings_aiPage_keys_downloadLLMPromptDetail.tr( - args: [ - llmResource.pendingResources[0].name, - llmResource.pendingResources[0].fileSize, - ], - ); - break; - case PendingResourceTypePB.OfflineApp: - desc = LocaleKeys.settings_aiPage_keys_downloadAppFlowyOfflineAI.tr(); - break; - } - - showConfirmDialog( - context: context, - style: ConfirmPopupStyle.cancelAndOk, - title: LocaleKeys.settings_aiPage_keys_downloadLLMPrompt.tr( - args: [res.name], - ), - description: desc, - confirmLabel: LocaleKeys.button_confirm.tr(), - onConfirm: () => context.read().add( - LocalAIChatSettingEvent.startDownloadModel( - llmModel, - ), - ), - onCancel: () => context.read().add( - const LocalAIChatSettingEvent.cancelDownload(), - ), - ); -} - -class _ShowDownloadIndicator extends StatelessWidget { - const _ShowDownloadIndicator({ - required this.llmResource, - required this.llmModel, - }); - final LocalModelResourcePB llmResource; - final LLMModelPB llmModel; - - @override - Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - return Row( - children: [ - const Spacer(), - IntrinsicWidth( - child: SizedBox( - height: 30, - child: FlowyButton( - text: FlowyText( - LocaleKeys.settings_aiPage_keys_downloadAIModelButton.tr(), - fontSize: 14, - color: const Color(0xFF005483), - ), - leftIcon: const FlowySvg( - FlowySvgs.local_model_download_s, - color: Color(0xFF005483), - ), - onTap: () { - _showDownloadDialog(context, llmResource, llmModel); - }, - ), - ), - ), - ], - ); - }, - ); - } -} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/local_ai_setting.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/local_ai_setting.dart index f9dc6fa4d8..4992864f99 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/local_ai_setting.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/local_ai_setting.dart @@ -1,17 +1,17 @@ -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/workspace/application/settings/ai/local_ai_bloc.dart'; -import 'package:appflowy/workspace/application/settings/ai/settings_ai_bloc.dart'; -import 'package:appflowy/workspace/presentation/settings/pages/setting_ai_view/local_ai_chat_setting.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:expandable/expandable.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'ollama_setting.dart'; +import 'plugin_status_indicator.dart'; + class LocalAISetting extends StatefulWidget { const LocalAISetting({super.key}); @@ -20,117 +20,122 @@ class LocalAISetting extends StatefulWidget { } class _LocalAISettingState extends State { + final expandableController = ExpandableController(initialExpanded: false); + + @override + void dispose() { + expandableController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocProvider( + create: (context) => LocalAiPluginBloc(), + child: BlocConsumer( + listener: (context, state) { + expandableController.value = state.isEnabled; + }, + builder: (context, state) { + return ExpandablePanel( + controller: expandableController, + theme: ExpandableThemeData( + tapBodyToCollapse: false, + hasIcon: false, + tapBodyToExpand: false, + tapHeaderToExpand: false, + ), + header: LocalAiSettingHeader( + isEnabled: state.isEnabled, + ), + collapsed: const SizedBox.shrink(), + expanded: Padding( + padding: EdgeInsets.only(top: 12), + child: LocalAISettingPanel(), + ), + ); + }, + ), + ); + } +} + +class LocalAiSettingHeader extends StatelessWidget { + const LocalAiSettingHeader({ + super.key, + required this.isEnabled, + }); + + final bool isEnabled; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FlowyText.medium( + LocaleKeys.settings_aiPage_keys_localAIToggleTitle.tr(), + ), + const VSpace(4), + FlowyText( + LocaleKeys.settings_aiPage_keys_localAIToggleSubTitle.tr(), + maxLines: 3, + fontSize: 12, + ), + ], + ), + ), + Toggle( + value: isEnabled, + onChanged: (value) { + _onToggleChanged(value, context); + }, + ), + ], + ); + } + + void _onToggleChanged(bool value, BuildContext context) { + if (value) { + context.read().add(const LocalAiPluginEvent.toggle()); + } else { + showConfirmDialog( + context: context, + title: LocaleKeys.settings_aiPage_keys_disableLocalAITitle.tr(), + description: + LocaleKeys.settings_aiPage_keys_disableLocalAIDescription.tr(), + confirmLabel: LocaleKeys.button_confirm.tr(), + onConfirm: () { + context + .read() + .add(const LocalAiPluginEvent.toggle()); + }, + ); + } + } +} + +class LocalAISettingPanel extends StatelessWidget { + const LocalAISettingPanel({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( builder: (context, state) { - if (state.aiSettings == null) { + if (state is! ReadyLocalAiPluginState) { return const SizedBox.shrink(); } - return BlocProvider( - create: (context) => - LocalAIToggleBloc()..add(const LocalAIToggleEvent.started()), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 6), - child: ExpandableNotifier( - child: BlocListener( - listener: (context, state) { - final controller = - ExpandableController.of(context, required: true)!; - state.pageIndicator.when( - error: (_) => controller.expanded = false, - ready: (enabled) => controller.expanded = enabled, - loading: () => controller.expanded = false, - ); - }, - child: ExpandablePanel( - theme: const ExpandableThemeData( - headerAlignment: ExpandablePanelHeaderAlignment.center, - tapBodyToCollapse: false, - hasIcon: false, - tapBodyToExpand: false, - tapHeaderToExpand: false, - ), - header: const LocalAISettingHeader(), - collapsed: const SizedBox.shrink(), - expanded: Column( - children: [ - const VSpace(6), - DecoratedBox( - decoration: BoxDecoration( - color: Theme.of(context) - .colorScheme - .surfaceContainerHighest, - borderRadius: - const BorderRadius.all(Radius.circular(4)), - ), - child: const Padding( - padding: - EdgeInsets.symmetric(horizontal: 12, vertical: 6), - child: LocalAIChatSetting(), - ), - ), - ], - ), - ), - ), - ), - ), - ); - }, - ); - } -} - -class LocalAISettingHeader extends StatelessWidget { - const LocalAISettingHeader({super.key}); - - @override - Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - return state.pageIndicator.when( - error: (error) { - return const SizedBox.shrink(); - }, - loading: () { - return const CircularProgressIndicator.adaptive(); - }, - ready: (isEnabled) { - return Row( - children: [ - FlowyText( - LocaleKeys.settings_aiPage_keys_localAIToggleTitle.tr(), - ), - const Spacer(), - Toggle( - value: isEnabled, - onChanged: (_) { - if (isEnabled) { - showConfirmDialog( - context: context, - title: LocaleKeys - .settings_aiPage_keys_disableLocalAITitle - .tr(), - description: LocaleKeys - .settings_aiPage_keys_disableLocalAIDescription - .tr(), - confirmLabel: LocaleKeys.button_confirm.tr(), - onConfirm: () => context - .read() - .add(const LocalAIToggleEvent.toggle()), - ); - } else { - context - .read() - .add(const LocalAIToggleEvent.toggle()); - } - }, - ), - ], - ); - }, + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const LocalAIStatusIndicator(), + const VSpace(10), + OllamaSettingPage(), + ], ); }, ); diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/local_settings_ai_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/local_settings_ai_view.dart new file mode 100644 index 0000000000..e90c42444f --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/local_settings_ai_view.dart @@ -0,0 +1,34 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/workspace/application/settings/ai/settings_ai_bloc.dart'; +import 'package:appflowy/workspace/presentation/settings/pages/setting_ai_view/local_ai_setting.dart'; +import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart'; +import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class LocalSettingsAIView extends StatelessWidget { + const LocalSettingsAIView({ + super.key, + required this.userProfile, + required this.workspaceId, + }); + + final UserProfilePB userProfile; + final String workspaceId; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (_) => SettingsAIBloc(userProfile, workspaceId) + ..add(const SettingsAIEvent.started()), + child: SettingsBody( + title: LocaleKeys.settings_aiPage_title.tr(), + description: "", + children: [ + const LocalAISetting(), + ], + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/model_selection.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/model_selection.dart index 22aaf0bcca..7357c2951c 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/model_selection.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/model_selection.dart @@ -1,47 +1,65 @@ +import 'package:appflowy/ai/ai.dart'; +import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart'; import 'package:flutter/material.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/workspace/application/settings/ai/settings_ai_bloc.dart'; import 'package:appflowy/workspace/presentation/settings/shared/af_dropdown_menu_entry.dart'; import 'package:appflowy/workspace/presentation/settings/shared/settings_dropdown.dart'; -import 'package:appflowy_backend/log.dart'; -import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class AIModelSelection extends StatelessWidget { const AIModelSelection({super.key}); + static const double height = 49; @override Widget build(BuildContext context) { return BlocBuilder( + buildWhen: (previous, current) => + previous.availableModels != current.availableModels, builder: (context, state) { + final models = state.availableModels?.models; + if (models == null) { + return const SizedBox( + // Using same height as SettingsDropdown to avoid layout shift + height: height, + ); + } + + final localModels = models.where((model) => model.isLocal).toList(); + final cloudModels = models.where((model) => !model.isLocal).toList(); + final selectedModel = state.availableModels!.selectedModel; + return Padding( padding: const EdgeInsets.symmetric(vertical: 6), child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Flexible( + Expanded( child: FlowyText.medium( LocaleKeys.settings_aiPage_keys_llmModelType.tr(), - fontSize: 14, + overflow: TextOverflow.ellipsis, ), ), - const Spacer(), Flexible( child: SettingsDropdown( key: const Key('_AIModelSelection'), onChanged: (model) => context .read() .add(SettingsAIEvent.selectModel(model)), - selectedOption: state.userProfile.aiModel, - options: _availableModels + selectedOption: selectedModel, + selectOptionCompare: (left, right) => + left?.name == right?.name, + options: [...localModels, ...cloudModels] .map( - (format) => buildDropdownMenuEntry( + (model) => buildDropdownMenuEntry( context, - value: format, - label: _titleForAIModel(format), + value: model, + label: + model.isLocal ? "${model.i18n} 🔐" : model.i18n, + subLabel: model.desc, + maximumHeight: height, ), ) .toList(), @@ -54,29 +72,3 @@ class AIModelSelection extends StatelessWidget { ); } } - -List _availableModels = [ - AIModelPB.DefaultModel, - AIModelPB.Claude3Opus, - AIModelPB.Claude3Sonnet, - AIModelPB.GPT4oMini, - AIModelPB.GPT4o, -]; - -String _titleForAIModel(AIModelPB model) { - switch (model) { - case AIModelPB.DefaultModel: - return "Default"; - case AIModelPB.Claude3Opus: - return "Claude 3 Opus"; - case AIModelPB.Claude3Sonnet: - return "Claude 3 Sonnet"; - case AIModelPB.GPT4oMini: - return "GPT-4o-mini"; - case AIModelPB.GPT4o: - return "GPT-4o"; - default: - Log.error("Unknown AI model: $model, fallback to default"); - return "Default"; - } -} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/ollama_setting.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/ollama_setting.dart new file mode 100644 index 0000000000..6f38043927 --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/ollama_setting.dart @@ -0,0 +1,115 @@ +import 'package:appflowy/workspace/application/settings/ai/ollama_setting_bloc.dart'; +import 'package:flowy_infra_ui/style_widget/button.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flowy_infra_ui/style_widget/text_field.dart'; +import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; +import 'package:flowy_infra_ui/widget/spacing.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class OllamaSettingPage extends StatelessWidget { + const OllamaSettingPage({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => + OllamaSettingBloc()..add(const OllamaSettingEvent.started()), + child: BlocBuilder( + buildWhen: (previous, current) => + previous.inputItems != current.inputItems || + previous.isEdited != current.isEdited, + builder: (context, state) { + return Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainerHighest, + borderRadius: const BorderRadius.all(Radius.circular(8.0)), + ), + padding: EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 10, + children: [ + for (final item in state.inputItems) + _SettingItemWidget(item: item), + _SaveButton(isEdited: state.isEdited), + ], + ), + ); + }, + ), + ); + } +} + +class _SettingItemWidget extends StatelessWidget { + const _SettingItemWidget({required this.item}); + + final SettingItem item; + + @override + Widget build(BuildContext context) { + return Column( + key: ValueKey(item.content + item.settingType.title), + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FlowyText( + item.settingType.title, + fontSize: 12, + figmaLineHeight: 16, + ), + const VSpace(4), + SizedBox( + height: 32, + child: FlowyTextField( + autoFocus: false, + hintText: item.hintText, + text: item.content, + onChanged: (content) { + context.read().add( + OllamaSettingEvent.onEdit(content, item.settingType), + ); + }, + ), + ), + ], + ); + } +} + +class _SaveButton extends StatelessWidget { + const _SaveButton({required this.isEdited}); + + final bool isEdited; + + @override + Widget build(BuildContext context) { + return Align( + alignment: AlignmentDirectional.centerEnd, + child: FlowyTooltip( + message: isEdited ? null : 'No changes', + child: SizedBox( + child: FlowyButton( + text: FlowyText( + 'Apply', + figmaLineHeight: 20, + color: Theme.of(context).colorScheme.onPrimary, + ), + disable: !isEdited, + expandText: false, + margin: EdgeInsets.symmetric(horizontal: 16.0, vertical: 6.0), + backgroundColor: Theme.of(context).colorScheme.primary, + hoverColor: Theme.of(context).colorScheme.primary.withAlpha(200), + onTap: () { + if (isEdited) { + context + .read() + .add(const OllamaSettingEvent.submit()); + } + }, + ), + ), + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/plugin_state.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/plugin_state.dart deleted file mode 100644 index bf601b6184..0000000000 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/plugin_state.dart +++ /dev/null @@ -1,255 +0,0 @@ -import 'package:appflowy/core/helpers/url_launcher.dart'; -import 'package:appflowy/generated/flowy_svgs.g.dart'; -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/workspace/application/settings/ai/download_offline_ai_app_bloc.dart'; -import 'package:appflowy/workspace/application/settings/ai/plugin_state_bloc.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/size.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:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; - -class PluginStateIndicator extends StatelessWidget { - const PluginStateIndicator({super.key}); - - @override - Widget build(BuildContext context) { - return BlocProvider( - create: (context) => - PluginStateBloc()..add(const PluginStateEvent.started()), - child: BlocBuilder( - builder: (context, state) { - return state.action.when( - init: () => const _InitPlugin(), - ready: () => const _LocalAIReadyToUse(), - restartPlugin: () => const _ReloadButton(), - loadingPlugin: () => const _InitPlugin(), - startAIOfflineApp: () => OpenOrDownloadOfflineAIApp( - onRetry: () { - context - .read() - .add(const PluginStateEvent.started()); - }, - ), - ); - }, - ), - ); - } -} - -class _InitPlugin extends StatelessWidget { - const _InitPlugin(); - - @override - Widget build(BuildContext context) { - return Row( - children: [ - FlowyText(LocaleKeys.settings_aiPage_keys_localAIStart.tr()), - const Spacer(), - const SizedBox( - height: 20, - child: CircularProgressIndicator.adaptive(), - ), - ], - ); - } -} - -class _ReloadButton extends StatelessWidget { - const _ReloadButton(); - - @override - Widget build(BuildContext context) { - return Row( - children: [ - const FlowySvg( - FlowySvgs.download_warn_s, - color: Color(0xFFC62828), - ), - const HSpace(6), - FlowyText(LocaleKeys.settings_aiPage_keys_failToLoadLocalAI.tr()), - const Spacer(), - SizedBox( - height: 30, - child: FlowyButton( - useIntrinsicWidth: true, - text: - FlowyText(LocaleKeys.settings_aiPage_keys_restartLocalAI.tr()), - onTap: () { - context.read().add( - const PluginStateEvent.restartLocalAI(), - ); - }, - ), - ), - ], - ); - } -} - -class _LocalAIReadyToUse extends StatelessWidget { - const _LocalAIReadyToUse(); - - @override - Widget build(BuildContext context) { - return DecoratedBox( - decoration: const BoxDecoration( - color: Color(0xFFEDF7ED), - borderRadius: BorderRadius.all( - Radius.circular(4), - ), - ), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: Row( - children: [ - const HSpace(8), - const FlowySvg( - FlowySvgs.download_success_s, - color: Color(0xFF2E7D32), - ), - const HSpace(6), - Flexible( - child: FlowyText( - LocaleKeys.settings_aiPage_keys_localAILoaded.tr(), - fontSize: 11, - color: const Color(0xFF1E4620), - maxLines: 3, - ), - ), - ], - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 6), - child: FlowyButton( - useIntrinsicWidth: true, - text: FlowyText( - LocaleKeys.settings_aiPage_keys_openModelDirectory.tr(), - fontSize: 11, - color: const Color(0xFF1E4620), - ), - onTap: () { - context.read().add( - const PluginStateEvent.openModelDirectory(), - ); - }, - ), - ), - ], - ), - ), - ); - } -} - -class OpenOrDownloadOfflineAIApp extends StatelessWidget { - const OpenOrDownloadOfflineAIApp({required this.onRetry, super.key}); - - final VoidCallback onRetry; - - @override - Widget build(BuildContext context) { - return BlocProvider( - create: (context) => DownloadOfflineAIBloc(), - child: BlocBuilder( - builder: (context, state) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - RichText( - maxLines: 3, - textAlign: TextAlign.left, - text: TextSpan( - children: [ - TextSpan( - text: - "${LocaleKeys.settings_aiPage_keys_offlineAIInstruction1.tr()} ", - style: Theme.of(context) - .textTheme - .bodySmall! - .copyWith(height: 1.5), - ), - TextSpan( - text: - " ${LocaleKeys.settings_aiPage_keys_offlineAIInstruction2.tr()} ", - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontSize: FontSizes.s14, - color: Theme.of(context).colorScheme.primary, - height: 1.5, - ), - recognizer: TapGestureRecognizer() - ..onTap = () => afLaunchUrlString( - "https://docs.appflowy.io/docs/appflowy/product/appflowy-ai-offline", - ), - ), - TextSpan( - text: - " ${LocaleKeys.settings_aiPage_keys_offlineAIInstruction3.tr()} ", - style: Theme.of(context) - .textTheme - .bodySmall! - .copyWith(height: 1.5), - ), - TextSpan( - text: - "${LocaleKeys.settings_aiPage_keys_offlineAIDownload1.tr()} ", - style: Theme.of(context) - .textTheme - .bodySmall! - .copyWith(height: 1.5), - ), - TextSpan( - text: - " ${LocaleKeys.settings_aiPage_keys_offlineAIDownload2.tr()} ", - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - fontSize: FontSizes.s14, - color: Theme.of(context).colorScheme.primary, - height: 1.5, - ), - recognizer: TapGestureRecognizer() - ..onTap = - () => context.read().add( - const DownloadOfflineAIEvent.started(), - ), - ), - TextSpan( - text: - " ${LocaleKeys.settings_aiPage_keys_offlineAIDownload3.tr()} ", - style: Theme.of(context) - .textTheme - .bodySmall! - .copyWith(height: 1.5), - ), - ], - ), - ), - // const SizedBox( - // height: 6, - // ), // Replaced VSpace with SizedBox for simplicity - // SizedBox( - // height: 30, - // child: FlowyButton( - // useIntrinsicWidth: true, - // margin: const EdgeInsets.symmetric(horizontal: 12), - // text: FlowyText( - // LocaleKeys.settings_aiPage_keys_activeOfflineAI.tr(), - // ), - // onTap: onRetry, - // ), - // ), - ], - ); - }, - ), - ); - } -} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/plugin_status_indicator.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/plugin_status_indicator.dart new file mode 100644 index 0000000000..a280cf0644 --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/plugin_status_indicator.dart @@ -0,0 +1,363 @@ +import 'package:appflowy/core/helpers/url_launcher.dart'; +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/util/theme_extension.dart'; +import 'package:appflowy/workspace/application/settings/ai/local_ai_bloc.dart'; +import 'package:appflowy_backend/protobuf/flowy-ai/protobuf.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flowy_infra_ui/widget/spacing.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class LocalAIStatusIndicator extends StatelessWidget { + const LocalAIStatusIndicator({super.key}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + ready: (_, version, runningState, lackOfResource) { + if (lackOfResource != null) { + return _LackOfResource(resource: lackOfResource); + } + + return switch (runningState) { + RunningStatePB.ReadyToRun => const _ReadyToRun(), + RunningStatePB.Connecting || + RunningStatePB.Connected => + _Initializing(), + RunningStatePB.Running => _LocalAIRunning(version: version), + RunningStatePB.Stopped => const _RestartPluginButton(), + _ => const SizedBox.shrink(), + }; + }, + orElse: () => const SizedBox.shrink(), + ); + }, + ); + } +} + +class _ReadyToRun extends StatelessWidget { + const _ReadyToRun(); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + children: [ + const SizedBox.square( + dimension: 20.0, + child: CircularProgressIndicator( + strokeWidth: 2.0, + strokeAlign: BorderSide.strokeAlignInside, + ), + ), + const HSpace(8.0), + Expanded( + child: FlowyText( + LocaleKeys.settings_aiPage_keys_localAIStart.tr(), + maxLines: 3, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ); + } +} + +class _Initializing extends StatelessWidget { + const _Initializing(); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + children: [ + const SizedBox.square( + dimension: 20.0, + child: CircularProgressIndicator( + strokeWidth: 2.0, + strokeAlign: BorderSide.strokeAlignInside, + ), + ), + HSpace(8), + Expanded( + child: FlowyText( + LocaleKeys.settings_aiPage_keys_localAIInitializing.tr(), + maxLines: 3, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ); + } +} + +class _RestartPluginButton extends StatelessWidget { + const _RestartPluginButton(); + + @override + Widget build(BuildContext context) { + final textStyle = + Theme.of(context).textTheme.bodyMedium?.copyWith(height: 1.5); + + return Container( + decoration: BoxDecoration( + color: Theme.of(context).isLightMode + ? const Color(0x80FFE7EE) + : const Color(0x80591734), + borderRadius: BorderRadius.all(Radius.circular(8.0)), + ), + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + const FlowySvg( + FlowySvgs.toast_error_filled_s, + size: Size.square(20.0), + blendMode: null, + ), + const HSpace(8), + Expanded( + child: RichText( + maxLines: 3, + text: TextSpan( + children: [ + TextSpan( + text: + LocaleKeys.settings_aiPage_keys_failToLoadLocalAI.tr(), + style: textStyle, + ), + TextSpan( + text: ' ', + style: textStyle, + ), + TextSpan( + text: LocaleKeys.settings_aiPage_keys_restartLocalAI.tr(), + style: textStyle?.copyWith( + fontWeight: FontWeight.w600, + decoration: TextDecoration.underline, + ), + recognizer: TapGestureRecognizer() + ..onTap = () { + context + .read() + .add(const LocalAiPluginEvent.restart()); + }, + ), + ], + ), + ), + ), + ], + ), + ); + } +} + +class _LocalAIRunning extends StatelessWidget { + const _LocalAIRunning({ + required this.version, + }); + + final String version; + + @override + Widget build(BuildContext context) { + final runningText = LocaleKeys.settings_aiPage_keys_localAIRunning.tr(); + final text = version.isEmpty ? runningText : "$runningText ($version)"; + + return Container( + decoration: const BoxDecoration( + color: Color(0xFFEDF7ED), + borderRadius: BorderRadius.all(Radius.circular(8.0)), + ), + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + const FlowySvg( + FlowySvgs.download_success_s, + color: Color(0xFF2E7D32), + ), + const HSpace(6), + Expanded( + child: FlowyText( + text, + color: const Color(0xFF1E4620), + maxLines: 3, + ), + ), + ], + ), + ); + } +} + +class _LackOfResource extends StatelessWidget { + const _LackOfResource({required this.resource}); + + final LackOfAIResourcePB resource; + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: Theme.of(context).isLightMode + ? const Color(0x80FFE7EE) + : const Color(0x80591734), + borderRadius: BorderRadius.all(Radius.circular(8.0)), + ), + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + FlowySvg( + FlowySvgs.toast_error_filled_s, + size: const Size.square(20.0), + blendMode: null, + ), + const HSpace(8), + Expanded( + child: switch (resource.resourceType) { + LackOfAIResourceTypePB.PluginExecutableNotReady => + _buildNoLAI(context), + LackOfAIResourceTypePB.OllamaServerNotReady => + _buildNoOllama(context), + LackOfAIResourceTypePB.MissingModel => + _buildNoModel(context, resource.missingModelNames), + _ => const SizedBox.shrink(), + }, + ), + ], + ), + ); + } + + TextStyle? _textStyle(BuildContext context) { + return Theme.of(context).textTheme.bodyMedium?.copyWith(height: 1.5); + } + + Widget _buildNoLAI(BuildContext context) { + final textStyle = _textStyle(context); + return RichText( + maxLines: 3, + text: TextSpan( + children: [ + TextSpan( + text: LocaleKeys.settings_aiPage_keys_laiNotReady.tr(), + style: textStyle, + ), + TextSpan(text: ' ', style: _textStyle(context)), + ..._downloadInstructions(textStyle), + ], + ), + ); + } + + Widget _buildNoOllama(BuildContext context) { + final textStyle = _textStyle(context); + return RichText( + maxLines: 3, + text: TextSpan( + children: [ + TextSpan( + text: LocaleKeys.settings_aiPage_keys_ollamaNotReady.tr(), + style: textStyle, + ), + TextSpan(text: ' ', style: textStyle), + ..._downloadInstructions(textStyle), + ], + ), + ); + } + + Widget _buildNoModel(BuildContext context, List modelNames) { + final textStyle = _textStyle(context); + + return RichText( + maxLines: 3, + text: TextSpan( + children: [ + TextSpan( + text: LocaleKeys.settings_aiPage_keys_modelsMissing.tr(), + style: textStyle, + ), + TextSpan( + text: modelNames.join(', '), + style: textStyle, + ), + TextSpan( + text: ' ', + style: textStyle, + ), + TextSpan( + text: LocaleKeys.settings_aiPage_keys_pleaseFollowThese.tr(), + style: textStyle, + ), + TextSpan( + text: ' ', + style: textStyle, + ), + TextSpan( + text: LocaleKeys.settings_aiPage_keys_instructions.tr(), + style: textStyle?.copyWith( + fontWeight: FontWeight.w600, + decoration: TextDecoration.underline, + ), + recognizer: TapGestureRecognizer() + ..onTap = () { + afLaunchUrlString( + "https://appflowy.com/guide/appflowy-local-ai-ollama", + ); + }, + ), + TextSpan( + text: ' ', + style: textStyle, + ), + TextSpan( + text: LocaleKeys.settings_aiPage_keys_downloadModel.tr(), + style: textStyle, + ), + ], + ), + ); + } + + List _downloadInstructions(TextStyle? textStyle) { + return [ + TextSpan( + text: LocaleKeys.settings_aiPage_keys_pleaseFollowThese.tr(), + style: textStyle, + ), + TextSpan( + text: ' ', + style: textStyle, + ), + TextSpan( + text: LocaleKeys.settings_aiPage_keys_instructions.tr(), + style: textStyle?.copyWith( + fontWeight: FontWeight.w600, + decoration: TextDecoration.underline, + ), + recognizer: TapGestureRecognizer() + ..onTap = () { + afLaunchUrlString( + "https://appflowy.com/guide/appflowy-local-ai-ollama", + ); + }, + ), + TextSpan(text: ' ', style: textStyle), + TextSpan( + text: LocaleKeys.settings_aiPage_keys_installOllamaLai.tr(), + style: textStyle, + ), + ]; + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/settings_ai_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/settings_ai_view.dart index a6181ba016..c2e75ff2f2 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/settings_ai_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/setting_ai_view/settings_ai_view.dart @@ -1,39 +1,15 @@ import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/shared/af_role_pb_extension.dart'; -import 'package:appflowy/shared/feature_flags.dart'; -import 'package:appflowy/workspace/application/settings/ai/local_ai_on_boarding_bloc.dart'; import 'package:appflowy/workspace/application/settings/ai/settings_ai_bloc.dart'; import 'package:appflowy/workspace/presentation/settings/pages/setting_ai_view/local_ai_setting.dart'; import 'package:appflowy/workspace/presentation/settings/pages/setting_ai_view/model_selection.dart'; import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart'; -import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart'; import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; -import 'package:flowy_infra_ui/widget/spacing.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -class AIFeatureOnlySupportedWhenUsingAppFlowyCloud extends StatelessWidget { - const AIFeatureOnlySupportedWhenUsingAppFlowyCloud({super.key}); - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 60, vertical: 30), - child: FlowyText( - LocaleKeys.settings_aiPage_keys_loginToEnableAIFeature.tr(), - maxLines: null, - fontSize: 16, - lineHeight: 1.6, - ), - ); - } -} - class SettingsAIView extends StatelessWidget { const SettingsAIView({ super.key, @@ -49,34 +25,16 @@ class SettingsAIView extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (_) => - SettingsAIBloc(userProfile, workspaceId, currentWorkspaceMemberRole) - ..add(const SettingsAIEvent.started()), - child: BlocBuilder( - builder: (context, state) { - final children = [ - const AIModelSelection(), - ]; - - children.add(const _AISearchToggle(value: false)); - - if (state.currentWorkspaceMemberRole != null) { - children.add( - _LocalAIOnBoarding( - userProfile: userProfile, - currentWorkspaceMemberRole: state.currentWorkspaceMemberRole!, - workspaceId: workspaceId, - ), - ); - } - - return SettingsBody( - title: LocaleKeys.settings_aiPage_title.tr(), - description: - LocaleKeys.settings_aiPage_keys_aiSettingsDescription.tr(), - children: children, - ); - }, + create: (_) => SettingsAIBloc(userProfile, workspaceId) + ..add(const SettingsAIEvent.started()), + child: SettingsBody( + title: LocaleKeys.settings_aiPage_title.tr(), + description: LocaleKeys.settings_aiPage_keys_aiSettingsDescription.tr(), + children: [ + const AIModelSelection(), + const _AISearchToggle(value: false), + const LocalAISetting(), + ], ), ); } @@ -124,122 +82,3 @@ class _AISearchToggle extends StatelessWidget { ); } } - -// ignore: unused_element -class _LocalAIOnBoarding extends StatelessWidget { - const _LocalAIOnBoarding({ - required this.userProfile, - required this.currentWorkspaceMemberRole, - required this.workspaceId, - }); - final UserProfilePB userProfile; - final AFRolePB? currentWorkspaceMemberRole; - final String workspaceId; - - @override - Widget build(BuildContext context) { - if (FeatureFlag.planBilling.isOn) { - return BillingGateGuard( - builder: (context) { - return BlocProvider( - create: (context) => LocalAIOnBoardingBloc( - userProfile, - currentWorkspaceMemberRole, - workspaceId, - )..add(const LocalAIOnBoardingEvent.started()), - child: BlocBuilder( - builder: (context, state) { - // Show the local AI settings if the user has purchased the AI Local plan - if (kDebugMode || state.isPurchaseAILocal) { - return const LocalAISetting(); - } else { - if (currentWorkspaceMemberRole?.isOwner ?? false) { - // Show the upgrade to AI Local plan button if the user has not purchased the AI Local plan - return _UpgradeToAILocalPlan( - onTap: () { - context.read().add( - const LocalAIOnBoardingEvent.addSubscription( - SubscriptionPlanPB.AiLocal, - ), - ); - }, - ); - } else { - return const _AskOwnerUpgradeToLocalAI(); - } - } - }, - ), - ); - }, - ); - } else { - return const SizedBox.shrink(); - } - } -} - -class _AskOwnerUpgradeToLocalAI extends StatelessWidget { - const _AskOwnerUpgradeToLocalAI(); - - @override - Widget build(BuildContext context) { - return FlowyText( - LocaleKeys.sideBar_askOwnerToUpgradeToLocalAI.tr(), - color: AFThemeExtension.of(context).strongText, - ); - } -} - -class _UpgradeToAILocalPlan extends StatefulWidget { - const _UpgradeToAILocalPlan({required this.onTap}); - - final VoidCallback onTap; - - @override - State<_UpgradeToAILocalPlan> createState() => _UpgradeToAILocalPlanState(); -} - -class _UpgradeToAILocalPlanState extends State<_UpgradeToAILocalPlan> { - @override - Widget build(BuildContext context) { - return Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - FlowyText.medium( - LocaleKeys.sideBar_upgradeToAILocal.tr(), - maxLines: 10, - lineHeight: 1.5, - ), - const VSpace(4), - Opacity( - opacity: 0.6, - child: FlowyText( - LocaleKeys.sideBar_upgradeToAILocalDesc.tr(), - fontSize: 12, - maxLines: 10, - lineHeight: 1.5, - ), - ), - ], - ), - ), - BlocBuilder( - builder: (context, state) { - if (state.isLoading) { - return const CircularProgressIndicator.adaptive(); - } else { - return Toggle( - value: false, - onChanged: (_) => widget.onTap(), - ); - } - }, - ), - ], - ); - } -} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_account_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_account_view.dart index bb0e4aac9f..d7afb03e87 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_account_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_account_view.dart @@ -2,13 +2,14 @@ import 'package:appflowy/env/cloud_env.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/workspace/application/user/settings_user_bloc.dart'; +import 'package:appflowy/workspace/presentation/settings/pages/about/app_version.dart'; import 'package:appflowy/workspace/presentation/settings/pages/account/account.dart'; +import 'package:appflowy/workspace/presentation/settings/pages/account/email/email_section.dart'; import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart'; import 'package:appflowy/workspace/presentation/settings/shared/settings_category.dart'; -import 'package:appflowy_backend/protobuf/flowy-user/auth.pbenum.dart'; import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart'; +import 'package:appflowy_backend/protobuf/flowy-user/workspace.pb.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -44,11 +45,11 @@ class _SettingsAccountViewState extends State { child: BlocBuilder( builder: (context, state) { return SettingsBody( - title: LocaleKeys.settings_accountPage_title.tr(), + title: LocaleKeys.newSettings_myAccount_title.tr(), children: [ // user profile SettingsCategory( - title: LocaleKeys.settings_accountPage_general_title.tr(), + title: LocaleKeys.newSettings_myAccount_myProfile.tr(), children: [ AccountUserProfile( name: userName, @@ -60,7 +61,7 @@ class _SettingsAccountViewState extends State { setState(() => userName = newName); context .read() - .add(SettingsUserEvent.updateUserName(newName)); + .add(SettingsUserEvent.updateUserName(name: newName)); }, ), ], @@ -69,34 +70,57 @@ class _SettingsAccountViewState extends State { // user email // Only show email if the user is authenticated and not using local auth if (isAuthEnabled && - state.userProfile.authenticator != AuthenticatorPB.Local) ...[ + state.userProfile.workspaceAuthType != AuthTypePB.Local) ...[ SettingsCategory( - title: LocaleKeys.settings_accountPage_email_title.tr(), + title: LocaleKeys.newSettings_myAccount_myAccount.tr(), children: [ - FlowyText.regular(state.userProfile.email), + SettingsEmailSection( + userProfile: state.userProfile, + ), + ChangePasswordSection( + userProfile: state.userProfile, + ), + AccountSignInOutSection( + userProfile: state.userProfile, + onAction: state.userProfile.workspaceAuthType == + AuthTypePB.Local + ? widget.didLogin + : widget.didLogout, + signIn: state.userProfile.workspaceAuthType == + AuthTypePB.Local, + ), ], ), ], - // user sign in/out + if (isAuthEnabled && + state.userProfile.workspaceAuthType == AuthTypePB.Local) ...[ + SettingsCategory( + title: LocaleKeys.settings_accountPage_login_title.tr(), + children: [ + AccountSignInOutSection( + userProfile: state.userProfile, + onAction: state.userProfile.workspaceAuthType == + AuthTypePB.Local + ? widget.didLogin + : widget.didLogout, + signIn: state.userProfile.workspaceAuthType == + AuthTypePB.Local, + ), + ], + ), + ], + + // App version SettingsCategory( - title: LocaleKeys.settings_accountPage_login_title.tr(), - children: [ - AccountSignInOutButton( - userProfile: state.userProfile, - onAction: - state.userProfile.authenticator == AuthenticatorPB.Local - ? widget.didLogin - : widget.didLogout, - signIn: state.userProfile.authenticator == - AuthenticatorPB.Local, - ), + title: LocaleKeys.newSettings_myAccount_aboutAppFlowy.tr(), + children: const [ + SettingsAppVersion(), ], ), // user deletion - if (widget.userProfile.authenticator == - AuthenticatorPB.AppFlowyCloud) + if (widget.userProfile.workspaceAuthType == AuthTypePB.Server) const AccountDeletionButton(), ], ); diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_billing_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_billing_view.dart index 0c57c9e158..77c1116319 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_billing_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_billing_view.dart @@ -1,6 +1,5 @@ -import 'dart:io'; - import 'package:appflowy/shared/flowy_error_page.dart'; +import 'package:appflowy/shared/loading.dart'; import 'package:appflowy/util/int64_extension.dart'; import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; import 'package:appflowy/workspace/application/settings/billing/settings_billing_bloc.dart'; @@ -25,7 +24,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../../generated/locale_keys.g.dart'; -import '../../../../plugins/document/presentation/editor_plugins/openai/widgets/loading.dart'; const _buttonsMinWidth = 100.0; @@ -211,26 +209,6 @@ class _SettingsBillingViewState extends State { ), ), const SettingsDashedDivider(), - - // Currently, the AI Local tile is only available on macOS - // TODO(nathan): enable windows and linux - if (Platform.isMacOS) - _AITile( - plan: SubscriptionPlanPB.AiLocal, - label: LocaleKeys - .settings_billingPage_addons_aiOnDevice_label - .tr(), - description: LocaleKeys - .settings_billingPage_addons_aiOnDevice_description, - activeDescription: LocaleKeys - .settings_billingPage_addons_aiOnDevice_activeDescription, - canceledDescription: LocaleKeys - .settings_billingPage_addons_aiOnDevice_canceledDescription, - subscriptionInfo: - state.subscriptionInfo.addOns.firstWhereOrNull( - (a) => a.type == WorkspaceAddOnPBType.AddOnAiLocal, - ), - ), ], ), ], diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_manage_data_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_manage_data_view.dart index 7c096b4b2f..a2d911ea40 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_manage_data_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_manage_data_view.dart @@ -157,7 +157,6 @@ class SettingsManageDataView extends StatelessWidget { if (context.mounted) { showToastNotification( - context, message: LocaleKeys .settings_manageDataPage_cache_dialog_successHint .tr(), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_comparison_dialog.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_comparison_dialog.dart index d9103b4cd4..420daa8698 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_comparison_dialog.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_comparison_dialog.dart @@ -1,4 +1,5 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/shared/loading.dart'; import 'package:appflowy/util/theme_extension.dart'; import 'package:appflowy/workspace/application/settings/plan/settings_plan_bloc.dart'; import 'package:appflowy/workspace/application/settings/plan/workspace_subscription_ext.dart'; @@ -13,7 +14,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../../generated/locale_keys.g.dart'; -import '../../../../plugins/document/presentation/editor_plugins/openai/widgets/loading.dart'; class SettingsPlanComparisonDialog extends StatefulWidget { const SettingsPlanComparisonDialog({ @@ -667,6 +667,10 @@ final _planLabels = [ label: LocaleKeys.settings_comparePlanDialog_planLabels_itemSix.tr(), tooltip: LocaleKeys.settings_comparePlanDialog_planLabels_tooltipSix.tr(), ), + _PlanItem( + label: LocaleKeys.settings_comparePlanDialog_planLabels_itemSeven.tr(), + tooltip: LocaleKeys.settings_comparePlanDialog_planLabels_tooltipSix.tr(), + ), _PlanItem( label: LocaleKeys.settings_comparePlanDialog_planLabels_itemFileUpload.tr(), ), @@ -712,6 +716,9 @@ final List<_CellItem> _freeLabels = [ _CellItem( label: LocaleKeys.settings_comparePlanDialog_freeLabels_itemSix.tr(), ), + _CellItem( + label: LocaleKeys.settings_comparePlanDialog_freeLabels_itemSeven.tr(), + ), _CellItem( label: LocaleKeys.settings_comparePlanDialog_freeLabels_itemFileUpload.tr(), ), @@ -746,6 +753,9 @@ final List<_CellItem> _proLabels = [ _CellItem( label: LocaleKeys.settings_comparePlanDialog_proLabels_itemSix.tr(), ), + _CellItem( + label: LocaleKeys.settings_comparePlanDialog_proLabels_itemSeven.tr(), + ), _CellItem( label: LocaleKeys.settings_comparePlanDialog_proLabels_itemFileUpload.tr(), ), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_view.dart index 61cb83d1ae..21896ead0e 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_plan_view.dart @@ -1,9 +1,8 @@ -import 'dart:io'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/shared/colors.dart'; import 'package:appflowy/shared/flowy_error_page.dart'; +import 'package:appflowy/shared/loading.dart'; import 'package:appflowy/util/int64_extension.dart'; import 'package:appflowy/util/theme_extension.dart'; import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; @@ -24,8 +23,6 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import '../../../../plugins/document/presentation/editor_plugins/openai/widgets/loading.dart'; - class SettingsPlanView extends StatefulWidget { const SettingsPlanView({ super.key, @@ -135,46 +132,6 @@ class _SettingsPlanViewState extends State { ), ), const HSpace(8), - - // Currently, the AI Local tile is only available on macOS - // TODO(nathan): enable windows and linux - if (Platform.isMacOS) - Flexible( - child: _AddOnBox( - title: LocaleKeys - .settings_planPage_planUsage_addons_aiOnDevice_title - .tr(), - description: LocaleKeys - .settings_planPage_planUsage_addons_aiOnDevice_description - .tr(), - price: LocaleKeys - .settings_planPage_planUsage_addons_aiOnDevice_price - .tr( - args: [ - SubscriptionPlanPB.AiLocal.priceAnnualBilling, - ], - ), - priceInfo: LocaleKeys - .settings_planPage_planUsage_addons_aiOnDevice_priceInfo - .tr(), - recommend: LocaleKeys - .settings_planPage_planUsage_addons_aiOnDevice_recommend - .tr( - args: [ - SubscriptionPlanPB.AiLocal.priceMonthBilling, - ], - ), - buttonText: state.subscriptionInfo.hasAIOnDevice - ? LocaleKeys - .settings_planPage_planUsage_addons_activeLabel - .tr() - : LocaleKeys - .settings_planPage_planUsage_addons_addLabel - .tr(), - isActive: state.subscriptionInfo.hasAIOnDevice, - plan: SubscriptionPlanPB.AiLocal, - ), - ), ], ), ], @@ -439,23 +396,6 @@ class _PlanUsageSummary extends StatelessWidget { }, ), ], - if (!subscriptionInfo.hasAIOnDevice) ...[ - _ToggleMore( - value: false, - label: LocaleKeys.settings_planPage_planUsage_aiOnDeviceToggle - .tr(), - badgeLabel: - LocaleKeys.settings_planPage_planUsage_aiOnDeviceBadge.tr(), - onTap: () async { - context.read().add( - const SettingsPlanEvent.addSubscription( - SubscriptionPlanPB.AiLocal, - ), - ); - await Future.delayed(const Duration(seconds: 2), () {}); - }, - ), - ], ], ), ], @@ -603,8 +543,8 @@ class _PlanProgressIndicator extends StatelessWidget { borderRadius: BorderRadius.circular(8), color: AFThemeExtension.of(context).progressBarBGColor, border: Border.all( - color: const Color(0xFFDDF1F7).withOpacity( - theme.brightness == Brightness.light ? 1 : 0.1, + color: const Color(0xFFDDF1F7).withValues( + alpha: theme.brightness == Brightness.light ? 1 : 0.1, ), ), ), @@ -674,7 +614,7 @@ class _AddOnBox extends StatelessWidget { border: Border.all( color: isActive ? const Color(0xFFBDBDBD) : const Color(0xFF9C00FB), ), - color: const Color(0xFFF7F8FC).withOpacity(0.05), + color: const Color(0xFFF7F8FC).withValues(alpha: 0.05), borderRadius: BorderRadius.circular(16), ), child: Column( diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_shortcuts_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_shortcuts_view.dart index 87ea9d9260..0d3716c7dc 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_shortcuts_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_shortcuts_view.dart @@ -5,9 +5,12 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/base/strin import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/custom_copy_command.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/custom_cut_command.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/custom_paste_command.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/math_equation/math_equation_shortcut.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/toggle/toggle_block_shortcuts.dart'; +import 'package:appflowy/shared/error_page/error_page.dart'; import 'package:appflowy/workspace/application/settings/shortcuts/settings_shortcuts_cubit.dart'; import 'package:appflowy/workspace/application/settings/shortcuts/settings_shortcuts_service.dart'; +import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart'; import 'package:appflowy/workspace/presentation/settings/shared/settings_alert_dialog.dart'; import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/emoji_picker/emoji_shortcut_event.dart'; @@ -19,7 +22,6 @@ import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; -import 'package:flowy_infra_ui/widget/error_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -55,21 +57,24 @@ class _SettingsShortcutsViewState extends State { ), const HSpace(10), _ResetButton( - onReset: () => SettingsAlertDialog( - isDangerous: true, - title: LocaleKeys.settings_shortcutsPage_resetDialog_title - .tr(), - subtitle: LocaleKeys - .settings_shortcutsPage_resetDialog_description - .tr(), - confirmLabel: LocaleKeys - .settings_shortcutsPage_resetDialog_buttonLabel - .tr(), - confirm: () { - Navigator.of(context).pop(); - context.read().resetToDefault(); - }, - ).show(context), + onReset: () { + showConfirmDialog( + context: context, + title: LocaleKeys.settings_shortcutsPage_resetDialog_title + .tr(), + description: LocaleKeys + .settings_shortcutsPage_resetDialog_description + .tr(), + confirmLabel: LocaleKeys + .settings_shortcutsPage_resetDialog_buttonLabel + .tr(), + onConfirm: () { + context.read().resetToDefault(); + Navigator.of(context).pop(); + }, + style: ConfirmPopupStyle.cancelAndOk, + ); + }, ), ], ), @@ -481,7 +486,7 @@ class KeyBadge extends StatelessWidget { borderRadius: Corners.s4Border, boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.25), + color: Colors.black.withValues(alpha: 0.25), blurRadius: 1, offset: const Offset(0, 1), ), @@ -593,6 +598,10 @@ extension CommandLabel on CommandShortcutEvent { label = LocaleKeys.settings_shortcutsPage_keybindings_alignCenter.tr(); } else if (key == customTextRightAlignCommand.key) { label = LocaleKeys.settings_shortcutsPage_keybindings_alignRight.tr(); + } else if (key == insertInlineMathEquationCommand.key) { + label = LocaleKeys + .settings_shortcutsPage_keybindings_insertInlineMathEquation + .tr(); } else if (key == undoCommand.key) { label = LocaleKeys.settings_shortcutsPage_keybindings_undo.tr(); } else if (key == redoCommand.key) { diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_workspace_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_workspace_view.dart index 94b8923db5..78ffd34eef 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_workspace_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/settings_workspace_view.dart @@ -88,7 +88,7 @@ class SettingsWorkspaceView extends StatelessWidget { autoSeparate: false, children: [ // We don't allow changing workspace name/icon for local/offline - if (userProfile.authenticator != AuthenticatorPB.Local) ...[ + if (userProfile.workspaceAuthType != AuthTypePB.Local) ...[ SettingsCategory( title: LocaleKeys.settings_workspacePage_workspaceName_title .tr(), @@ -180,7 +180,7 @@ class SettingsWorkspaceView extends StatelessWidget { ), const SettingsCategorySpacer(), - if (userProfile.authenticator != AuthenticatorPB.Local) ...[ + if (userProfile.workspaceAuthType != AuthTypePB.Local) ...[ SingleSettingAction( label: LocaleKeys.settings_workspacePage_manageWorkspace_title .tr(), @@ -381,7 +381,7 @@ class TextDirectionSelect extends StatelessWidget { Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { - final selectedItem = state.textDirection ?? AppFlowyTextDirection.ltr; + final selectedItem = state.textDirection; return SettingsRadioSelect( onChanged: (item) { @@ -1090,9 +1090,11 @@ class _FontListPopupState extends State<_FontListPopup> { hoverColor: Theme.of(context) .colorScheme .onSurface - .withOpacity(0.12), - selectedTileColor: - Theme.of(context).colorScheme.primary.withOpacity(0.12), + .withValues(alpha: 0.12), + selectedTileColor: Theme.of(context) + .colorScheme + .primary + .withValues(alpha: 0.12), contentPadding: const EdgeInsets.symmetric(horizontal: 6, vertical: 4), minTileHeight: 0, diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/constants.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/constants.dart index f764bec9e7..2f03fc052c 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/constants.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/constants.dart @@ -53,8 +53,7 @@ class SettingsPageSitesEvent { ); getIt().setData(ClipboardServiceData(plainText: url)); showToastNotification( - context, - message: LocaleKeys.grid_url_copy.tr(), + message: LocaleKeys.message_copy_success.tr(), ); } } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/domain/domain_item.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/domain/domain_item.dart index d075deabb0..b1d9b9cdae 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/domain/domain_item.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/domain/domain_item.dart @@ -146,28 +146,30 @@ class _HomePageButton extends StatelessWidget { return Row( mainAxisSize: MainAxisSize.min, children: [ - AppFlowyPopover( - direction: PopoverDirection.bottomWithCenterAligned, - constraints: const BoxConstraints( - maxWidth: 260, - maxHeight: 345, + Flexible( + child: AppFlowyPopover( + direction: PopoverDirection.bottomWithCenterAligned, + constraints: const BoxConstraints( + maxWidth: 260, + maxHeight: 345, + ), + margin: const EdgeInsets.symmetric( + horizontal: 14.0, + vertical: 12.0, + ), + popupBuilder: (_) { + final bloc = context.read(); + return BlocProvider.value( + value: bloc, + child: SelectHomePageMenu( + userProfile: bloc.user, + workspaceId: bloc.workspaceId, + onSelected: (view) {}, + ), + ); + }, + child: child, ), - margin: const EdgeInsets.symmetric( - horizontal: 14.0, - vertical: 12.0, - ), - popupBuilder: (_) { - final bloc = context.read(); - return BlocProvider.value( - value: bloc, - child: SelectHomePageMenu( - userProfile: bloc.user, - workspaceId: bloc.workspaceId, - onSelected: (view) {}, - ), - ); - }, - child: child, ), if (homePageView != null) FlowyTooltip( @@ -247,11 +249,10 @@ class _FreePlanUpgradeButton extends StatelessWidget { horizontal: 8.0, vertical: 6.0, ), - hoverColor: context.proSecondaryColor.withOpacity(0.9), + hoverColor: context.proSecondaryColor.withValues(alpha: 0.9), onTap: () { if (isOwner) { showToastNotification( - context, message: LocaleKeys.settings_sites_namespace_redirectToPayment.tr(), type: ToastificationType.info, @@ -262,7 +263,6 @@ class _FreePlanUpgradeButton extends StatelessWidget { ); } else { showToastNotification( - context, message: LocaleKeys .settings_sites_namespace_pleaseAskOwnerToSetHomePage .tr(), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/domain/domain_settings_dialog.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/domain/domain_settings_dialog.dart index 6555494144..9617f2c8d6 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/domain/domain_settings_dialog.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/domain/domain_settings_dialog.dart @@ -216,7 +216,6 @@ class _DomainSettingsDialogState extends State { result.fold( (s) { showToastNotification( - context, message: LocaleKeys.settings_sites_success_namespaceUpdated.tr(), ); @@ -234,7 +233,6 @@ class _DomainSettingsDialogState extends State { Log.error('Failed to update namespace: $f'); showToastNotification( - context, message: basicErrorMessage, type: ToastificationType.error, description: errorMessage, diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/publish_info_view_item.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/publish_info_view_item.dart index f2a3980bf6..3ba2c7e75e 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/publish_info_view_item.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/publish_info_view_item.dart @@ -1,4 +1,6 @@ import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart'; +import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; import 'package:appflowy/util/string_extension.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; @@ -51,13 +53,9 @@ class PublishInfoViewItem extends StatelessWidget { } Widget _buildIcon() { - final icon = publishInfoView.view.icon.value; + final icon = publishInfoView.view.icon.toEmojiIconData(); return icon.isNotEmpty - ? FlowyText.emoji( - icon, - fontSize: 16.0, - figmaLineHeight: 18.0, - ) + ? RawEmojiIconWidget(emoji: icon, emojiSize: 16.0) : publishInfoView.view.defaultIcon(); } } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/published_page/published_view_settings_dialog.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/published_page/published_view_settings_dialog.dart index b7f3cecebf..ad37bae866 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/published_page/published_view_settings_dialog.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/published_page/published_view_settings_dialog.dart @@ -203,7 +203,6 @@ class _PublishedViewSettingsDialogState result.fold( (s) { showToastNotification( - context, message: LocaleKeys.settings_sites_success_updatePathNameSuccess.tr(), ); Navigator.of(context).pop(); @@ -212,7 +211,6 @@ class _PublishedViewSettingsDialogState Log.error('update path name failed: $f'); showToastNotification( - context, message: LocaleKeys.settings_sites_error_updatePathNameFailed.tr(), type: ToastificationType.error, description: f.code.publishErrorMessage, diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/settings_sites_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/settings_sites_view.dart index 7b00e652ed..f3845b0896 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/settings_sites_view.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/pages/sites/settings_sites_view.dart @@ -178,7 +178,6 @@ class _SettingsSitesPageView extends StatelessWidget { Log.error('Failed to generate payment link for Pro Plan: ${f.msg}'); showToastNotification( - context, message: LocaleKeys.settings_sites_error_failedToGeneratePaymentLink.tr(), type: ToastificationType.error, @@ -188,14 +187,12 @@ class _SettingsSitesPageView extends StatelessWidget { result != null) { result.fold((_) { showToastNotification( - context, message: LocaleKeys.publish_unpublishSuccessfully.tr(), ); }, (f) { Log.error('Failed to unpublish view: ${f.msg}'); showToastNotification( - context, message: LocaleKeys.publish_unpublishFailed.tr(), type: ToastificationType.error, description: f.msg, @@ -204,14 +201,12 @@ class _SettingsSitesPageView extends StatelessWidget { } else if (type == SettingsSitesActionType.setHomePage && result != null) { result.fold((s) { showToastNotification( - context, message: LocaleKeys.settings_sites_success_setHomepageSuccess.tr(), ); }, (f) { Log.error('Failed to set homepage: ${f.msg}'); showToastNotification( - context, message: LocaleKeys.settings_sites_error_setHomepageFailed.tr(), type: ToastificationType.error, ); @@ -220,14 +215,12 @@ class _SettingsSitesPageView extends StatelessWidget { result != null) { result.fold((s) { showToastNotification( - context, message: LocaleKeys.settings_sites_success_removeHomePageSuccess.tr(), ); }, (f) { Log.error('Failed to remove homepage: ${f.msg}'); showToastNotification( - context, message: LocaleKeys.settings_sites_error_removeHomePageFailed.tr(), type: ToastificationType.error, ); diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/settings_dialog.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/settings_dialog.dart index 24e1e52deb..cd33c62090 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/settings_dialog.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/settings_dialog.dart @@ -1,5 +1,6 @@ import 'package:appflowy/env/cloud_env.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/shared/share/constants.dart'; import 'package:appflowy/shared/appflowy_cache_manager.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/util/share_log_files.dart'; @@ -22,6 +23,7 @@ import 'package:appflowy/workspace/presentation/settings/widgets/feature_flags/f import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_page.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/settings_notifications_view.dart'; +import 'package:appflowy/workspace/presentation/settings/widgets/web_url_hint_widget.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; @@ -30,11 +32,15 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'pages/setting_ai_view/local_settings_ai_view.dart'; import 'widgets/setting_cloud.dart'; @visibleForTesting const kSelfHostedTextInputFieldKey = ValueKey('self_hosted_url_input_text_field'); +@visibleForTesting +const kSelfHostedWebTextInputFieldKey = + ValueKey('self_hosted_web_url_input_text_field'); class SettingsDialog extends StatelessWidget { SettingsDialog( @@ -134,14 +140,19 @@ class SettingsDialog extends StatelessWidget { case SettingsPage.shortcuts: return const SettingsShortcutsView(); case SettingsPage.ai: - if (user.authenticator == AuthenticatorPB.AppFlowyCloud) { + if (user.workspaceAuthType == AuthTypePB.Server) { return SettingsAIView( + key: ValueKey(workspaceId), userProfile: user, currentWorkspaceMemberRole: currentWorkspaceMemberRole, workspaceId: workspaceId, ); } else { - return const AIFeatureOnlySupportedWhenUsingAppFlowyCloud(); + return LocalSettingsAIView( + key: ValueKey(workspaceId), + userProfile: user, + workspaceId: workspaceId, + ); } case SettingsPage.member: return WorkspaceMembersPage( @@ -165,8 +176,6 @@ class SettingsDialog extends StatelessWidget { ); case SettingsPage.featureFlags: return const FeatureFlagsPage(); - default: - return const SizedBox.shrink(); } } } @@ -245,26 +254,22 @@ class _SelfHostSettings extends StatefulWidget { } class _SelfHostSettingsState extends State<_SelfHostSettings> { - final textController = TextEditingController(); + final cloudUrlTextController = TextEditingController(); + final webUrlTextController = TextEditingController(); + AuthenticatorType type = AuthenticatorType.appflowyCloud; @override void initState() { super.initState(); - getAppFlowyCloudUrl().then((url) { - textController.text = url; - if (kAppflowyCloudUrl != url) { - setState(() { - type = AuthenticatorType.appflowyCloudSelfHost; - }); - } - }); + _fetchUrls(); } @override void dispose() { - textController.dispose(); + cloudUrlTextController.dispose(); + webUrlTextController.dispose(); super.dispose(); } @@ -285,43 +290,55 @@ class _SelfHostSettingsState extends State<_SelfHostSettings> { } Widget _buildInputField() { - return Row( + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Expanded( - child: SizedBox( - height: 36, - child: FlowyTextField( - key: kSelfHostedTextInputFieldKey, - controller: textController, - autoFocus: false, - textStyle: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w400, - ), - hintText: kAppflowyCloudUrl, - onEditingComplete: () => _saveUrl( - url: textController.text, - type: AuthenticatorType.appflowyCloudSelfHost, - ), - ), + _SelfHostUrlField( + textFieldKey: kSelfHostedTextInputFieldKey, + textController: cloudUrlTextController, + title: LocaleKeys.settings_menu_cloudURL.tr(), + hintText: LocaleKeys.settings_menu_cloudURLHint.tr(), + onSave: (url) => _saveUrl( + cloudUrl: url, + webUrl: webUrlTextController.text, + type: AuthenticatorType.appflowyCloudSelfHost, ), ), - const HSpace(12.0), - Container( - height: 36, - constraints: const BoxConstraints(minWidth: 78), - child: OutlinedRoundedButton( - text: LocaleKeys.button_save.tr(), - onTap: () => _saveUrl( - url: textController.text, - type: AuthenticatorType.appflowyCloudSelfHost, - ), + const VSpace(12.0), + _SelfHostUrlField( + textFieldKey: kSelfHostedWebTextInputFieldKey, + textController: webUrlTextController, + title: LocaleKeys.settings_menu_webURL.tr(), + hintText: LocaleKeys.settings_menu_webURLHint.tr(), + hintBuilder: (context) => const WebUrlHintWidget(), + onSave: (url) => _saveUrl( + cloudUrl: cloudUrlTextController.text, + webUrl: url, + type: AuthenticatorType.appflowyCloudSelfHost, ), ), + const VSpace(12.0), + _buildSaveButton(), ], ); } + Widget _buildSaveButton() { + return Container( + height: 36, + constraints: const BoxConstraints(minWidth: 78), + child: OutlinedRoundedButton( + text: LocaleKeys.button_save.tr(), + onTap: () => _saveUrl( + cloudUrl: cloudUrlTextController.text, + webUrl: webUrlTextController.text, + type: AuthenticatorType.appflowyCloudSelfHost, + ), + ), + ); + } + void _onSelected(AuthenticatorType type) { if (type == this.type) { return; @@ -334,48 +351,83 @@ class _SelfHostSettingsState extends State<_SelfHostSettings> { }); if (type == AuthenticatorType.appflowyCloud) { - textController.text = kAppflowyCloudUrl; + cloudUrlTextController.text = kAppflowyCloudUrl; + webUrlTextController.text = ShareConstants.defaultBaseWebDomain; _saveUrl( - url: textController.text, + cloudUrl: kAppflowyCloudUrl, + webUrl: ShareConstants.defaultBaseWebDomain, type: type, ); } } - void _saveUrl({ - required String url, + Future _saveUrl({ + required String cloudUrl, + required String webUrl, required AuthenticatorType type, - }) { - if (url.isEmpty) { + }) async { + if (cloudUrl.isEmpty || webUrl.isEmpty) { showToastNotification( - context, message: LocaleKeys.settings_menu_pleaseInputValidURL.tr(), type: ToastificationType.error, ); return; } - validateUrl(url).fold( - (url) async { + final isValid = await _validateUrl(cloudUrl) && await _validateUrl(webUrl); + + if (mounted) { + if (isValid) { showToastNotification( - context, - message: LocaleKeys.settings_menu_changeUrl.tr(args: [url]), + message: LocaleKeys.settings_menu_changeUrl.tr(args: [cloudUrl]), ); Navigator.of(context).pop(); - await useAppFlowyBetaCloudWithURL(url, type); + + await useBaseWebDomain(webUrl); + await useAppFlowyBetaCloudWithURL(cloudUrl, type); + await runAppFlowy(); - }, - (err) { + } else { showToastNotification( - context, message: LocaleKeys.settings_menu_pleaseInputValidURL.tr(), type: ToastificationType.error, ); + } + } + } + + Future _validateUrl(String url) async { + return await validateUrl(url).fold( + (url) async { + return true; + }, + (err) { Log.error(err); + return false; }, ); } + + Future _fetchUrls() async { + await Future.wait([ + getAppFlowyCloudUrl(), + getAppFlowyShareDomain(), + ]).then((values) { + if (values.length != 2) { + return; + } + + cloudUrlTextController.text = values[0]; + webUrlTextController.text = values[1]; + + if (kAppflowyCloudUrl != values[0]) { + setState(() { + type = AuthenticatorType.appflowyCloudSelfHost; + }); + } + }); + } } @visibleForTesting @@ -472,7 +524,6 @@ class _SupportSettings extends StatelessWidget { await getIt().clearAllCache(); if (context.mounted) { showToastNotification( - context, message: LocaleKeys .settings_manageDataPage_cache_dialog_successHint .tr(), @@ -487,3 +538,59 @@ class _SupportSettings extends StatelessWidget { ); } } + +class _SelfHostUrlField extends StatelessWidget { + const _SelfHostUrlField({ + required this.textController, + required this.title, + required this.hintText, + required this.onSave, + this.textFieldKey, + this.hintBuilder, + }); + + final TextEditingController textController; + final String title; + final String hintText; + final ValueChanged onSave; + final Key? textFieldKey; + final WidgetBuilder? hintBuilder; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildHintWidget(context), + const VSpace(6.0), + SizedBox( + height: 36, + child: FlowyTextField( + key: textFieldKey, + controller: textController, + autoFocus: false, + textStyle: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + ), + hintText: hintText, + onEditingComplete: () => onSave(textController.text), + ), + ), + ], + ); + } + + Widget _buildHintWidget(BuildContext context) { + return Row( + children: [ + FlowyText( + title, + overflow: TextOverflow.ellipsis, + ), + hintBuilder?.call(context) ?? const SizedBox.shrink(), + ], + ); + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/af_dropdown_menu_entry.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/af_dropdown_menu_entry.dart index d005901cff..720f7793f2 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/af_dropdown_menu_entry.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/af_dropdown_menu_entry.dart @@ -9,14 +9,40 @@ DropdownMenuEntry buildDropdownMenuEntry( BuildContext context, { required T value, required String label, + String subLabel = '', T? selectedValue, Widget? leadingWidget, Widget? trailingWidget, String? fontFamily, + double maximumHeight = 29, }) { final fontFamilyUsed = fontFamily != null ? getGoogleFontSafely(fontFamily).fontFamily ?? defaultFontFamily : defaultFontFamily; + Widget? labelWidget; + if (subLabel.isNotEmpty) { + labelWidget = Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + FlowyText.regular( + label, + fontSize: 14, + ), + const VSpace(4), + FlowyText.regular( + subLabel, + fontSize: 10, + ), + ], + ); + } else { + labelWidget = FlowyText.regular( + label, + fontSize: 14, + textAlign: TextAlign.start, + fontFamily: fontFamilyUsed, + ); + } return DropdownMenuEntry( style: ButtonStyle( @@ -26,17 +52,12 @@ DropdownMenuEntry buildDropdownMenuEntry( const EdgeInsets.symmetric(horizontal: 6, vertical: 4), ), minimumSize: const WidgetStatePropertyAll(Size(double.infinity, 29)), - maximumSize: const WidgetStatePropertyAll(Size(double.infinity, 29)), + maximumSize: WidgetStatePropertyAll(Size(double.infinity, maximumHeight)), ), value: value, label: label, leadingIcon: leadingWidget, - labelWidget: FlowyText.regular( - label, - fontSize: 14, - textAlign: TextAlign.start, - fontFamily: fontFamilyUsed, - ), + labelWidget: labelWidget, trailingIcon: Row( children: [ if (trailingWidget != null) ...[ diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/flowy_gradient_button.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/flowy_gradient_button.dart index eb39df8b32..68556f8294 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/flowy_gradient_button.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/flowy_gradient_button.dart @@ -1,6 +1,5 @@ -import 'package:flutter/material.dart'; - import 'package:flowy_infra_ui/style_widget/text.dart'; +import 'package:flutter/material.dart'; class FlowyGradientButton extends StatefulWidget { const FlowyGradientButton({ @@ -49,7 +48,7 @@ class _FlowyGradientButtonState extends State { boxShadow: [ BoxShadow( blurRadius: 4, - color: Colors.black.withOpacity(0.25), + color: Colors.black.withValues(alpha: 0.25), offset: const Offset(0, 2), ), ], diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/setting_value_dropdown.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/setting_value_dropdown.dart index 5d1c858d29..6c8eeb9ae4 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/setting_value_dropdown.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/setting_value_dropdown.dart @@ -12,6 +12,8 @@ class SettingValueDropDown extends StatefulWidget { this.child, this.popoverController, this.offset, + this.boxConstraints, + this.margin = const EdgeInsets.all(6), }); final String currentValue; @@ -21,6 +23,8 @@ class SettingValueDropDown extends StatefulWidget { final Widget? child; final PopoverController? popoverController; final Offset? offset; + final BoxConstraints? boxConstraints; + final EdgeInsets margin; @override State createState() => _SettingValueDropDownState(); @@ -33,12 +37,14 @@ class _SettingValueDropDownState extends State { key: widget.popoverKey, controller: widget.popoverController, direction: PopoverDirection.bottomWithCenterAligned, + margin: widget.margin, popupBuilder: widget.popupBuilder, - constraints: const BoxConstraints( - minWidth: 80, - maxWidth: 160, - maxHeight: 400, - ), + constraints: widget.boxConstraints ?? + const BoxConstraints( + minWidth: 80, + maxWidth: 160, + maxHeight: 400, + ), offset: widget.offset, onClose: widget.onClose, child: widget.child ?? diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_alert_dialog.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_alert_dialog.dart index 61a29b77a1..c56b46eae0 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_alert_dialog.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_alert_dialog.dart @@ -1,12 +1,10 @@ -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/size.dart'; -import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart'; +import 'package:flutter/material.dart'; class SettingsAlertDialog extends StatefulWidget { const SettingsAlertDialog({ @@ -201,18 +199,16 @@ class _Actions extends StatelessWidget { children: [ if (!hideCancelButton) ...[ SizedBox( - height: 24, - child: FlowyTextButton( - LocaleKeys.button_cancel.tr(), - padding: const EdgeInsets.symmetric( + height: 48, + child: PrimaryRoundedButton( + text: LocaleKeys.button_cancel.tr(), + margin: const EdgeInsets.symmetric( horizontal: 24, vertical: 12, ), - fontColor: AFThemeExtension.of(context).textColor, - fillColor: Colors.transparent, - hoverColor: Colors.transparent, - radius: Corners.s12Border, - onPressed: () { + fontWeight: FontWeight.w600, + radius: 12.0, + onTap: () { cancel?.call(); Navigator.of(context).pop(); }, diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_body.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_body.dart index 8091a72684..5114218041 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_body.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_body.dart @@ -1,20 +1,21 @@ -import 'package:flutter/material.dart'; - import 'package:appflowy/workspace/presentation/settings/shared/settings_category_spacer.dart'; import 'package:appflowy/workspace/presentation/settings/shared/settings_header.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; class SettingsBody extends StatelessWidget { const SettingsBody({ super.key, required this.title, this.description, + this.descriptionBuilder, this.autoSeparate = true, required this.children, }); final String title; final String? description; + final WidgetBuilder? descriptionBuilder; final bool autoSeparate; final List children; @@ -27,7 +28,12 @@ class SettingsBody extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - SettingsHeader(title: title, description: description), + SettingsHeader( + title: title, + description: description, + descriptionBuilder: descriptionBuilder, + ), + SettingsCategorySpacer(), Flexible( child: SeparatedColumn( mainAxisSize: MainAxisSize.min, diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_category.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_category.dart index a111fa2626..33c81b99e8 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_category.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_category.dart @@ -1,4 +1,5 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; @@ -25,15 +26,18 @@ class SettingsCategory extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ - FlowyText.semibold( + Text( title, + style: theme.textStyle.heading4.enhanced( + color: theme.textColorScheme.primary, + ), maxLines: 2, - fontSize: 16, overflow: TextOverflow.ellipsis, ), if (tooltip != null) ...[ @@ -47,7 +51,7 @@ class SettingsCategory extends StatelessWidget { if (actions != null) ...actions!, ], ), - const VSpace(8), + const VSpace(16), if (description?.isNotEmpty ?? false) ...[ FlowyText.regular( description!, diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_category_spacer.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_category_spacer.dart index 5637fdd20c..1ef7f13d0c 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_category_spacer.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_category_spacer.dart @@ -1,3 +1,4 @@ +import 'package:appflowy_ui/appflowy_ui.dart'; import 'package:flutter/material.dart'; /// This is used to create a uniform space and divider @@ -7,6 +8,11 @@ class SettingsCategorySpacer extends StatelessWidget { const SettingsCategorySpacer({super.key}); @override - Widget build(BuildContext context) => - const Divider(height: 32, color: Color(0xFFF2F2F2)); + Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); + return Divider( + height: theme.spacing.xl * 2.0, + color: theme.borderColorScheme.primary, + ); + } } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_dropdown.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_dropdown.dart index 56d2c8d2cc..e392ed91f0 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_dropdown.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_dropdown.dart @@ -16,9 +16,11 @@ class SettingsDropdown extends StatefulWidget { this.onChanged, this.actions, this.expandWidth = true, + this.selectOptionCompare, }); final T selectedOption; + final CompareFunction? selectOptionCompare; final List> options; final void Function(T)? onChanged; final List? actions; @@ -52,6 +54,7 @@ class _SettingsDropdownState extends State> { expandedInsets: widget.expandWidth ? EdgeInsets.zero : null, initialSelection: widget.selectedOption, dropdownMenuEntries: widget.options, + selectOptionCompare: widget.selectOptionCompare, textStyle: Theme.of(context).textTheme.bodyLarge?.copyWith( fontFamily: fontFamilyUsed, fontWeight: FontWeight.w400, @@ -61,7 +64,7 @@ class _SettingsDropdownState extends State> { const WidgetStatePropertyAll(Size(double.infinity, 250)), elevation: const WidgetStatePropertyAll(10), shadowColor: - WidgetStatePropertyAll(Colors.black.withOpacity(0.4)), + WidgetStatePropertyAll(Colors.black.withValues(alpha: 0.4)), backgroundColor: WidgetStatePropertyAll( Theme.of(context).cardColor, ), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_header.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_header.dart index c028e6886d..332b25e686 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_header.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/settings_header.dart @@ -1,32 +1,46 @@ -import 'package:flutter/material.dart'; - -import 'package:flowy_infra/theme_extension.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; /// Renders a simple header for the settings view /// class SettingsHeader extends StatelessWidget { - const SettingsHeader({super.key, required this.title, this.description}); + const SettingsHeader({ + super.key, + required this.title, + this.description, + this.descriptionBuilder, + }); final String title; final String? description; + final WidgetBuilder? descriptionBuilder; @override Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - FlowyText.semibold(title, fontSize: 24), - if (description?.isNotEmpty == true) ...[ - const VSpace(8), - FlowyText( + Text( + title, + style: theme.textStyle.heading2.enhanced( + color: theme.textColorScheme.primary, + ), + ), + if (descriptionBuilder != null) ...[ + VSpace(theme.spacing.xs), + descriptionBuilder!(context), + ] else if (description?.isNotEmpty == true) ...[ + VSpace(theme.spacing.xs), + Text( description!, maxLines: 4, - fontSize: 12, - color: AFThemeExtension.of(context).secondaryTextColor, + style: theme.textStyle.caption.standard( + color: theme.textColorScheme.secondary, + ), ), ], - const VSpace(16), ], ); } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/single_setting_action.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/single_setting_action.dart index a356e3fd50..6b0c920a04 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/single_setting_action.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/shared/single_setting_action.dart @@ -128,11 +128,11 @@ class SingleSettingAction extends StatelessWidget { Color? hoverColor(BuildContext context) { if (buttonType.isDangerous) { - return Theme.of(context).colorScheme.error.withOpacity(0.1); + return Theme.of(context).colorScheme.error.withValues(alpha: 0.1); } if (buttonType.isPrimary) { - return Theme.of(context).colorScheme.primary.withOpacity(0.9); + return Theme.of(context).colorScheme.primary.withValues(alpha: 0.9); } if (buttonType.isHighlight) { diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/emoji_picker/emoji_menu_item.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/emoji_picker/emoji_menu_item.dart deleted file mode 100644 index 6cdccb3b3b..0000000000 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/emoji_picker/emoji_menu_item.dart +++ /dev/null @@ -1,115 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; - -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/base/emoji/emoji_picker.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/base/selectable_svg_widget.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra_ui/style_widget/decoration.dart'; - -SelectionMenuItem emojiMenuItem = SelectionMenuItem( - getName: LocaleKeys.document_plugins_emoji.tr, - icon: (editorState, onSelected, style) => SelectableIconWidget( - icon: Icons.emoji_emotions_outlined, - isSelected: onSelected, - style: style, - ), - keywords: ['emoji'], - handler: (editorState, menuService, context) { - final container = Overlay.of(context); - menuService.dismiss(); - showEmojiPickerMenu( - container, - editorState, - menuService.alignment, - menuService.offset, - ); - }, -); - -void showEmojiPickerMenu( - OverlayState container, - EditorState editorState, - Alignment alignment, - Offset offset, -) { - final top = alignment == Alignment.topLeft ? offset.dy : null; - final bottom = alignment == Alignment.bottomLeft ? offset.dy : null; - - keepEditorFocusNotifier.increase(); - late OverlayEntry emojiPickerMenuEntry; - emojiPickerMenuEntry = FullScreenOverlayEntry( - top: top, - bottom: bottom, - left: offset.dx, - dismissCallback: () => keepEditorFocusNotifier.decrease(), - builder: (context) => Material( - type: MaterialType.transparency, - child: Container( - width: 360, - height: 380, - padding: const EdgeInsets.all(4.0), - decoration: FlowyDecoration.decoration( - Theme.of(context).cardColor, - Theme.of(context).colorScheme.shadow, - ), - child: EmojiSelectionMenu( - onSubmitted: (emoji) { - editorState.insertTextAtCurrentSelection(emoji); - }, - onExit: () { - // close emoji panel - emojiPickerMenuEntry.remove(); - }, - ), - ), - ), - ).build(); - container.insert(emojiPickerMenuEntry); -} - -class EmojiSelectionMenu extends StatefulWidget { - const EmojiSelectionMenu({ - super.key, - required this.onSubmitted, - required this.onExit, - }); - - final void Function(String emoji) onSubmitted; - final void Function() onExit; - - @override - State createState() => _EmojiSelectionMenuState(); -} - -class _EmojiSelectionMenuState extends State { - @override - void initState() { - super.initState(); - HardwareKeyboard.instance.addHandler(_handleGlobalKeyEvent); - } - - bool _handleGlobalKeyEvent(KeyEvent event) { - if (event.logicalKey == LogicalKeyboardKey.escape && - event is KeyDownEvent) { - //triggers on esc - widget.onExit(); - return true; - } - return false; - } - - @override - void deactivate() { - HardwareKeyboard.instance.removeHandler(_handleGlobalKeyEvent); - super.deactivate(); - } - - @override - Widget build(BuildContext context) { - return FlowyEmojiPicker( - onEmojiSelected: (_, emoji) => widget.onSubmitted(emoji), - ); - } -} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/emoji_picker/emoji_picker.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/emoji_picker/emoji_picker.dart index a369cc6b87..6e1f6e239f 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/emoji_picker/emoji_picker.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/emoji_picker/emoji_picker.dart @@ -1,4 +1,3 @@ -export 'emoji_menu_item.dart'; export 'emoji_shortcut_event.dart'; export 'src/emji_picker_config.dart'; export 'src/emoji_picker.dart'; diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/emoji_picker/emoji_shortcut_event.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/emoji_picker/emoji_shortcut_event.dart index 078cf64963..6959f69788 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/emoji_picker/emoji_shortcut_event.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/emoji_picker/emoji_shortcut_event.dart @@ -1,5 +1,7 @@ -import 'package:appflowy/workspace/presentation/settings/widgets/emoji_picker/emoji_picker.dart'; +import 'package:appflowy/plugins/emoji/emoji_actions_command.dart'; +import 'package:appflowy/plugins/emoji/emoji_menu.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; import 'package:flutter/material.dart'; final CommandShortcutEvent emojiShortcutEvent = CommandShortcutEvent( @@ -15,73 +17,16 @@ CommandShortcutEventHandler _emojiShortcutHandler = (editorState) { if (selection == null) { return KeyEventResult.ignored; } - final context = editorState.getNodeAtPath(selection.start.path)?.context; - if (context == null) { + final node = editorState.getNodeAtPath(selection.start.path); + final context = node?.context; + if (node == null || + context == null || + node.delta == null || + node.type == CodeBlockKeys.type) { return KeyEventResult.ignored; } - final container = Overlay.of(context); - - Alignment alignment = Alignment.topLeft; - Offset offset = Offset.zero; - - final selectionService = editorState.service.selectionService; - final selectionRects = selectionService.selectionRects; - if (selectionRects.isEmpty) { - return KeyEventResult.ignored; - } - final rect = selectionRects.first; - - // Calculate the offset and alignment - // Don't like these values being hardcoded but unsure how to grab the - // values dynamically to match the /emoji command. - const menuHeight = 200.0; - const menuOffset = Offset(10, 10); // Tried (0, 10) but that looked off - - final editorOffset = - editorState.renderBox?.localToGlobal(Offset.zero) ?? Offset.zero; - final editorHeight = editorState.renderBox!.size.height; - final editorWidth = editorState.renderBox!.size.width; - - // show below default - alignment = Alignment.topLeft; - final bottomRight = rect.bottomRight; - final topRight = rect.topRight; - final newOffset = bottomRight + menuOffset; - offset = Offset( - newOffset.dx, - newOffset.dy, - ); - - // show above - if (newOffset.dy + menuHeight >= editorOffset.dy + editorHeight) { - offset = topRight - menuOffset; - alignment = Alignment.bottomLeft; - - offset = Offset( - newOffset.dx, - MediaQuery.of(context).size.height - newOffset.dy, - ); - } - - // show on left - if (offset.dx - editorOffset.dx > editorWidth / 2) { - alignment = alignment == Alignment.topLeft - ? Alignment.topRight - : Alignment.bottomRight; - - offset = Offset( - editorWidth - offset.dx + editorOffset.dx, - offset.dy, - ); - } - - showEmojiPickerMenu( - container, - editorState, - alignment, - offset, - ); - + emojiMenuService = EmojiMenu(editorState: editorState, overlay: container); + emojiMenuService?.show(''); return KeyEventResult.handled; }; diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/emoji_picker/src/emji_picker_config.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/emoji_picker/src/emji_picker_config.dart index 22d2bbe034..f329e9dd1c 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/emoji_picker/src/emji_picker_config.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/emoji_picker/src/emji_picker_config.dart @@ -3,8 +3,8 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; -import 'models/emoji_category_models.dart'; import 'emoji_picker.dart'; +import 'models/emoji_category_models.dart'; part 'emji_picker_config.freezed.dart'; @@ -87,8 +87,6 @@ class EmojiPickerConfig with _$EmojiPickerConfig { return emojiCategoryIcons.flagIcon; case EmojiCategory.SEARCH: return emojiCategoryIcons.searchIcon; - default: - throw Exception('Unsupported EmojiCategory'); } } } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/members/inivitation/inivite_member_by_link.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/members/inivitation/inivite_member_by_link.dart new file mode 100644 index 0000000000..6f143a83c1 --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/members/inivitation/inivite_member_by_link.dart @@ -0,0 +1,154 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart'; +import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart'; +import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class InviteMemberByLink extends StatelessWidget { + const InviteMemberByLink({super.key}); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _Title(), + _Description(), + ], + ), + Spacer(), + _CopyLinkButton(), + ], + ); + } +} + +class _Title extends StatelessWidget { + const _Title(); + + @override + Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); + return Text( + LocaleKeys.settings_appearance_members_inviteLinkToAddMember.tr(), + style: theme.textStyle.body.enhanced( + color: theme.textColorScheme.primary, + ), + ); + } +} + +class _Description extends StatelessWidget { + const _Description(); + + @override + Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); + return Text.rich( + TextSpan( + children: [ + TextSpan( + text: LocaleKeys.settings_appearance_members_clickToCopyLink.tr(), + style: theme.textStyle.caption.standard( + color: theme.textColorScheme.primary, + ), + ), + TextSpan( + text: ' ${LocaleKeys.settings_appearance_members_or.tr()} ', + style: theme.textStyle.caption.standard( + color: theme.textColorScheme.primary, + ), + ), + TextSpan( + text: LocaleKeys.settings_appearance_members_generateANewLink.tr(), + style: theme.textStyle.caption.standard( + color: theme.textColorScheme.action, + ), + mouseCursor: SystemMouseCursors.click, + recognizer: TapGestureRecognizer() + ..onTap = () => _onGenerateInviteLink(context), + ), + ], + ), + ); + } + + Future _onGenerateInviteLink(BuildContext context) async { + final inviteLink = context.read().state.inviteLink; + if (inviteLink != null) { + // show a dialog to confirm if the user wants to copy the link to the clipboard + await showConfirmDialog( + context: context, + style: ConfirmPopupStyle.cancelAndOk, + title: 'Reset the invite link?', + description: + 'Resetting will deactivate the current link for all space members and generate a new one. The old link will no longer be available.', + confirmLabel: 'Reset', + onConfirm: () { + context.read().add( + const WorkspaceMemberEvent.generateInviteLink(), + ); + }, + confirmButtonBuilder: (_) => AFFilledTextButton.destructive( + text: 'Reset', + onTap: () { + context.read().add( + const WorkspaceMemberEvent.generateInviteLink(), + ); + + Navigator.of(context).pop(); + }, + ), + ); + } else { + context.read().add( + const WorkspaceMemberEvent.generateInviteLink(), + ); + } + } +} + +class _CopyLinkButton extends StatelessWidget { + const _CopyLinkButton(); + + @override + Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); + return AFOutlinedTextButton.normal( + text: LocaleKeys.button_copyLink.tr(), + textStyle: theme.textStyle.body.standard( + color: theme.textColorScheme.primary, + ), + padding: EdgeInsets.symmetric( + horizontal: theme.spacing.l, + vertical: theme.spacing.s, + ), + onTap: () { + final link = context.read().state.inviteLink; + if (link != null) { + getIt().setData( + ClipboardServiceData( + plainText: link, + ), + ); + + showToastNotification( + message: LocaleKeys.document_inlineLink_copyLink.tr(), + ); + } else { + showToastNotification( + message: LocaleKeys.shareAction_copyLinkFailed.tr(), + ); + } + }, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/members/inivitation/invite_member_by_email.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/members/inivitation/invite_member_by_email.dart new file mode 100644 index 0000000000..9f8ce45a97 --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/members/inivitation/invite_member_by_email.dart @@ -0,0 +1,79 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:string_validator/string_validator.dart'; + +class InviteMemberByEmail extends StatefulWidget { + const InviteMemberByEmail({super.key}); + + @override + State createState() => _InviteMemberByEmailState(); +} + +class _InviteMemberByEmailState extends State { + final _emailController = TextEditingController(); + + @override + void dispose() { + _emailController.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + LocaleKeys.settings_appearance_members_inviteMemberByEmail.tr(), + style: theme.textStyle.body.enhanced( + color: theme.textColorScheme.primary, + ), + ), + VSpace(theme.spacing.m), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: AFTextField( + controller: _emailController, + hintText: + LocaleKeys.settings_appearance_members_inviteHint.tr(), + onSubmitted: (value) => _inviteMember(), + ), + ), + HSpace(theme.spacing.l), + AFFilledTextButton.primary( + text: LocaleKeys.settings_appearance_members_sendInvite.tr(), + onTap: _inviteMember, + ), + ], + ), + ], + ); + } + + void _inviteMember() { + final email = _emailController.text; + if (!isEmail(email)) { + showToastNotification( + type: ToastificationType.error, + message: LocaleKeys.settings_appearance_members_emailInvalidError.tr(), + ); + return; + } + + context + .read() + .add(WorkspaceMemberEvent.inviteWorkspaceMemberByEmail(email)); + // clear the email field after inviting + _emailController.clear(); + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/members/inivitation/member_http_service.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/members/inivitation/member_http_service.dart new file mode 100644 index 0000000000..01d507ea24 --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/members/inivitation/member_http_service.dart @@ -0,0 +1,187 @@ +import 'dart:convert'; + +import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; +import 'package:appflowy_result/appflowy_result.dart'; +import 'package:http/http.dart' as http; + +enum InviteCodeEndpoint { + getInviteCode, + deleteInviteCode, + generateInviteCode; + + String get path { + switch (this) { + case InviteCodeEndpoint.getInviteCode: + case InviteCodeEndpoint.deleteInviteCode: + case InviteCodeEndpoint.generateInviteCode: + return '/api/workspace/{workspaceId}/invite-code'; + } + } + + String get method { + switch (this) { + case InviteCodeEndpoint.getInviteCode: + return 'GET'; + case InviteCodeEndpoint.deleteInviteCode: + return 'DELETE'; + case InviteCodeEndpoint.generateInviteCode: + return 'POST'; + } + } + + Uri uri(String baseUrl, String workspaceId) => + Uri.parse(path.replaceAll('{workspaceId}', workspaceId)).replace( + scheme: Uri.parse(baseUrl).scheme, + host: Uri.parse(baseUrl).host, + port: Uri.parse(baseUrl).port, + ); +} + +class MemberHttpService { + MemberHttpService({ + required this.baseUrl, + required this.authToken, + }); + + final String baseUrl; + final String authToken; + + final http.Client client = http.Client(); + + Map get headers => { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer $authToken', + }; + + /// Gets the invite code for a workspace + Future> getInviteCode({ + required String workspaceId, + }) async { + final result = await _makeRequest( + endpoint: InviteCodeEndpoint.getInviteCode, + workspaceId: workspaceId, + errorMessage: 'Failed to get invite code', + ); + + try { + return result.fold( + (data) => FlowyResult.success(data['code'] as String), + (error) => FlowyResult.failure(error), + ); + } catch (e) { + return FlowyResult.failure( + FlowyError(msg: 'Failed to get invite code: $e'), + ); + } + } + + /// Deletes the invite code for a workspace + Future> deleteInviteCode({ + required String workspaceId, + }) async { + final result = await _makeRequest( + endpoint: InviteCodeEndpoint.deleteInviteCode, + workspaceId: workspaceId, + errorMessage: 'Failed to delete invite code', + ); + + return result.fold( + (data) => FlowyResult.success(true), + (error) => FlowyResult.failure(error), + ); + } + + /// Generates a new invite code for a workspace + /// + /// [workspaceId] - The ID of the workspace + Future> generateInviteCode({ + required String workspaceId, + int? validityPeriodHours, + }) async { + final result = await _makeRequest( + endpoint: InviteCodeEndpoint.generateInviteCode, + workspaceId: workspaceId, + errorMessage: 'Failed to generate invite code', + body: { + 'validity_period_hours': validityPeriodHours, + }, + ); + + try { + return result.fold( + (data) => FlowyResult.success(data['data']['code'].toString()), + (error) => FlowyResult.failure(error), + ); + } catch (e) { + return FlowyResult.failure( + FlowyError(msg: 'Failed to generate invite code: $e'), + ); + } + } + + /// Makes a request to the specified endpoint + Future> _makeRequest({ + required InviteCodeEndpoint endpoint, + required String workspaceId, + Map? body, + String errorMessage = 'Request failed', + }) async { + try { + final uri = endpoint.uri(baseUrl, workspaceId); + http.Response response; + + switch (endpoint.method) { + case 'GET': + response = await client.get( + uri, + headers: headers, + ); + break; + case 'DELETE': + response = await client.delete( + uri, + headers: headers, + ); + break; + case 'POST': + response = await client.post( + uri, + headers: headers, + body: body != null ? jsonEncode(body) : null, + ); + break; + default: + return FlowyResult.failure( + FlowyError(msg: 'Invalid request method: ${endpoint.method}'), + ); + } + + if (response.statusCode == 200) { + if (response.body.isNotEmpty) { + return FlowyResult.success(jsonDecode(response.body)); + } + return FlowyResult.success(true); + } else { + final errorBody = + response.body.isNotEmpty ? jsonDecode(response.body) : {}; + + Log.info( + '${endpoint.name} request failed: ${response.statusCode}, $errorBody', + ); + + return FlowyResult.failure( + FlowyError( + msg: errorBody['msg'] ?? errorMessage, + ), + ); + } + } catch (e) { + Log.error('${endpoint.name} request failed: error: $e'); + + return FlowyResult.failure( + FlowyError(msg: 'Network error: ${e.toString()}'), + ); + } + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart index c9fcb34204..3fc13c7b18 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart @@ -1,8 +1,11 @@ import 'dart:async'; import 'package:appflowy/core/helpers/url_launcher.dart'; +import 'package:appflowy/env/cloud_env.dart'; import 'package:appflowy/shared/af_role_pb_extension.dart'; +import 'package:appflowy/shared/af_user_profile_extension.dart'; import 'package:appflowy/user/application/user_service.dart'; +import 'package:appflowy/workspace/presentation/settings/widgets/members/inivitation/member_http_service.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; @@ -34,163 +37,260 @@ class WorkspaceMemberBloc super(WorkspaceMemberState.initial()) { on((event, emit) async { await event.when( - initial: () async { - await _setCurrentWorkspaceId(workspaceId); - - final result = await _userBackendService.getWorkspaceMembers( - _workspaceId, - ); - final members = result.fold>( - (s) => s.items, - (e) => [], - ); - final myRole = _getMyRole(members); - - if (myRole.isOwner) { - unawaited(_fetchWorkspaceSubscriptionInfo()); - } - emit( - state.copyWith( - members: members, - myRole: myRole, - isLoading: false, - actionResult: WorkspaceMemberActionResult( - actionType: WorkspaceMemberActionType.get, - result: result, - ), - ), - ); - }, - getWorkspaceMembers: () async { - final result = await _userBackendService.getWorkspaceMembers( - _workspaceId, - ); - final members = result.fold>( - (s) => s.items, - (e) => [], - ); - final myRole = _getMyRole(members); - emit( - state.copyWith( - members: members, - myRole: myRole, - actionResult: WorkspaceMemberActionResult( - actionType: WorkspaceMemberActionType.get, - result: result, - ), - ), - ); - }, - addWorkspaceMember: (email) async { - final result = await _userBackendService.addWorkspaceMember( - _workspaceId, - email, - ); - emit( - state.copyWith( - actionResult: WorkspaceMemberActionResult( - actionType: WorkspaceMemberActionType.add, - result: result, - ), - ), - ); - // the addWorkspaceMember doesn't return the updated members, - // so we need to get the members again - result.onSuccess((s) { - add(const WorkspaceMemberEvent.getWorkspaceMembers()); - }); - }, - inviteWorkspaceMember: (email) async { - final result = await _userBackendService.inviteWorkspaceMember( - _workspaceId, - email, - role: AFRolePB.Member, - ); - emit( - state.copyWith( - actionResult: WorkspaceMemberActionResult( - actionType: WorkspaceMemberActionType.invite, - result: result, - ), - ), - ); - }, - removeWorkspaceMember: (email) async { - final result = await _userBackendService.removeWorkspaceMember( - _workspaceId, - email, - ); - final members = result.fold( - (s) => state.members.where((e) => e.email != email).toList(), - (e) => state.members, - ); - emit( - state.copyWith( - members: members, - actionResult: WorkspaceMemberActionResult( - actionType: WorkspaceMemberActionType.remove, - result: result, - ), - ), - ); - }, - updateWorkspaceMember: (email, role) async { - final result = await _userBackendService.updateWorkspaceMember( - _workspaceId, - email, - role, - ); - final members = result.fold( - (s) => state.members.map((e) { - if (e.email == email) { - e.freeze(); - return e.rebuild((p0) => p0.role = role); - } - return e; - }).toList(), - (e) => state.members, - ); - emit( - state.copyWith( - members: members, - actionResult: WorkspaceMemberActionResult( - actionType: WorkspaceMemberActionType.updateRole, - result: result, - ), - ), - ); - }, + initial: () async => _onInitial(emit, workspaceId), + getWorkspaceMembers: () async => _onGetWorkspaceMembers(emit), + addWorkspaceMember: (email) async => _onAddWorkspaceMember(emit, email), + inviteWorkspaceMemberByEmail: (email) async => + _onInviteWorkspaceMemberByEmail(emit, email), + removeWorkspaceMemberByEmail: (email) async => + _onRemoveWorkspaceMemberByEmail(emit, email), + inviteWorkspaceMemberByLink: (link) async => + _onInviteWorkspaceMemberByLink(emit, link), + generateInviteLink: () async => _onGenerateInviteLink(emit), + updateWorkspaceMember: (email, role) async => + _onUpdateWorkspaceMember(emit, email, role), updateSubscriptionInfo: (info) async => - emit(state.copyWith(subscriptionInfo: info)), - upgradePlan: () async { - final plan = state.subscriptionInfo?.plan; - if (plan == null) { - return Log.error('Failed to upgrade plan: plan is null'); - } - - if (plan == WorkspacePlanPB.FreePlan) { - final checkoutLink = await _userBackendService.createSubscription( - _workspaceId, - SubscriptionPlanPB.Pro, - ); - - checkoutLink.fold( - (pl) => afLaunchUrlString(pl.paymentLink), - (f) => Log.error('Failed to create subscription: ${f.msg}', f), - ); - } - }, + _onUpdateSubscriptionInfo(emit, info), + upgradePlan: () async => _onUpgradePlan(), ); }); } final UserProfilePB userProfile; - - // if the workspace is null, use the current workspace final UserWorkspacePB? workspace; - late final String _workspaceId; final UserBackendService _userBackendService; + MemberHttpService? _memberHttpService; + + Future _onInitial( + Emitter emit, + String? workspaceId, + ) async { + await _setCurrentWorkspaceId(workspaceId); + + final result = await _userBackendService.getWorkspaceMembers(_workspaceId); + final members = result.fold>( + (s) => s.items, + (e) => [], + ); + final myRole = _getMyRole(members); + + if (myRole.isOwner) { + unawaited(_fetchWorkspaceSubscriptionInfo()); + } + + final baseUrl = await getAppFlowyCloudUrl(); + final authToken = userProfile.authToken; + if (authToken != null) { + _memberHttpService = MemberHttpService( + baseUrl: baseUrl, + authToken: authToken, + ); + unawaited( + _memberHttpService?.getInviteCode(workspaceId: _workspaceId).fold( + (s) async { + final inviteLink = await _buildInviteLink(inviteCode: s); + emit(state.copyWith(inviteLink: inviteLink)); + }, + (e) => Log.info('Failed to get invite code: ${e.msg}', e), + ), + ); + } else { + Log.error('Failed to get auth token'); + } + + emit( + state.copyWith( + members: members, + myRole: myRole, + isLoading: false, + actionResult: WorkspaceMemberActionResult( + actionType: WorkspaceMemberActionType.get, + result: result, + ), + ), + ); + } + + Future _onGetWorkspaceMembers( + Emitter emit, + ) async { + final result = await _userBackendService.getWorkspaceMembers(_workspaceId); + final members = result.fold>( + (s) => s.items, + (e) => [], + ); + final myRole = _getMyRole(members); + emit( + state.copyWith( + members: members, + myRole: myRole, + actionResult: WorkspaceMemberActionResult( + actionType: WorkspaceMemberActionType.get, + result: result, + ), + ), + ); + } + + Future _onAddWorkspaceMember( + Emitter emit, + String email, + ) async { + final result = await _userBackendService.addWorkspaceMember( + _workspaceId, + email, + ); + emit( + state.copyWith( + actionResult: WorkspaceMemberActionResult( + actionType: WorkspaceMemberActionType.addByEmail, + result: result, + ), + ), + ); + // the addWorkspaceMember doesn't return the updated members, + // so we need to get the members again + result.onSuccess((s) { + add(const WorkspaceMemberEvent.getWorkspaceMembers()); + }); + } + + Future _onInviteWorkspaceMemberByEmail( + Emitter emit, + String email, + ) async { + final result = await _userBackendService.inviteWorkspaceMember( + _workspaceId, + email, + role: AFRolePB.Member, + ); + emit( + state.copyWith( + actionResult: WorkspaceMemberActionResult( + actionType: WorkspaceMemberActionType.inviteByEmail, + result: result, + ), + ), + ); + } + + Future _onRemoveWorkspaceMemberByEmail( + Emitter emit, + String email, + ) async { + final result = await _userBackendService.removeWorkspaceMember( + _workspaceId, + email, + ); + final members = result.fold( + (s) => state.members.where((e) => e.email != email).toList(), + (e) => state.members, + ); + emit( + state.copyWith( + members: members, + actionResult: WorkspaceMemberActionResult( + actionType: WorkspaceMemberActionType.removeByEmail, + result: result, + ), + ), + ); + } + + Future _onInviteWorkspaceMemberByLink( + Emitter emit, + String link, + ) async {} + + Future _onGenerateInviteLink(Emitter emit) async { + final result = await _memberHttpService?.generateInviteCode( + workspaceId: _workspaceId, + ); + + await result?.fold( + (s) async { + final inviteLink = await _buildInviteLink(inviteCode: s); + emit( + state.copyWith( + inviteLink: inviteLink, + actionResult: WorkspaceMemberActionResult( + actionType: WorkspaceMemberActionType.generateInviteLink, + result: result, + ), + ), + ); + }, + (e) async { + Log.error('Failed to generate invite link: ${e.msg}', e); + emit( + state.copyWith( + actionResult: WorkspaceMemberActionResult( + actionType: WorkspaceMemberActionType.generateInviteLink, + result: result, + ), + ), + ); + }, + ); + } + + Future _onUpdateWorkspaceMember( + Emitter emit, + String email, + AFRolePB role, + ) async { + final result = await _userBackendService.updateWorkspaceMember( + _workspaceId, + email, + role, + ); + final members = result.fold( + (s) => state.members.map((e) { + if (e.email == email) { + e.freeze(); + return e.rebuild((p0) => p0.role = role); + } + return e; + }).toList(), + (e) => state.members, + ); + emit( + state.copyWith( + members: members, + actionResult: WorkspaceMemberActionResult( + actionType: WorkspaceMemberActionType.updateRole, + result: result, + ), + ), + ); + } + + Future _onUpdateSubscriptionInfo( + Emitter emit, + WorkspaceSubscriptionInfoPB info, + ) async { + emit(state.copyWith(subscriptionInfo: info)); + } + + Future _onUpgradePlan() async { + final plan = state.subscriptionInfo?.plan; + if (plan == null) { + return Log.error('Failed to upgrade plan: plan is null'); + } + + if (plan == WorkspacePlanPB.FreePlan) { + final checkoutLink = await _userBackendService.createSubscription( + _workspaceId, + SubscriptionPlanPB.Pro, + ); + + checkoutLink.fold( + (pl) => afLaunchUrlString(pl.paymentLink), + (f) => Log.error('Failed to create subscription: ${f.msg}', f), + ); + } + } AFRolePB _getMyRole(List members) { final role = members @@ -222,8 +322,6 @@ class WorkspaceMemberBloc } } - // We fetch workspace subscription info lazily as it's not needed in the first - // render of the page. Future _fetchWorkspaceSubscriptionInfo() async { final result = await UserBackendService.getWorkspaceSubscriptionInfo(_workspaceId); @@ -237,6 +335,15 @@ class WorkspaceMemberBloc (f) => Log.error('Failed to fetch subscription info: ${f.msg}', f), ); } + + Future _buildInviteLink({required String inviteCode}) async { + final baseUrl = await getAppFlowyShareDomain(); + final authToken = userProfile.authToken; + if (authToken != null) { + return '$baseUrl/app/invited/$inviteCode'; + } + return ''; + } } @freezed @@ -246,10 +353,15 @@ class WorkspaceMemberEvent with _$WorkspaceMemberEvent { GetWorkspaceMembers; const factory WorkspaceMemberEvent.addWorkspaceMember(String email) = AddWorkspaceMember; - const factory WorkspaceMemberEvent.inviteWorkspaceMember(String email) = - InviteWorkspaceMember; - const factory WorkspaceMemberEvent.removeWorkspaceMember(String email) = - RemoveWorkspaceMember; + const factory WorkspaceMemberEvent.inviteWorkspaceMemberByEmail( + String email, + ) = InviteWorkspaceMemberByEmail; + const factory WorkspaceMemberEvent.removeWorkspaceMemberByEmail( + String email, + ) = RemoveWorkspaceMemberByEmail; + const factory WorkspaceMemberEvent.inviteWorkspaceMemberByLink(String link) = + InviteWorkspaceMemberByLink; + const factory WorkspaceMemberEvent.generateInviteLink() = GenerateInviteLink; const factory WorkspaceMemberEvent.updateWorkspaceMember( String email, AFRolePB role, @@ -265,10 +377,12 @@ enum WorkspaceMemberActionType { none, get, // this event will send an invitation to the member - invite, + inviteByEmail, + inviteByLink, + generateInviteLink, // this event will add the member without sending an invitation - add, - remove, + addByEmail, + removeByEmail, updateRole, } @@ -292,6 +406,7 @@ class WorkspaceMemberState with _$WorkspaceMemberState { @Default(null) WorkspaceMemberActionResult? actionResult, @Default(true) bool isLoading, @Default(null) WorkspaceSubscriptionInfoPB? subscriptionInfo, + @Default(null) String? inviteLink, }) = _WorkspaceMemberState; factory WorkspaceMemberState.initial() => const WorkspaceMemberState(); @@ -307,6 +422,7 @@ class WorkspaceMemberState with _$WorkspaceMemberState { other.members == members && other.myRole == myRole && other.subscriptionInfo == subscriptionInfo && + other.inviteLink == inviteLink && identical(other.actionResult, actionResult); } } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/members/workspace_member_page.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/members/workspace_member_page.dart index bf33ab9d72..3ead104ee3 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/members/workspace_member_page.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/members/workspace_member_page.dart @@ -1,22 +1,23 @@ -import 'package:appflowy/core/helpers/url_launcher.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart'; import 'package:appflowy/shared/af_role_pb_extension.dart'; -import 'package:appflowy/workspace/presentation/home/toast.dart'; +import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart'; import 'package:appflowy/workspace/presentation/settings/shared/settings_category_spacer.dart'; +import 'package:appflowy/workspace/presentation/settings/widgets/members/inivitation/inivite_member_by_link.dart'; +import 'package:appflowy/workspace/presentation/settings/widgets/members/inivitation/invite_member_by_email.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-error/code.pbenum.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; import 'package:easy_localization/easy_localization.dart'; -import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:string_validator/string_validator.dart'; class WorkspaceMembersPage extends StatelessWidget { const WorkspaceMembersPage({ @@ -38,14 +39,14 @@ class WorkspaceMembersPage extends StatelessWidget { builder: (context, state) { return SettingsBody( title: LocaleKeys.settings_appearance_members_title.tr(), + // Enable it when the backend support admin panel + // descriptionBuilder: _buildDescription, autoSeparate: false, children: [ - if (state.actionResult != null) ...[ - _showMemberLimitWarning(context, state), - const VSpace(16), - ], if (state.myRole.canInvite) ...[ - const _InviteMember(), + const InviteMemberByLink(), + const SettingsCategorySpacer(), + const InviteMemberByEmail(), const SettingsCategorySpacer(), ], if (state.members.isNotEmpty) @@ -61,104 +62,141 @@ class WorkspaceMembersPage extends StatelessWidget { ); } - Widget _showMemberLimitWarning( - BuildContext context, - WorkspaceMemberState state, - ) { - // We promise that state.actionResult != null before calling - // this method - final actionResult = state.actionResult!.result; - final actionType = state.actionResult!.actionType; + // Enable it when the backend support admin panel + // Widget _buildDescription(BuildContext context) { + // final theme = AppFlowyTheme.of(context); + // return Text.rich( + // TextSpan( + // children: [ + // TextSpan( + // text: + // '${LocaleKeys.settings_appearance_members_memberPageDescription1.tr()} ', + // style: theme.textStyle.caption.standard( + // color: theme.textColorScheme.secondary, + // ), + // ), + // TextSpan( + // text: LocaleKeys.settings_appearance_members_adminPanel.tr(), + // style: theme.textStyle.caption.underline( + // color: theme.textColorScheme.secondary, + // ), + // mouseCursor: SystemMouseCursors.click, + // recognizer: TapGestureRecognizer() + // ..onTap = () async { + // final baseUrl = await getAppFlowyCloudUrl(); + // await afLaunchUrlString(baseUrl); + // }, + // ), + // TextSpan( + // text: + // ' ${LocaleKeys.settings_appearance_members_memberPageDescription2.tr()} ', + // style: theme.textStyle.caption.standard( + // color: theme.textColorScheme.secondary, + // ), + // ), + // ], + // ), + // ); + // } - if (actionType == WorkspaceMemberActionType.invite && - actionResult.isFailure) { - final error = actionResult.getFailure().code; - if (error == ErrorCode.WorkspaceMemberLimitExceeded) { - return Row( - children: [ - const FlowySvg( - FlowySvgs.warning_s, - blendMode: BlendMode.dst, - size: Size.square(20), - ), - const HSpace(12), - Expanded( - child: RichText( - text: TextSpan( - children: [ - if (state.subscriptionInfo?.plan == - WorkspacePlanPB.ProPlan) ...[ - TextSpan( - text: LocaleKeys - .settings_appearance_members_memberLimitExceededPro - .tr(), - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w400, - color: AFThemeExtension.of(context).strongText, - ), - ), - WidgetSpan( - child: MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - // Hardcoded support email, in the future we might - // want to add this to an environment variable - onTap: () async => afLaunchUrlString( - 'mailto:support@appflowy.io', - ), - child: FlowyText( - LocaleKeys - .settings_appearance_members_memberLimitExceededProContact - .tr(), - fontSize: 14, - fontWeight: FontWeight.w400, - color: Theme.of(context).colorScheme.primary, - ), - ), - ), - ), - ] else ...[ - TextSpan( - text: LocaleKeys - .settings_appearance_members_memberLimitExceeded - .tr(), - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w400, - color: AFThemeExtension.of(context).strongText, - ), - ), - WidgetSpan( - child: MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: () => context - .read() - .add(const WorkspaceMemberEvent.upgradePlan()), - child: FlowyText( - LocaleKeys - .settings_appearance_members_memberLimitExceededUpgrade - .tr(), - fontSize: 14, - fontWeight: FontWeight.w400, - color: Theme.of(context).colorScheme.primary, - ), - ), - ), - ), - ], - ], - ), - ), - ), - ], - ); - } - } + // Widget _showMemberLimitWarning( + // BuildContext context, + // WorkspaceMemberState state, + // ) { + // // We promise that state.actionResult != null before calling + // // this method + // final actionResult = state.actionResult!.result; + // final actionType = state.actionResult!.actionType; - return const SizedBox.shrink(); - } + // if (actionType == WorkspaceMemberActionType.inviteByEmail && + // actionResult.isFailure) { + // final error = actionResult.getFailure().code; + // if (error == ErrorCode.WorkspaceMemberLimitExceeded) { + // return Row( + // children: [ + // const FlowySvg( + // FlowySvgs.warning_s, + // blendMode: BlendMode.dst, + // size: Size.square(20), + // ), + // const HSpace(12), + // Expanded( + // child: RichText( + // text: TextSpan( + // children: [ + // if (state.subscriptionInfo?.plan == + // WorkspacePlanPB.ProPlan) ...[ + // TextSpan( + // text: LocaleKeys + // .settings_appearance_members_memberLimitExceededPro + // .tr(), + // style: TextStyle( + // fontSize: 14, + // fontWeight: FontWeight.w400, + // color: AFThemeExtension.of(context).strongText, + // ), + // ), + // WidgetSpan( + // child: MouseRegion( + // cursor: SystemMouseCursors.click, + // child: GestureDetector( + // // Hardcoded support email, in the future we might + // // want to add this to an environment variable + // onTap: () async => afLaunchUrlString( + // 'mailto:support@appflowy.io', + // ), + // child: FlowyText( + // LocaleKeys + // .settings_appearance_members_memberLimitExceededProContact + // .tr(), + // fontSize: 14, + // fontWeight: FontWeight.w400, + // color: Theme.of(context).colorScheme.primary, + // ), + // ), + // ), + // ), + // ] else ...[ + // TextSpan( + // text: LocaleKeys + // .settings_appearance_members_memberLimitExceeded + // .tr(), + // style: TextStyle( + // fontSize: 14, + // fontWeight: FontWeight.w400, + // color: AFThemeExtension.of(context).strongText, + // ), + // ), + // WidgetSpan( + // child: MouseRegion( + // cursor: SystemMouseCursors.click, + // child: GestureDetector( + // onTap: () => context + // .read() + // .add(const WorkspaceMemberEvent.upgradePlan()), + // child: FlowyText( + // LocaleKeys + // .settings_appearance_members_memberLimitExceededUpgrade + // .tr(), + // fontSize: 14, + // fontWeight: FontWeight.w400, + // color: Theme.of(context).colorScheme.primary, + // ), + // ), + // ), + // ), + // ], + // ], + // ), + // ), + // ), + // ], + // ); + // } + // } + + // return const SizedBox.shrink(); + // } void _showResultDialog(BuildContext context, WorkspaceMemberState state) { final actionResult = state.actionResult; @@ -170,12 +208,12 @@ class WorkspaceMembersPage extends StatelessWidget { final result = actionResult.result; // only show the result dialog when the action is WorkspaceMemberActionType.add - if (actionType == WorkspaceMemberActionType.add) { + if (actionType == WorkspaceMemberActionType.addByEmail) { result.fold( (s) { - showSnackBarMessage( - context, - LocaleKeys.settings_appearance_members_addMemberSuccess.tr(), + showToastNotification( + message: + LocaleKeys.settings_appearance_members_addMemberSuccess.tr(), ); }, (f) { @@ -189,12 +227,12 @@ class WorkspaceMembersPage extends StatelessWidget { ); }, ); - } else if (actionType == WorkspaceMemberActionType.invite) { + } else if (actionType == WorkspaceMemberActionType.inviteByEmail) { result.fold( (s) { - showSnackBarMessage( - context, - LocaleKeys.settings_appearance_members_inviteMemberSuccess.tr(), + showToastNotification( + message: + LocaleKeys.settings_appearance_members_inviteMemberSuccess.tr(), ); }, (f) { @@ -214,116 +252,27 @@ class WorkspaceMembersPage extends StatelessWidget { ); }, ); - } - } -} + } else if (actionType == WorkspaceMemberActionType.generateInviteLink) { + result.fold( + (s) { + showToastNotification( + message: 'Invite link generated successfully', + ); -class _InviteMember extends StatefulWidget { - const _InviteMember(); - - @override - State<_InviteMember> createState() => _InviteMemberState(); -} - -class _InviteMemberState extends State<_InviteMember> { - final _emailController = TextEditingController(); - - @override - void dispose() { - _emailController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - FlowyText.semibold( - LocaleKeys.settings_appearance_members_inviteMembers.tr(), - fontSize: 16.0, - ), - const VSpace(8.0), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - child: ConstrainedBox( - constraints: const BoxConstraints.tightFor( - height: 48.0, - ), - child: FlowyTextField( - hintText: - LocaleKeys.settings_appearance_members_inviteHint.tr(), - controller: _emailController, - onEditingComplete: _inviteMember, - ), - ), - ), - const HSpace(10.0), - SizedBox( - height: 48.0, - child: IntrinsicWidth( - child: PrimaryRoundedButton( - text: LocaleKeys.settings_appearance_members_sendInvite.tr(), - margin: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 8, - ), - onTap: _inviteMember, - ), - ), - ), - ], - ), - /* Enable this when the feature is ready - PrimaryButton( - backgroundColor: const Color(0xFFE0E0E0), - child: Padding( - padding: const EdgeInsets.only( - left: 20, - right: 24, - top: 8, - bottom: 8, - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const FlowySvg( - FlowySvgs.invite_member_link_m, - color: Colors.black, - ), - const HSpace(8.0), - FlowyText( - LocaleKeys.settings_appearance_members_copyInviteLink.tr(), - color: Colors.black, - ), - ], - ), - ), - onPressed: () { - showSnackBarMessage(context, 'not implemented'); - }, - ), - const VSpace(16.0), - */ - ], - ); - } - - void _inviteMember() { - final email = _emailController.text; - if (!isEmail(email)) { - return showSnackBarMessage( - context, - LocaleKeys.settings_appearance_members_emailInvalidError.tr(), + // copy the invite link to the clipboard + final inviteLink = state.inviteLink; + if (inviteLink != null) { + getIt().setPlainText(inviteLink); + } + }, + (f) { + Log.error('generate invite link failed: $f'); + showToastNotification( + message: 'Failed to generate invite link', + ); + }, ); } - context - .read() - .add(WorkspaceMemberEvent.inviteWorkspaceMember(email)); - // clear the email field after inviting - _emailController.clear(); } } @@ -340,9 +289,12 @@ class _MemberList extends StatelessWidget { @override Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); return SeparatedColumn( crossAxisAlignment: CrossAxisAlignment.start, - separatorBuilder: () => const Divider(), + separatorBuilder: () => Divider( + color: theme.borderColorScheme.primary, + ), children: [ const _MemberListHeader(), ...members.map( @@ -362,31 +314,34 @@ class _MemberListHeader extends StatelessWidget { @override Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, + final theme = AppFlowyTheme.of(context); + return Row( children: [ - FlowyText.semibold( - LocaleKeys.settings_appearance_members_label.tr(), - fontSize: 16.0, - ), - const VSpace(16.0), - Row( - children: [ - Expanded( - child: FlowyText.semibold( - LocaleKeys.settings_appearance_members_user.tr(), - fontSize: 14.0, - ), + Expanded( + child: Text( + LocaleKeys.settings_appearance_members_user.tr(), + style: theme.textStyle.body.standard( + color: theme.textColorScheme.secondary, ), - Expanded( - child: FlowyText.semibold( - LocaleKeys.settings_appearance_members_role.tr(), - fontSize: 14.0, - ), - ), - const HSpace(28.0), - ], + ), ), + Expanded( + child: Text( + LocaleKeys.settings_appearance_members_role.tr(), + style: theme.textStyle.body.standard( + color: theme.textColorScheme.secondary, + ), + ), + ), + Expanded( + child: Text( + LocaleKeys.settings_accountPage_email_title.tr(), + style: theme.textStyle.body.standard( + color: theme.textColorScheme.secondary, + ), + ), + ), + const HSpace(28.0), ], ); } @@ -405,27 +360,42 @@ class _MemberItem extends StatelessWidget { @override Widget build(BuildContext context) { - final textColor = member.role.isOwner ? Theme.of(context).hintColor : null; + final theme = AppFlowyTheme.of(context); return Row( children: [ Expanded( - child: FlowyText.medium( + child: Text( member.name, - color: textColor, - fontSize: 14.0, + style: theme.textStyle.body.enhanced( + color: theme.textColorScheme.primary, + ), ), ), Expanded( child: member.role.isOwner || !myRole.canUpdate - ? FlowyText.medium( + ? Text( member.role.description, - color: textColor, - fontSize: 14.0, + style: theme.textStyle.body.standard( + color: theme.textColorScheme.primary, + ), ) : _MemberRoleActionList( member: member, ), ), + Expanded( + child: FlowyTooltip( + message: member.email, + child: Text( + member.email, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: theme.textStyle.body.standard( + color: theme.textColorScheme.primary, + ), + ), + ), + ), myRole.canDelete && member.email != userProfile.email // can't delete self ? _MemberMoreActionList(member: member) @@ -476,7 +446,7 @@ class _MemberMoreActionList extends StatelessWidget { .settings_appearance_members_areYouSureToRemoveMember .tr(), onOkPressed: () => context.read().add( - WorkspaceMemberEvent.removeWorkspaceMember( + WorkspaceMemberEvent.removeWorkspaceMemberByEmail( action.member.email, ), ), @@ -515,106 +485,12 @@ class _MemberRoleActionList extends StatelessWidget { @override Widget build(BuildContext context) { - return PopoverActionList<_MemberRoleActionWrapper>( - asBarrier: true, - direction: PopoverDirection.bottomWithLeftAligned, - actions: [AFRolePB.Member] - .map((e) => _MemberRoleActionWrapper(e, member)) - .toList(), - offset: const Offset(0, 10), - buildChild: (controller) { - return MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () => controller.show(), - child: Row( - children: [ - FlowyText.medium( - member.role.description, - fontSize: 14.0, - ), - const HSpace(8.0), - const FlowySvg( - FlowySvgs.drop_menu_show_s, - ), - ], - ), - ), - ); - }, - onSelected: (action, controller) async { - switch (action.inner) { - case AFRolePB.Member: - case AFRolePB.Guest: - context.read().add( - WorkspaceMemberEvent.updateWorkspaceMember( - action.member.email, - action.inner, - ), - ); - break; - case AFRolePB.Owner: - break; - } - controller.close(); - }, - ); - } -} - -class _MemberRoleActionWrapper extends ActionCell { - _MemberRoleActionWrapper(this.inner, this.member); - - final AFRolePB inner; - final WorkspaceMemberPB member; - - @override - Widget? rightIcon(Color iconColor) { - return SizedBox( - width: 58.0, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - FlowyTooltip( - message: tooltip, - child: const FlowySvg( - FlowySvgs.information_s, - // color: iconColor, - ), - ), - const Spacer(), - if (member.role == inner) - const FlowySvg( - FlowySvgs.checkmark_tiny_s, - ), - ], + final theme = AppFlowyTheme.of(context); + return Text( + member.role.description, + style: theme.textStyle.body.standard( + color: theme.textColorScheme.primary, ), ); } - - @override - String get name { - switch (inner) { - case AFRolePB.Guest: - return LocaleKeys.settings_appearance_members_guest.tr(); - case AFRolePB.Member: - return LocaleKeys.settings_appearance_members_member.tr(); - case AFRolePB.Owner: - return LocaleKeys.settings_appearance_members_owner.tr(); - } - throw UnimplementedError('Unknown role: $inner'); - } - - String get tooltip { - switch (inner) { - case AFRolePB.Guest: - return LocaleKeys.settings_appearance_members_guestHintText.tr(); - case AFRolePB.Member: - return LocaleKeys.settings_appearance_members_memberHintText.tr(); - case AFRolePB.Owner: - return ''; - } - throw UnimplementedError('Unknown role: $inner'); - } } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart index d99a6cbf01..5f158f4ae1 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart @@ -1,14 +1,13 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; - import 'package:appflowy/core/helpers/url_launcher.dart'; import 'package:appflowy/env/cloud_env.dart'; import 'package:appflowy/env/env.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/shared/share/constants.dart'; +import 'package:appflowy/shared/error_page/error_page.dart'; import 'package:appflowy/workspace/application/settings/appflowy_cloud_setting_bloc.dart'; import 'package:appflowy/workspace/application/settings/appflowy_cloud_urls_bloc.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/_restart_app_button.dart'; +import 'package:appflowy/workspace/presentation/settings/widgets/web_url_hint_widget.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart'; @@ -21,7 +20,9 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flowy_infra_ui/widget/error_page.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; class AppFlowyCloudViewSetting extends StatelessWidget { @@ -67,14 +68,19 @@ class AppFlowyCloudViewSetting extends StatelessWidget { builder: (context, state) { return Column( children: [ + const VSpace(8), const AppFlowyCloudEnableSync(), - const AppFlowyCloudSyncLogEnabled(), + const VSpace(6), + // const AppFlowyCloudSyncLogEnabled(), const VSpace(12), RestartButton( onClick: () { NavigatorAlertDialog( title: LocaleKeys.settings_menu_restartAppTip.tr(), confirm: () async { + await useBaseWebDomain( + ShareConstants.defaultBaseWebDomain, + ); await useAppFlowyBetaCloudWithURL( serverURL, authenticatorType, @@ -124,7 +130,7 @@ class CustomAppFlowyCloudView extends StatelessWidget { final List children = []; children.addAll([ const AppFlowyCloudEnableSync(), - const AppFlowyCloudSyncLogEnabled(), + // const AppFlowyCloudSyncLogEnabled(), const VSpace(40), ]); @@ -148,6 +154,7 @@ class CustomAppFlowyCloudView extends StatelessWidget { create: (context) => AppFlowyCloudSettingBloc(setting) ..add(const AppFlowyCloudSettingEvent.initial()), child: Column( + mainAxisSize: MainAxisSize.min, children: children, ), ); @@ -173,8 +180,10 @@ class AppFlowyCloudURLs extends StatelessWidget { child: BlocBuilder( builder: (context, state) { return Column( + mainAxisSize: MainAxisSize.min, children: [ - const AppFlowySelfhostTip(), + const AppFlowySelfHostTip(), + const VSpace(12), CloudURLInput( title: LocaleKeys.settings_menu_cloudURL.tr(), url: state.config.base_url, @@ -188,6 +197,20 @@ class AppFlowyCloudURLs extends StatelessWidget { }, ), const VSpace(8), + CloudURLInput( + title: LocaleKeys.settings_menu_webURL.tr(), + url: state.config.base_web_domain, + hint: LocaleKeys.settings_menu_webURLHint.tr(), + hintBuilder: (context) => const WebUrlHintWidget(), + onChanged: (text) { + context.read().add( + AppFlowyCloudURLsEvent.updateBaseWebDomain( + text, + ), + ); + }, + ), + const VSpace(12), RestartButton( onClick: () { NavigatorAlertDialog( @@ -210,8 +233,8 @@ class AppFlowyCloudURLs extends StatelessWidget { } } -class AppFlowySelfhostTip extends StatelessWidget { - const AppFlowySelfhostTip({super.key}); +class AppFlowySelfHostTip extends StatelessWidget { + const AppFlowySelfHostTip({super.key}); final url = "https://docs.appflowy.io/docs/guides/appflowy/self-hosting-appflowy#build-appflowy-with-a-self-hosted-server"; @@ -256,12 +279,14 @@ class CloudURLInput extends StatefulWidget { required this.url, required this.hint, required this.onChanged, + this.hintBuilder, }); final String title; final String url; final String hint; - final Function(String) onChanged; + final ValueChanged onChanged; + final WidgetBuilder? hintBuilder; @override CloudURLInputState createState() => CloudURLInputState(); @@ -284,27 +309,55 @@ class CloudURLInputState extends State { @override Widget build(BuildContext context) { - return TextField( - controller: _controller, - style: const TextStyle(fontSize: 12.0), - decoration: InputDecoration( - contentPadding: const EdgeInsets.symmetric(vertical: 6), - labelText: widget.title, - labelStyle: Theme.of(context) - .textTheme - .titleMedium! - .copyWith(fontWeight: FontWeight.w400, fontSize: 16), - enabledBorder: UnderlineInputBorder( - borderSide: - BorderSide(color: AFThemeExtension.of(context).onBackground), + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildHint(context), + SizedBox( + height: 28, + child: TextField( + controller: _controller, + style: Theme.of(context).textTheme.titleMedium!.copyWith( + fontSize: 14, + fontWeight: FontWeight.w400, + ), + decoration: InputDecoration( + enabledBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: AFThemeExtension.of(context).onBackground, + ), + ), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: Theme.of(context).colorScheme.primary, + ), + ), + hintText: widget.hint, + errorText: context.read().state.urlError, + ), + onChanged: widget.onChanged, + ), ), - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide(color: Theme.of(context).colorScheme.primary), - ), - hintText: widget.hint, - errorText: context.read().state.urlError, + ], + ); + } + + Widget _buildHint(BuildContext context) { + final children = [ + FlowyText( + widget.title, + fontSize: 12, ), - onChanged: widget.onChanged, + ]; + + if (widget.hintBuilder != null) { + children.add(widget.hintBuilder!(context)); + } + + return Row( + mainAxisSize: MainAxisSize.min, + children: children, ); } } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_cloud.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_cloud.dart index d937b47736..692be99baa 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_cloud.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_cloud.dart @@ -1,5 +1,3 @@ -import 'package:flutter/material.dart'; - import 'package:appflowy/env/cloud_env.dart'; import 'package:appflowy/env/env.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart'; @@ -16,6 +14,7 @@ import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:universal_platform/universal_platform.dart'; @@ -47,23 +46,7 @@ class SettingCloud extends StatelessWidget { autoSeparate: false, children: [ if (Env.enableCustomCloud) - Row( - children: [ - Expanded( - child: FlowyText.medium( - LocaleKeys.settings_menu_cloudServerType.tr(), - ), - ), - Flexible( - child: CloudTypeSwitcher( - cloudType: state.cloudType, - onSelected: (type) => context - .read() - .add(CloudSettingEvent.updateCloudType(type)), - ), - ), - ], - ), + _CloudServerSwitcher(cloudType: state.cloudType), _viewFromCloudType(state.cloudType), ], ); @@ -137,7 +120,9 @@ class CloudTypeSwitcher extends StatelessWidget { .toList(), ) : FlowyButton( - text: FlowyText(titleFromCloudType(cloudType)), + text: FlowyText( + titleFromCloudType(cloudType), + ), useIntrinsicWidth: true, rightIcon: const Icon( Icons.chevron_right, @@ -172,12 +157,12 @@ class CloudTypeItem extends StatelessWidget { const CloudTypeItem({ super.key, required this.cloudType, - required this.currentCloudtype, + required this.currentCloudType, required this.onSelected, }); final AuthenticatorType cloudType; - final AuthenticatorType currentCloudtype; + final AuthenticatorType currentCloudType; final Function(AuthenticatorType) onSelected; @override @@ -188,11 +173,11 @@ class CloudTypeItem extends StatelessWidget { text: FlowyText.medium( titleFromCloudType(cloudType), ), - rightIcon: currentCloudtype == cloudType + rightIcon: currentCloudType == cloudType ? const FlowySvg(FlowySvgs.check_s) : null, onTap: () { - if (currentCloudtype != cloudType) { + if (currentCloudType != cloudType) { NavigatorAlertDialog( title: LocaleKeys.settings_menu_changeServerTip.tr(), confirm: () async { @@ -208,6 +193,50 @@ class CloudTypeItem extends StatelessWidget { } } +class _CloudServerSwitcher extends StatelessWidget { + const _CloudServerSwitcher({ + required this.cloudType, + }); + + final AuthenticatorType cloudType; + + @override + Widget build(BuildContext context) { + return UniversalPlatform.isDesktopOrWeb + ? Row( + children: [ + Expanded( + child: FlowyText.medium( + LocaleKeys.settings_menu_cloudServerType.tr(), + ), + ), + Flexible( + child: CloudTypeSwitcher( + cloudType: cloudType, + onSelected: (type) => context + .read() + .add(CloudSettingEvent.updateCloudType(type)), + ), + ), + ], + ) + : Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + FlowyText.medium( + LocaleKeys.settings_menu_cloudServerType.tr(), + ), + CloudTypeSwitcher( + cloudType: cloudType, + onSelected: (type) => context + .read() + .add(CloudSettingEvent.updateCloudType(type)), + ), + ], + ); + } +} + String titleFromCloudType(AuthenticatorType cloudType) { switch (cloudType) { case AuthenticatorType.local: diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_third_party_login.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_third_party_login.dart index cf51d7a3e9..8a85377efe 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_third_party_login.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/setting_third_party_login.dart @@ -2,7 +2,6 @@ import 'package:appflowy/env/cloud_env.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/user/application/sign_in_bloc.dart'; -import 'package:appflowy/user/presentation/router.dart'; import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart'; @@ -64,12 +63,8 @@ class SettingThirdPartyLogin extends StatelessWidget { ) async { result.fold( (user) async { - if (user.encryptionType == EncryptionTypePB.Symmetric) { - getIt().pushEncryptionScreen(context, user); - } else { - didLogin(); - await runAppFlowy(); - } + didLogin(); + await runAppFlowy(); }, (error) => showSnapBar(context, error.msg), ); diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_menu.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_menu.dart index c9069b8be3..f628aadc6b 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_menu.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_menu.dart @@ -52,52 +52,51 @@ class SettingsMenu extends StatelessWidget { page: SettingsPage.account, selectedPage: currentPage, label: LocaleKeys.settings_accountPage_menuLabel.tr(), - icon: const FlowySvg(FlowySvgs.settings_account_m), + icon: const FlowySvg(FlowySvgs.settings_page_user_m), changeSelectedPage: changeSelectedPage, ), SettingsMenuElement( page: SettingsPage.workspace, selectedPage: currentPage, label: LocaleKeys.settings_workspacePage_menuLabel.tr(), - icon: const FlowySvg(FlowySvgs.settings_workplace_m), + icon: const FlowySvg(FlowySvgs.settings_page_workspace_m), changeSelectedPage: changeSelectedPage, ), if (FeatureFlag.membersSettings.isOn && - userProfile.authenticator == - AuthenticatorPB.AppFlowyCloud) + userProfile.workspaceAuthType == AuthTypePB.Server) SettingsMenuElement( page: SettingsPage.member, selectedPage: currentPage, label: LocaleKeys.settings_appearance_members_label.tr(), - icon: const Icon(Icons.people), + icon: const FlowySvg(FlowySvgs.settings_page_users_m), changeSelectedPage: changeSelectedPage, ), SettingsMenuElement( page: SettingsPage.manageData, selectedPage: currentPage, label: LocaleKeys.settings_manageDataPage_menuLabel.tr(), - icon: const FlowySvg(FlowySvgs.settings_data_m), + icon: const FlowySvg(FlowySvgs.settings_page_database_m), changeSelectedPage: changeSelectedPage, ), SettingsMenuElement( page: SettingsPage.notifications, selectedPage: currentPage, label: LocaleKeys.settings_menu_notifications.tr(), - icon: const Icon(Icons.notifications_outlined), + icon: const FlowySvg(FlowySvgs.settings_page_bell_m), changeSelectedPage: changeSelectedPage, ), SettingsMenuElement( page: SettingsPage.cloud, selectedPage: currentPage, label: LocaleKeys.settings_menu_cloudSettings.tr(), - icon: const Icon(Icons.sync), + icon: const FlowySvg(FlowySvgs.settings_page_cloud_m), changeSelectedPage: changeSelectedPage, ), SettingsMenuElement( page: SettingsPage.shortcuts, selectedPage: currentPage, label: LocaleKeys.settings_shortcutsPage_menuLabel.tr(), - icon: const FlowySvg(FlowySvgs.settings_shortcuts_m), + icon: const FlowySvg(FlowySvgs.settings_page_keyboard_m), changeSelectedPage: changeSelectedPage, ), SettingsMenuElement( @@ -105,18 +104,17 @@ class SettingsMenu extends StatelessWidget { selectedPage: currentPage, label: LocaleKeys.settings_aiPage_menuLabel.tr(), icon: const FlowySvg( - FlowySvgs.ai_summary_generate_s, + FlowySvgs.settings_page_ai_m, size: Size.square(24), ), changeSelectedPage: changeSelectedPage, ), - if (userProfile.authenticator == - AuthenticatorPB.AppFlowyCloud) + if (userProfile.workspaceAuthType == AuthTypePB.Server) SettingsMenuElement( page: SettingsPage.sites, selectedPage: currentPage, label: LocaleKeys.settings_sites_title.tr(), - icon: const Icon(Icons.web), + icon: const FlowySvg(FlowySvgs.settings_page_earth_m), changeSelectedPage: changeSelectedPage, ), if (FeatureFlag.planBilling.isOn && isBillingEnabled) ...[ @@ -124,14 +122,15 @@ class SettingsMenu extends StatelessWidget { page: SettingsPage.plan, selectedPage: currentPage, label: LocaleKeys.settings_planPage_menuLabel.tr(), - icon: const FlowySvg(FlowySvgs.settings_plan_m), + icon: const FlowySvg(FlowySvgs.settings_page_plan_m), changeSelectedPage: changeSelectedPage, ), SettingsMenuElement( page: SettingsPage.billing, selectedPage: currentPage, label: LocaleKeys.settings_billingPage_menuLabel.tr(), - icon: const FlowySvg(FlowySvgs.settings_billing_m), + icon: + const FlowySvg(FlowySvgs.settings_page_credit_card_m), changeSelectedPage: changeSelectedPage, ), ], diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload_decoration.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload_decoration.dart index f3cd25afde..ea8ebfe36b 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload_decoration.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload_decoration.dart @@ -16,8 +16,8 @@ class ThemeUploadDecoration extends StatelessWidget { borderRadius: BorderRadius.circular(ThemeUploadWidget.borderRadius), color: Theme.of(context).colorScheme.surface, border: Border.all( - color: AFThemeExtension.of(context).onBackground.withOpacity( - ThemeUploadWidget.fadeOpacity, + color: AFThemeExtension.of(context).onBackground.withValues( + alpha: ThemeUploadWidget.fadeOpacity, ), ), ), @@ -28,7 +28,7 @@ class ThemeUploadDecoration extends StatelessWidget { color: Theme.of(context) .colorScheme .onSurface - .withOpacity(ThemeUploadWidget.fadeOpacity), + .withValues(alpha: ThemeUploadWidget.fadeOpacity), radius: const Radius.circular(ThemeUploadWidget.borderRadius), child: ClipRRect( borderRadius: BorderRadius.circular(ThemeUploadWidget.borderRadius), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload_failure_widget.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload_failure_widget.dart index edb382d6ee..a7286bee48 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload_failure_widget.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload_failure_widget.dart @@ -15,7 +15,7 @@ class ThemeUploadFailureWidget extends StatelessWidget { color: Theme.of(context) .colorScheme .error - .withOpacity(ThemeUploadWidget.fadeOpacity), + .withValues(alpha: ThemeUploadWidget.fadeOpacity), constraints: const BoxConstraints.expand(), padding: ThemeUploadWidget.padding, child: Column( diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload_learn_more_button.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload_learn_more_button.dart index 9e25dece0e..bdc5ef0546 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload_learn_more_button.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload_learn_more_button.dart @@ -1,12 +1,12 @@ import 'package:appflowy/core/helpers/url_launcher.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/shared/error_page/error_page.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/theme_upload/theme_upload_view.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/widget/buttons/secondary_button.dart'; -import 'package:flowy_infra_ui/widget/error_page.dart'; import 'package:flutter/material.dart'; class ThemeUploadLearnMoreButton extends StatelessWidget { diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload_loading_widget.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload_loading_widget.dart index 5e0ad15f38..1d3e7ab0f8 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload_loading_widget.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/theme_upload_loading_widget.dart @@ -14,7 +14,7 @@ class ThemeUploadLoadingWidget extends StatelessWidget { color: Theme.of(context) .colorScheme .surface - .withOpacity(ThemeUploadWidget.fadeOpacity), + .withValues(alpha: ThemeUploadWidget.fadeOpacity), constraints: const BoxConstraints.expand(), child: Column( mainAxisAlignment: MainAxisAlignment.center, diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/upload_new_theme_widget.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/upload_new_theme_widget.dart index 0113d26a37..02a7c8e7ab 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/upload_new_theme_widget.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/theme_upload/upload_new_theme_widget.dart @@ -15,7 +15,7 @@ class UploadNewThemeWidget extends StatelessWidget { color: Theme.of(context) .colorScheme .surface - .withOpacity(ThemeUploadWidget.fadeOpacity), + .withValues(alpha: ThemeUploadWidget.fadeOpacity), padding: ThemeUploadWidget.padding, child: Column( mainAxisAlignment: MainAxisAlignment.center, diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/web_url_hint_widget.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/web_url_hint_widget.dart new file mode 100644 index 0000000000..ecf3cc7ef7 --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/web_url_hint_widget.dart @@ -0,0 +1,34 @@ +import 'package:appflowy/core/helpers/url_launcher.dart'; +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/style_widget/icon_button.dart'; +import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; +import 'package:flutter/material.dart'; + +class WebUrlHintWidget extends StatelessWidget { + const WebUrlHintWidget({super.key}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(left: 2), + child: FlowyTooltip( + message: LocaleKeys.workspace_learnMore.tr(), + preferBelow: false, + child: FlowyIconButton( + width: 24, + height: 24, + icon: const FlowySvg( + FlowySvgs.information_s, + ), + onPressed: () { + afLaunchUrlString( + 'https://appflowy.com/docs/self-host-appflowy-run-appflowy-web', + ); + }, + ), + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/date_picker/appflowy_date_picker_base.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/date_picker/appflowy_date_picker_base.dart index 8bfc187422..d965670f77 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/date_picker/appflowy_date_picker_base.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/date_picker/appflowy_date_picker_base.dart @@ -23,6 +23,7 @@ abstract class AppFlowyDatePicker extends StatefulWidget { this.onIncludeTimeChanged, this.onIsRangeChanged, this.onReminderSelected, + this.enableDidUpdate = true, }); final DateTime? dateTime; @@ -55,6 +56,7 @@ abstract class AppFlowyDatePicker extends StatefulWidget { final ReminderOption reminderOption; final OnReminderSelected? onReminderSelected; + final bool enableDidUpdate; } abstract class AppFlowyDatePickerState @@ -75,34 +77,31 @@ abstract class AppFlowyDatePickerState @override void initState() { super.initState(); - - dateTime = widget.dateTime; - startDateTime = widget.isRange ? widget.dateTime : null; - endDateTime = widget.isRange ? widget.endDateTime : null; - includeTime = widget.includeTime; - isRange = widget.isRange; - reminderOption = widget.reminderOption; + initData(); focusedDateTime = widget.dateTime ?? DateTime.now(); } @override void didUpdateWidget(covariant oldWidget) { - dateTime = widget.dateTime; - if (widget.isRange) { - startDateTime = widget.dateTime; - endDateTime = widget.endDateTime; - } else { - startDateTime = endDateTime = null; + if (widget.enableDidUpdate) { + initData(); } - includeTime = widget.includeTime; - isRange = widget.isRange; if (oldWidget.reminderOption != widget.reminderOption) { reminderOption = widget.reminderOption; } super.didUpdateWidget(oldWidget); } + void initData() { + dateTime = widget.dateTime; + startDateTime = widget.isRange ? widget.dateTime : null; + endDateTime = widget.isRange ? widget.endDateTime : null; + includeTime = widget.includeTime; + isRange = widget.isRange; + reminderOption = widget.reminderOption; + } + void onDateSelectedFromDatePicker( DateTime? newStartDateTime, DateTime? newEndDateTime, diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/date_picker/desktop_date_picker.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/date_picker/desktop_date_picker.dart index c404f576b1..fada23e994 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/date_picker/desktop_date_picker.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/date_picker/desktop_date_picker.dart @@ -32,6 +32,7 @@ class DesktopAppFlowyDatePicker extends AppFlowyDatePicker { super.onIncludeTimeChanged, super.onIsRangeChanged, super.onReminderSelected, + super.enableDidUpdate, this.popoverMutex, this.options = const [], }); diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/date_picker/widgets/date_picker_dialog.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/date_picker/widgets/date_picker_dialog.dart index 301fd038ee..54fc2fac2a 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/date_picker/widgets/date_picker_dialog.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/date_picker/widgets/date_picker_dialog.dart @@ -16,7 +16,6 @@ import 'package:flutter/services.dart'; class DatePickerOptions { DatePickerOptions({ DateTime? focusedDay, - this.popoverMutex, this.selectedDay, this.includeTime = false, this.isRange = false, @@ -31,7 +30,6 @@ class DatePickerOptions { }) : focusedDay = focusedDay ?? DateTime.now(); final DateTime focusedDay; - final PopoverMutex? popoverMutex; final DateTime? selectedDay; final bool includeTime; final bool isRange; @@ -48,6 +46,7 @@ class DatePickerOptions { abstract class DatePickerService { void show(Offset offset, {required DatePickerOptions options}); + void dismiss(); } @@ -60,6 +59,7 @@ class DatePickerMenu extends DatePickerService { final BuildContext context; final EditorState editorState; + PopoverMutex? popoverMutex; OverlayEntry? _menuEntry; @@ -67,6 +67,9 @@ class DatePickerMenu extends DatePickerService { void dismiss() { _menuEntry?.remove(); _menuEntry = null; + popoverMutex?.close(); + popoverMutex?.dispose(); + popoverMutex = null; } @override @@ -97,6 +100,7 @@ class DatePickerMenu extends DatePickerService { } } + popoverMutex = PopoverMutex(); _menuEntry = OverlayEntry( builder: (_) => Material( type: MaterialType.transparency, @@ -119,6 +123,7 @@ class DatePickerMenu extends DatePickerService { offset: Offset(offsetX, offsetY), showBelow: showBelow, options: options, + popoverMutex: popoverMutex, ), ], ), @@ -137,11 +142,13 @@ class _AnimatedDatePicker extends StatelessWidget { required this.offset, required this.showBelow, required this.options, + this.popoverMutex, }); final Offset offset; final bool showBelow; final DatePickerOptions options; + final PopoverMutex? popoverMutex; @override Widget build(BuildContext context) { @@ -165,11 +172,12 @@ class _AnimatedDatePicker extends StatelessWidget { dateFormat: options.dateFormat.simplified, timeFormat: options.timeFormat.simplified, dateTime: options.selectedDay, - popoverMutex: options.popoverMutex, + popoverMutex: popoverMutex, reminderOption: options.selectedReminderOption ?? ReminderOption.none, onDaySelected: options.onDaySelected, onRangeSelected: options.onRangeSelected, onReminderSelected: options.onReminderSelected, + enableDidUpdate: false, ), ), ); diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/date_picker/widgets/date_time_text_field.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/date_picker/widgets/date_time_text_field.dart index acd50ce764..553ffb4c0d 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/date_picker/widgets/date_time_text_field.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/date_picker/widgets/date_time_text_field.dart @@ -301,6 +301,14 @@ class _DateTimeTextFieldState extends State { focusNode: timeFocusNode, controller: timeTextController, style: Theme.of(context).textTheme.bodyMedium, + maxLength: widget.timeFormat == TimeFormatPB.TwelveHour + ? 8 // 12:34 PM = 8 characters + : 5, // 12:34 = 5 characters + inputFormatters: [ + FilteringTextInputFormatter.allow( + RegExp('[0-9:AaPpMm]'), + ), + ], decoration: getInputDecoration( const EdgeInsetsDirectional.fromSTEB(6, 6, 12, 6), timeFormat.format(hintDate), @@ -359,6 +367,7 @@ class _DateTimeTextFieldState extends State { isCollapsed: true, isDense: true, hintText: widget.showHint ? hintText : null, + counterText: "", hintStyle: Theme.of(context) .textTheme .bodyMedium diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialog_v2.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialog_v2.dart new file mode 100644 index 0000000000..43ab8897e1 --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialog_v2.dart @@ -0,0 +1,109 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:flutter/material.dart'; + +typedef SimpleAFDialogAction = (String, void Function(BuildContext)?); + +/// A simple dialog with a title, content, and actions. +/// +/// The primary button is a filled button and colored using theme or destructive +/// color depending on the [isDestructive] parameter. The secondary button is an +/// outlined button. +/// +Future showSimpleAFDialog({ + required BuildContext context, + required String title, + required String content, + bool isDestructive = false, + required SimpleAFDialogAction primaryAction, + SimpleAFDialogAction? secondaryAction, + bool barrierDismissible = true, +}) { + final theme = AppFlowyTheme.of(context); + + return showDialog( + context: context, + barrierColor: theme.surfaceColorScheme.overlay, + barrierDismissible: barrierDismissible, + builder: (_) { + return AFModal( + constraints: BoxConstraints( + maxWidth: AFModalDimension.S, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + AFModalHeader( + leading: Text( + title, + style: theme.textStyle.heading4.standard( + color: theme.textColorScheme.primary, + ), + ), + trailing: [ + AFGhostButton.normal( + onTap: () => Navigator.of(context).pop(), + builder: (context, isHovering, disabled) { + return FlowySvg( + FlowySvgs.close_s, + size: Size.square(20), + ); + }, + ), + ], + ), + Flexible( + child: ConstrainedBox( + // AFModalDimension.dialogHeight - header - footer + constraints: BoxConstraints(minHeight: 108.0), + child: AFModalBody( + child: Text(content), + ), + ), + ), + AFModalFooter( + trailing: [ + if (secondaryAction != null) + AFOutlinedButton.normal( + onTap: () { + secondaryAction.$2?.call(context); + Navigator.of(context).pop(); + }, + builder: (context, isHovering, disabled) { + return Text(secondaryAction.$1); + }, + ), + isDestructive + ? AFFilledButton.destructive( + onTap: () { + primaryAction.$2?.call(context); + Navigator.of(context).pop(); + }, + builder: (context, isHovering, disabled) { + return Text( + primaryAction.$1, + style: TextStyle( + color: AppFlowyTheme.of(context) + .textColorScheme + .onFill, + ), + ); + }, + ) + : AFFilledButton.primary( + onTap: () { + primaryAction.$2?.call(context); + Navigator.of(context).pop(); + }, + builder: (context, isHovering, disabled) { + return Text(primaryAction.$1); + }, + ), + ], + ), + ], + ), + ); + }, + ); +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart index 7686b1d891..8d65ee23bb 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart @@ -1,6 +1,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/startup/tasks/app_widget.dart'; +import 'package:appflowy/util/theme_extension.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/size.dart'; @@ -156,7 +157,6 @@ class _NavigatorTextFieldDialogState extends State { onOkPressed: () { if (newValue.isEmpty) { showToastNotification( - context, message: LocaleKeys.space_spaceNameCannotBeEmpty.tr(), ); return; @@ -362,73 +362,60 @@ class OkCancelButton extends StatelessWidget { } } -void showToastNotification( - BuildContext context, { - required String message, +ToastificationItem showToastNotification({ + String? message, + TextSpan? richMessage, String? description, ToastificationType type = ToastificationType.success, ToastificationCallbacks? callbacks, double bottomPadding = 100, }) { - if (UniversalPlatform.isMobile) { - toastification.showCustom( - alignment: Alignment.bottomCenter, - autoCloseDuration: const Duration(milliseconds: 3000), - callbacks: callbacks ?? const ToastificationCallbacks(), - builder: (_, __) => _MToast( - message: message, - type: type, - bottomPadding: bottomPadding, - description: description, - ), - ); - return; - } - - toastification.show( - context: context, - type: type, - style: ToastificationStyle.flat, - closeButtonShowType: CloseButtonShowType.onHover, + assert( + (message == null) != (richMessage == null), + "Exactly one of message or richMessage must be non-null.", + ); + return toastification.showCustom( alignment: Alignment.bottomCenter, autoCloseDuration: const Duration(milliseconds: 3000), - showProgressBar: false, - backgroundColor: Theme.of(context).colorScheme.surface, - borderSide: BorderSide( - color: Colors.grey.withOpacity(0.4), - ), - title: FlowyText( - message, - maxLines: 3, - ), - description: description != null - ? FlowyText.regular( - description, - fontSize: 12, - lineHeight: 1.2, - maxLines: 3, - ) - : null, + callbacks: callbacks ?? const ToastificationCallbacks(), + builder: (_, item) { + return UniversalPlatform.isMobile + ? _MobileToast( + message: message, + type: type, + bottomPadding: bottomPadding, + description: description, + ) + : DesktopToast( + message: message, + richMessage: richMessage, + type: type, + onDismiss: () => toastification.dismiss(item), + ); + }, ); } -class _MToast extends StatelessWidget { - const _MToast({ - required this.message, +class _MobileToast extends StatelessWidget { + const _MobileToast({ + this.message, this.type = ToastificationType.success, this.bottomPadding = 100, this.description, }); - final String message; + final String? message; final ToastificationType type; final double bottomPadding; final String? description; @override Widget build(BuildContext context) { + if (message == null) { + return const SizedBox.shrink(); + } final hintText = FlowyText.regular( - message, + message!, fontSize: 16.0, figmaLineHeight: 18.0, color: Colors.white, @@ -498,6 +485,92 @@ class _MToast extends StatelessWidget { } } +@visibleForTesting +class DesktopToast extends StatelessWidget { + const DesktopToast({ + super.key, + this.message, + this.richMessage, + required this.type, + this.onDismiss, + }); + + final String? message; + final TextSpan? richMessage; + final ToastificationType type; + final void Function()? onDismiss; + + @override + Widget build(BuildContext context) { + return Center( + child: Container( + constraints: const BoxConstraints(maxWidth: 360.0), + padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 8.0), + margin: const EdgeInsets.only(bottom: 32.0), + decoration: BoxDecoration( + color: Theme.of(context).isLightMode + ? const Color(0xFF333333) + : const Color(0xFF363D49), + borderRadius: const BorderRadius.all(Radius.circular(8.0)), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // icon + FlowySvg( + switch (type) { + ToastificationType.warning => FlowySvgs.toast_warning_filled_s, + ToastificationType.success => FlowySvgs.toast_checked_filled_s, + ToastificationType.error => FlowySvgs.toast_error_filled_s, + _ => throw UnimplementedError(), + }, + size: const Size.square(20.0), + blendMode: null, + ), + const HSpace(8.0), + // text + Flexible( + child: message != null + ? FlowyText( + message!, + maxLines: 2, + figmaLineHeight: 20.0, + overflow: TextOverflow.ellipsis, + color: const Color(0xFFFFFFFF), + ) + : RichText( + text: richMessage!, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + const HSpace(16.0), + // close + MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: onDismiss, + child: const SizedBox.square( + dimension: 24.0, + child: Center( + child: FlowySvg( + FlowySvgs.toast_close_s, + size: Size.square(16.0), + color: Color(0xFFBDBDBD), + ), + ), + ), + ), + ), + ], + ), + ), + ); + } +} + Future showConfirmDeletionDialog({ required BuildContext context, required String name, @@ -533,6 +606,7 @@ Future showConfirmDialog({ VoidCallback? onCancel, String? confirmLabel, ConfirmPopupStyle style = ConfirmPopupStyle.onlyOk, + WidgetBuilder? confirmButtonBuilder, }) { return showDialog( context: context, @@ -546,6 +620,7 @@ Future showConfirmDialog({ child: ConfirmPopup( title: title, description: description, + confirmButtonBuilder: confirmButtonBuilder, onConfirm: () => onConfirm?.call(), onCancel: () => onCancel?.call(), confirmLabel: confirmLabel, @@ -598,9 +673,13 @@ Future showCustomConfirmDialog({ String? confirmLabel, ConfirmPopupStyle style = ConfirmPopupStyle.onlyOk, bool closeOnConfirm = true, + bool showCloseButton = true, + bool enableKeyboardListener = true, + bool barrierDismissible = true, }) { return showDialog( context: context, + barrierDismissible: barrierDismissible, builder: (context) { return Dialog( shape: RoundedRectangleBorder( @@ -617,6 +696,8 @@ Future showCustomConfirmDialog({ confirmButtonColor: Theme.of(context).colorScheme.primary, style: style, closeOnAction: closeOnConfirm, + showCloseButton: showCloseButton, + enableKeyboardListener: enableKeyboardListener, child: builder(context), ), ), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/draggable_item/draggable_item.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/draggable_item/draggable_item.dart index cfada71311..5b3962cd63 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/draggable_item/draggable_item.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/draggable_item/draggable_item.dart @@ -1,6 +1,11 @@ import 'package:flutter/material.dart'; import 'package:universal_platform/universal_platform.dart'; +/// This value is used to disable the auto scroll when dragging. +/// +/// It is used to prevent the auto scroll when dragging a view item to a document. +bool disableAutoScrollWhenDragging = false; + class DraggableItem extends StatefulWidget { const DraggableItem({ super.key, @@ -67,7 +72,7 @@ class _DraggableItemState extends State> { childWhenDragging: widget.childWhenDragging ?? widget.child, child: widget.child, onDragUpdate: (details) { - if (widget.enableAutoScroll) { + if (widget.enableAutoScroll && !disableAutoScrollWhenDragging) { dragTarget = details.globalPosition & widget.hitTestSize; autoScroller?.startAutoScrollIfNecessary(dragTarget!); } @@ -88,7 +93,7 @@ class _DraggableItemState extends State> { } void initAutoScrollerIfNeeded(BuildContext context) { - if (!widget.enableAutoScroll) { + if (!widget.enableAutoScroll || disableAutoScrollWhenDragging) { return; } @@ -104,7 +109,7 @@ class _DraggableItemState extends State> { autoScroller = EdgeDraggingAutoScroller( scrollable!, onScrollViewScrolled: () { - if (dragTarget != null) { + if (dragTarget != null && !disableAutoScrollWhenDragging) { autoScroller!.startAutoScrollIfNecessary(dragTarget!); } }, diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/question_bubble.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/question_bubble.dart index d666e606f6..e3117c7f86 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/question_bubble.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/question_bubble.dart @@ -86,7 +86,7 @@ class _BubbleActionListState extends State { ), buildChild: (controller) { return FlowyTooltip( - message: LocaleKeys.questionBubble_help.tr(), + message: LocaleKeys.questionBubble_getSupport.tr(), child: MouseRegion( cursor: SystemMouseCursors.click, child: GestureDetector( @@ -121,22 +121,22 @@ class _BubbleActionListState extends State { if (action is BubbleActionWrapper) { switch (action.inner) { case BubbleAction.whatsNews: - afLaunchUrlString("https://www.appflowy.io/what-is-new"); + afLaunchUrlString('https://www.appflowy.io/what-is-new'); break; - case BubbleAction.help: - afLaunchUrlString("https://discord.gg/9Q2xaN37tV"); + case BubbleAction.getSupport: + afLaunchUrlString('https://discord.gg/9Q2xaN37tV'); break; case BubbleAction.debug: _DebugToast().show(); break; case BubbleAction.shortcuts: afLaunchUrlString( - "https://docs.appflowy.io/docs/appflowy/product/shortcuts", + 'https://docs.appflowy.io/docs/appflowy/product/shortcuts', ); break; case BubbleAction.markdown: afLaunchUrlString( - "https://docs.appflowy.io/docs/appflowy/product/markdown", + 'https://docs.appflowy.io/docs/appflowy/product/markdown', ); break; case BubbleAction.github: @@ -144,6 +144,11 @@ class _BubbleActionListState extends State { 'https://github.com/AppFlowy-IO/AppFlowy/issues/new/choose', ); break; + case BubbleAction.helpAndDocumentation: + afLaunchUrlString( + 'https://appflowy.com/guide', + ); + break; } } @@ -155,7 +160,7 @@ class _BubbleActionListState extends State { class _DebugToast { void show() async { - String debugInfo = ""; + String debugInfo = ''; debugInfo += await _getDeviceInfo(); debugInfo += await _getDocumentPath(); await Clipboard.setData(ClipboardData(text: debugInfo)); @@ -168,20 +173,21 @@ class _DebugToast { final deviceInfo = await deviceInfoPlugin.deviceInfo; return deviceInfo.data.entries - .fold('', (prev, el) => "$prev${el.key}: ${el.value}\n"); + .fold('', (prev, el) => '$prev${el.key}: ${el.value}\n'); } Future _getDocumentPath() async { return appFlowyApplicationDataDirectory().then((directory) { final path = directory.path.toString(); - return "Document: $path\n"; + return 'Document: $path\n'; }); } } enum BubbleAction { whatsNews, - help, + helpAndDocumentation, + getSupport, debug, shortcuts, markdown, @@ -204,8 +210,10 @@ extension QuestionBubbleExtension on BubbleAction { switch (this) { case BubbleAction.whatsNews: return LocaleKeys.questionBubble_whatsNew.tr(); - case BubbleAction.help: - return LocaleKeys.questionBubble_help.tr(); + case BubbleAction.helpAndDocumentation: + return LocaleKeys.questionBubble_helpAndDocumentation.tr(); + case BubbleAction.getSupport: + return LocaleKeys.questionBubble_getSupport.tr(); case BubbleAction.debug: return LocaleKeys.questionBubble_debug_name.tr(); case BubbleAction.shortcuts: @@ -221,7 +229,12 @@ extension QuestionBubbleExtension on BubbleAction { switch (this) { case BubbleAction.whatsNews: return const FlowySvg(FlowySvgs.star_s); - case BubbleAction.help: + case BubbleAction.helpAndDocumentation: + return const FlowySvg( + FlowySvgs.help_and_documentation_s, + size: Size.square(16.0), + ); + case BubbleAction.getSupport: return const FlowySvg(FlowySvgs.message_support_s); case BubbleAction.debug: return const FlowySvg(FlowySvgs.debug_s); diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/social_media_section.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/social_media_section.dart index f5c8ffa146..8b58557455 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/social_media_section.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/social_media_section.dart @@ -25,18 +25,13 @@ class SocialMediaSection extends CustomActionCell { action: SocialMediaWrapper(social), itemHeight: ActionListSizes.itemHeight, onSelected: (action) { - switch (action.inner) { - case SocialMedia.reddit: - afLaunchUrlString( - 'https://www.reddit.com/r/AppFlowy/', - ); - case SocialMedia.twitter: - afLaunchUrlString( - 'https://x.com/appflowy', - ); - case SocialMedia.forum: - afLaunchUrlString('https://forum.appflowy.io/'); - } + final url = switch (action.inner) { + SocialMedia.reddit => 'https://www.reddit.com/r/AppFlowy/', + SocialMedia.twitter => 'https://x.com/appflowy', + SocialMedia.forum => 'https://forum.appflowy.com/', + }; + + afLaunchUrlString(url); }, ); }, @@ -79,20 +74,17 @@ extension QuestionBubbleExtension on SocialMedia { case SocialMedia.forum: return Theme.of(context).hintColor; - - default: - return null; } } String get name { switch (this) { case SocialMedia.forum: - return "Community Forum"; + return 'Community Forum'; case SocialMedia.twitter: - return "Twitter – @appflowy"; + return 'Twitter – @appflowy'; case SocialMedia.reddit: - return "Reddit – r/appflowy"; + return 'Reddit – r/appflowy'; } } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/version_section.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/version_section.dart index 923f695188..f6a2caa5a2 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/version_section.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/float_bubble/version_section.dart @@ -51,7 +51,6 @@ class FlowyVersionSection extends CustomActionCell { } enableDocumentInternalLog = !enableDocumentInternalLog; showToastNotification( - context, message: enableDocumentInternalLog ? 'Enabled Internal Log' : 'Disabled Internal Log', diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/image_viewer/image_provider.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/image_viewer/image_provider.dart index 7fb9f1edbe..b69c56abf2 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/image_viewer/image_provider.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/image_viewer/image_provider.dart @@ -29,13 +29,13 @@ class AFBlockImageProvider implements AFImageProvider { const AFBlockImageProvider({ required this.images, this.initialIndex = 0, - required this.onDeleteImage, + this.onDeleteImage, }); final List images; @override - final Function(int) onDeleteImage; + final Function(int)? onDeleteImage; @override final int initialIndex; diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/image_viewer/interactive_image_toolbar.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/image_viewer/interactive_image_toolbar.dart index a602aaed58..765a385b0b 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/image_viewer/interactive_image_toolbar.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/image_viewer/interactive_image_toolbar.dart @@ -119,7 +119,7 @@ class InteractiveImageToolbar extends StatelessWidget { child: FlowyHover( resetHoverOnRebuild: false, style: HoverStyle( - hoverColor: Colors.white.withOpacity(0.1), + hoverColor: Colors.white.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(4), ), child: Padding( @@ -204,7 +204,7 @@ class InteractiveImageToolbar extends StatelessWidget { return DecoratedBox( decoration: BoxDecoration( borderRadius: BorderRadius.circular(6), - color: Colors.black.withOpacity(0.6), + color: Colors.black.withValues(alpha: 0.6), ), child: Padding( padding: const EdgeInsets.all(4), @@ -284,8 +284,9 @@ class _ToolbarItem extends StatelessWidget { child: FlowyHover( resetHoverOnRebuild: false, style: HoverStyle( - hoverColor: - isDisabled ? Colors.transparent : Colors.white.withOpacity(0.1), + hoverColor: isDisabled + ? Colors.transparent + : Colors.white.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(4), ), child: Container( diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/image_viewer/interactive_image_viewer.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/image_viewer/interactive_image_viewer.dart index 1678be5a16..143c6b1ad3 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/image_viewer/interactive_image_viewer.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/image_viewer/interactive_image_viewer.dart @@ -140,8 +140,9 @@ class _InteractiveImageViewerState extends State { final scaleStep = scale / currentScale; _zoom(scaleStep, size); }, - onDelete: () => - widget.imageProvider.onDeleteImage?.call(currentIndex), + onDelete: widget.imageProvider.onDeleteImage == null + ? null + : () => widget.imageProvider.onDeleteImage?.call(currentIndex), ), ], ), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/more_view_actions/more_view_actions.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/more_view_actions/more_view_actions.dart index 09885dc796..62b3ccc8f3 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/more_view_actions/more_view_actions.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/more_view_actions/more_view_actions.dart @@ -4,10 +4,13 @@ import 'package:appflowy/workspace/application/settings/appearance/appearance_cu import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart'; import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart'; import 'package:appflowy/workspace/application/view/view_bloc.dart'; +import 'package:appflowy/workspace/application/view/view_ext.dart'; +import 'package:appflowy/workspace/application/view/view_lock_status_bloc.dart'; import 'package:appflowy/workspace/application/view_info/view_info_bloc.dart'; import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart'; import 'package:appflowy/workspace/presentation/widgets/more_view_actions/widgets/common_view_action.dart'; import 'package:appflowy/workspace/presentation/widgets/more_view_actions/widgets/font_size_action.dart'; +import 'package:appflowy/workspace/presentation/widgets/more_view_actions/widgets/lock_page_action.dart'; import 'package:appflowy/workspace/presentation/widgets/more_view_actions/widgets/view_meta_info.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; @@ -21,14 +24,16 @@ class MoreViewActions extends StatefulWidget { const MoreViewActions({ super.key, required this.view, - this.isDocument = true, + this.customActions = const [], }); /// The view to show the actions for. + /// final ViewPB view; - /// If false the view is a Database, otherwise it is a Document. - final bool isDocument; + /// Custom actions to show in the popover, will be laid out at the top. + /// + final List customActions; @override State createState() => _MoreViewActionsState(); @@ -49,8 +54,9 @@ class _MoreViewActionsState extends State { builder: (context, state) { return AppFlowyPopover( mutex: popoverMutex, - constraints: const BoxConstraints(maxWidth: 220), - offset: const Offset(0, 42), + constraints: const BoxConstraints(maxWidth: 245), + direction: PopoverDirection.bottomWithRightAligned, + offset: const Offset(0, 12), popupBuilder: (_) => _buildPopup(state), child: const _ThreeDots(), ); @@ -58,18 +64,23 @@ class _MoreViewActionsState extends State { ); } - Widget _buildPopup(ViewInfoState state) { + Widget _buildPopup(ViewInfoState viewInfoState) { final userWorkspaceBloc = context.read(); final userProfile = userWorkspaceBloc.userProfile; final workspaceId = userWorkspaceBloc.state.currentWorkspace?.workspaceId ?? ''; - final actions = _buildActions(state); return MultiBlocProvider( providers: [ BlocProvider( - create: (_) => - ViewBloc(view: widget.view)..add(const ViewEvent.initial()), + create: (_) => ViewBloc(view: widget.view) + ..add( + const ViewEvent.initial(), + ), + ), + BlocProvider( + create: (_) => ViewLockStatusBloc(view: widget.view) + ..add(ViewLockStatusEvent.initial()), ), BlocProvider( create: (context) => SpaceBloc( @@ -80,47 +91,71 @@ class _MoreViewActionsState extends State { ), ), ], - child: BlocBuilder( - builder: (context, state) { - if (state.spaces.isEmpty && - userProfile.authenticator == AuthenticatorPB.AppFlowyCloud) { - return const SizedBox.shrink(); - } + child: BlocBuilder( + builder: (context, viewState) { + return BlocBuilder( + builder: (context, state) { + if (state.spaces.isEmpty && + userProfile.workspaceAuthType == AuthTypePB.Server) { + return const SizedBox.shrink(); + } - return ListView.builder( - key: ValueKey(state.spaces.hashCode), - shrinkWrap: true, - padding: EdgeInsets.zero, - itemCount: actions.length, - physics: StyledScrollPhysics(), - itemBuilder: (_, index) => actions[index], + final actions = _buildActions( + context, + viewInfoState, + ); + return ListView.builder( + key: ValueKey(state.spaces.hashCode), + shrinkWrap: true, + padding: EdgeInsets.zero, + itemCount: actions.length, + physics: StyledScrollPhysics(), + itemBuilder: (_, index) => actions[index], + ); + }, ); }, ), ); } - List _buildActions(ViewInfoState state) { + List _buildActions(BuildContext context, ViewInfoState state) { + final view = context.watch().state.view; final appearanceSettings = context.watch().state; final dateFormat = appearanceSettings.dateFormat; final timeFormat = appearanceSettings.timeFormat; final viewMoreActionTypes = [ - if (widget.isDocument) ViewMoreActionType.divider, - ViewMoreActionType.duplicate, + if (widget.view.layout != ViewLayoutPB.Chat) ViewMoreActionType.duplicate, ViewMoreActionType.moveTo, ViewMoreActionType.delete, ViewMoreActionType.divider, ]; final actions = [ - if (widget.isDocument) ...[ + ...widget.customActions, + if (widget.view.isDocument) ...[ const FontSizeAction(), + ViewAction( + type: ViewMoreActionType.divider, + view: view, + mutex: popoverMutex, + ), + ], + if (widget.view.isDocument || widget.view.isDatabase) ...[ + LockPageAction( + view: view, + ), + ViewAction( + type: ViewMoreActionType.divider, + view: view, + mutex: popoverMutex, + ), ], ...viewMoreActionTypes.map( (type) => ViewAction( type: type, - view: widget.view, + view: view, mutex: popoverMutex, ), ), @@ -129,6 +164,7 @@ class _MoreViewActionsState extends State { dateFormat: dateFormat, timeFormat: timeFormat, documentCounters: state.documentCounters, + titleCounters: state.titleCounters, createdAt: state.createdAt, ), const VSpace(4.0), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/more_view_actions/widgets/common_view_action.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/more_view_actions/widgets/common_view_action.dart index 5d0cf62f73..2ecec3244c 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/more_view_actions/widgets/common_view_action.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/more_view_actions/widgets/common_view_action.dart @@ -1,3 +1,4 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart'; import 'package:appflowy/workspace/application/view/view_bloc.dart'; @@ -9,8 +10,8 @@ import 'package:appflowy/workspace/presentation/home/menu/view/view_more_action_ import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; -import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -97,3 +98,51 @@ class ViewAction extends StatelessWidget { } } } + +class CustomViewAction extends StatelessWidget { + const CustomViewAction({ + super.key, + required this.view, + required this.leftIcon, + required this.label, + this.tooltipMessage, + this.disabled = false, + this.onTap, + this.mutex, + }); + + final ViewPB view; + final FlowySvgData leftIcon; + final String label; + final bool disabled; + final String? tooltipMessage; + final VoidCallback? onTap; + final PopoverMutex? mutex; + + @override + Widget build(BuildContext context) { + return Container( + height: 34, + padding: const EdgeInsets.symmetric(vertical: 2.0), + child: FlowyTooltip( + message: tooltipMessage, + child: FlowyButton( + margin: const EdgeInsets.symmetric(horizontal: 6), + disable: disabled, + onTap: onTap, + leftIcon: FlowySvg( + leftIcon, + size: const Size.square(16.0), + color: disabled ? Theme.of(context).disabledColor : null, + ), + iconPadding: 10.0, + text: FlowyText( + label, + figmaLineHeight: 18.0, + color: disabled ? Theme.of(context).disabledColor : null, + ), + ), + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/more_view_actions/widgets/lock_page_action.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/more_view_actions/widgets/lock_page_action.dart new file mode 100644 index 0000000000..202919b639 --- /dev/null +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/more_view_actions/widgets/lock_page_action.dart @@ -0,0 +1,119 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/workspace/application/view/view_lock_status_bloc.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +import 'package:easy_localization/easy_localization.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/flowy_tooltip.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class LockPageAction extends StatefulWidget { + const LockPageAction({ + super.key, + required this.view, + }); + + final ViewPB view; + + @override + State createState() => _LockPageActionState(); +} + +class _LockPageActionState extends State { + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => ViewLockStatusBloc(view: widget.view) + ..add( + ViewLockStatusEvent.initial(), + ), + child: BlocBuilder( + builder: (context, state) { + return _buildTextButton(context); + }, + ), + ); + } + + Widget _buildTextButton( + BuildContext context, + ) { + return Container( + height: 34, + padding: const EdgeInsets.symmetric(vertical: 2.0), + child: FlowyIconTextButton( + margin: const EdgeInsets.symmetric(horizontal: 6), + onTap: () => _toggle(context), + leftIconBuilder: (onHover) => FlowySvg( + FlowySvgs.lock_page_s, + size: const Size.square(16.0), + ), + iconPadding: 10.0, + textBuilder: (onHover) => FlowyText( + LocaleKeys.disclosureAction_lockPage.tr(), + figmaLineHeight: 18.0, + ), + rightIconBuilder: (_) => _buildSwitch( + context, + ), + ), + ); + } + + Widget _buildSwitch(BuildContext context) { + final lockState = context.read().state; + if (lockState.isLoadingLockStatus) { + return SizedBox.shrink(); + } + + return Container( + width: 30, + height: 20, + margin: const EdgeInsets.only(right: 6), + child: FittedBox( + fit: BoxFit.fill, + child: CupertinoSwitch( + value: lockState.isLocked, + activeTrackColor: Theme.of(context).colorScheme.primary, + onChanged: (_) => _toggle(context), + ), + ), + ); + } + + Future _toggle(BuildContext context) async { + final isLocked = context.read().state.isLocked; + + context.read().add( + isLocked ? ViewLockStatusEvent.unlock() : ViewLockStatusEvent.lock(), + ); + + Log.info('update page(${widget.view.id}) lock status: $isLocked'); + } +} + +class LockPageButtonWrapper extends StatelessWidget { + const LockPageButtonWrapper({ + super.key, + required this.child, + }); + + final Widget child; + + @override + Widget build(BuildContext context) { + return FlowyTooltip( + message: LocaleKeys.lockPage_lockedOperationTooltip.tr(), + child: IgnorePointer( + child: Opacity( + opacity: 0.5, + child: child, + ), + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/more_view_actions/widgets/view_meta_info.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/more_view_actions/widgets/view_meta_info.dart index 5b746a2aa3..27b96d39e9 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/more_view_actions/widgets/view_meta_info.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/more_view_actions/widgets/view_meta_info.dart @@ -13,12 +13,14 @@ class ViewMetaInfo extends StatelessWidget { required this.dateFormat, required this.timeFormat, this.documentCounters, + this.titleCounters, this.createdAt, }); final UserDateFormatPB dateFormat; final UserTimeFormatPB timeFormat; final Counters? documentCounters; + final Counters? titleCounters; final DateTime? createdAt; @override @@ -31,11 +33,15 @@ class ViewMetaInfo extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (documentCounters != null) ...[ + if (documentCounters != null && titleCounters != null) ...[ FlowyText.regular( LocaleKeys.moreAction_wordCount.tr( args: [ - numberFormat.format(documentCounters!.wordCount).toString(), + numberFormat + .format( + documentCounters!.wordCount + titleCounters!.wordCount, + ) + .toString(), ], ), fontSize: 12, @@ -45,7 +51,11 @@ class ViewMetaInfo extends StatelessWidget { FlowyText.regular( LocaleKeys.moreAction_charCount.tr( args: [ - numberFormat.format(documentCounters!.charCount).toString(), + numberFormat + .format( + documentCounters!.charCount + titleCounters!.charCount, + ) + .toString(), ], ), fontSize: 12, @@ -53,7 +63,8 @@ class ViewMetaInfo extends StatelessWidget { ), ], if (createdAt != null) ...[ - if (documentCounters != null) const VSpace(2), + if (documentCounters != null && titleCounters != null) + const VSpace(2), FlowyText.regular( LocaleKeys.moreAction_createdAt.tr( args: [dateFormat.formatDate(createdAt!, true, timeFormat)], diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/rename_view_popover.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/rename_view_popover.dart index ccea2f895c..954fc77603 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/rename_view_popover.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/rename_view_popover.dart @@ -1,5 +1,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/workspace/application/view/view_service.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flowy_infra_ui/style_widget/text_field.dart'; import 'package:flowy_infra_ui/widget/spacing.dart'; @@ -11,20 +13,22 @@ import '../../../shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; class RenameViewPopover extends StatefulWidget { const RenameViewPopover({ super.key, - required this.viewId, + required this.view, required this.name, required this.popoverController, required this.emoji, this.icon, this.showIconChanger = true, + this.tabs = const [PickerTabType.emoji, PickerTabType.icon], }); - final String viewId; + final ViewPB view; final String name; final PopoverController popoverController; final EmojiIconData emoji; final Widget? icon; final bool showIconChanger; + final List tabs; @override State createState() => _RenameViewPopoverState(); @@ -61,6 +65,8 @@ class _RenameViewPopoverState extends State { direction: PopoverDirection.bottomWithCenterAligned, offset: const Offset(0, 18), onSubmitted: _updateViewIcon, + documentId: widget.view.id, + tabs: widget.tabs, ), ), const HSpace(6), @@ -83,7 +89,7 @@ class _RenameViewPopoverState extends State { Future _updateViewName(String name) async { if (name.isNotEmpty && name != widget.name) { await ViewBackendService.updateView( - viewId: widget.viewId, + viewId: widget.view.id, name: _controller.text, ); widget.popoverController.close(); @@ -91,13 +97,15 @@ class _RenameViewPopoverState extends State { } Future _updateViewIcon( - EmojiIconData emoji, + SelectedEmojiIconResult r, PopoverController? _, ) async { await ViewBackendService.updateViewIcon( - viewId: widget.viewId, - viewIcon: emoji, + view: widget.view, + viewIcon: r.data, ); - widget.popoverController.close(); + if (!r.keepOpen) { + widget.popoverController.close(); + } } } diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/tab_bar_item.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/tab_bar_item.dart index 4ce5a93ab0..88474b20b3 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/tab_bar_item.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/tab_bar_item.dart @@ -1,9 +1,10 @@ +import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart'; +import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; -import 'package:flutter/material.dart'; - import 'package:appflowy/workspace/application/view/view_listener.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; class ViewTabBarItem extends StatefulWidget { const ViewTabBarItem({ @@ -50,11 +51,9 @@ class _ViewTabBarItemState extends State { widget.shortForm ? MainAxisAlignment.center : MainAxisAlignment.start, children: [ if (widget.view.icon.value.isNotEmpty) - FlowyText.emoji( - widget.view.icon.value, - fontSize: 16.0, - figmaLineHeight: 20.0, - optimizeEmojiAlign: true, + RawEmojiIconWidget( + emoji: widget.view.icon.toEmojiIconData(), + emojiSize: 16, ), if (!widget.shortForm && view.icon.value.isNotEmpty) const HSpace(6), if (!widget.shortForm || view.icon.value.isEmpty) ...[ diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/toggle/toggle.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/toggle/toggle.dart index 673f08c668..5cb834cbf3 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/toggle/toggle.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/toggle/toggle.dart @@ -1,6 +1,5 @@ -import 'package:flutter/material.dart'; - import 'package:flowy_infra/theme_extension.dart'; +import 'package:flutter/material.dart'; class ToggleStyle { const ToggleStyle({ @@ -38,6 +37,7 @@ class Toggle extends StatelessWidget { this.thumbColor, this.activeBackgroundColor, this.inactiveBackgroundColor, + this.duration = const Duration(milliseconds: 150), this.padding = const EdgeInsets.all(8.0), }); @@ -48,6 +48,7 @@ class Toggle extends StatelessWidget { final Color? activeBackgroundColor; final Color? inactiveBackgroundColor; final EdgeInsets padding; + final Duration duration; @override Widget build(BuildContext context) { @@ -70,7 +71,7 @@ class Toggle extends StatelessWidget { ), ), AnimatedPositioned( - duration: const Duration(milliseconds: 150), + duration: duration, top: (style.height - style.thumbRadius) / 2, left: value ? style.width - style.thumbRadius - 1 : 1, child: Container( diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/user_avatar.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/user_avatar.dart index adb3b1e454..347d95d01d 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/user_avatar.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/user_avatar.dart @@ -90,15 +90,14 @@ class UserAvatar extends StatelessWidget { : null, ), child: ClipRRect( - borderRadius: Corners.s5Border, - child: CircleAvatar( - backgroundColor: Colors.transparent, - child: Image.network( - iconUrl, - fit: BoxFit.cover, - errorBuilder: (context, error, stackTrace) => - _buildEmptyAvatar(context), - ), + borderRadius: BorderRadius.circular(size / 2), + child: Image.network( + iconUrl, + width: size, + height: size, + fit: BoxFit.cover, + errorBuilder: (context, error, stackTrace) => + _buildEmptyAvatar(context), ), ), ), diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/view_title_bar.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/view_title_bar.dart index 03eabab65c..3be0973123 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/view_title_bar.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/view_title_bar.dart @@ -1,19 +1,21 @@ -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/shared/icon_emoji_picker/tab.dart'; import 'package:appflowy/startup/plugin/plugin.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart'; +import 'package:appflowy/workspace/application/view/view_lock_status_bloc.dart'; import 'package:appflowy/workspace/application/view_title/view_title_bar_bloc.dart'; import 'package:appflowy/workspace/application/view_title/view_title_bloc.dart'; import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart'; import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon.dart'; +import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy/workspace/presentation/widgets/rename_view_popover.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../../../plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart'; @@ -29,8 +31,14 @@ class ViewTitleBar extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocProvider( - create: (_) => ViewTitleBarBloc(view: view), + return MultiBlocProvider( + providers: [ + BlocProvider(create: (_) => ViewTitleBarBloc(view: view)), + BlocProvider( + create: (_) => ViewLockStatusBloc(view: view) + ..add(const ViewLockStatusEvent.initial()), + ), + ], child: BlocBuilder( builder: (context, state) { final ancestors = state.ancestors; @@ -42,11 +50,14 @@ class ViewTitleBar extends StatelessWidget { child: SizedBox( height: 24, child: Row( - children: _buildViewTitles( - context, - ancestors, - state.isDeleted, - ), + children: [ + ..._buildViewTitles( + context, + ancestors, + state.isDeleted, + ), + _buildLockPageStatus(context), + ], ), ), ); @@ -55,6 +66,29 @@ class ViewTitleBar extends StatelessWidget { ); } + Widget _buildLockPageStatus(BuildContext context) { + return BlocConsumer( + listenWhen: (previous, current) => + previous.isLoadingLockStatus == current.isLoadingLockStatus && + current.isLoadingLockStatus == false, + listener: (context, state) { + if (state.isLocked) { + showToastNotification( + message: LocaleKeys.lockPage_pageLockedToast.tr(), + ); + } + }, + builder: (context, state) { + if (state.isLocked) { + return LockedPageStatus(); + } else if (!state.isLocked && state.lockCounter > 0) { + return ReLockedPageStatus(); + } + return const SizedBox.shrink(); + }, + ); + } + List _buildViewTitles( BuildContext context, List views, @@ -98,7 +132,7 @@ class ViewTitleBar extends StatelessWidget { message: view.name, child: ViewTitle( view: view, - behavior: i == views.length - 1 + behavior: i == views.length - 1 && !view.isLocked ? ViewTitleBehavior.editable // only the last one is editable : ViewTitleBehavior.uneditable, // others are not editable onUpdated: () { @@ -280,11 +314,16 @@ class _ViewTitleState extends State { // icon + textfield _resetTextEditingController(state); return RenameViewPopover( - viewId: widget.view.id, + view: widget.view, name: widget.view.name, popoverController: popoverController, icon: widget.view.defaultIcon(), emoji: state.icon, + tabs: const [ + PickerTabType.emoji, + PickerTabType.icon, + PickerTabType.custom, + ], ); }, child: SizedBox( @@ -345,3 +384,92 @@ class _ViewTitleState extends State { ); } } + +class LockedPageStatus extends StatelessWidget { + const LockedPageStatus({super.key}); + + @override + Widget build(BuildContext context) { + final color = const Color(0xFFD95A0B); + return FlowyTooltip( + message: LocaleKeys.lockPage_lockTooltip.tr(), + child: DecoratedBox( + decoration: ShapeDecoration( + shape: RoundedRectangleBorder( + side: BorderSide(color: color), + borderRadius: BorderRadius.circular(6), + ), + color: context.lockedPageButtonBackground, + ), + child: FlowyButton( + useIntrinsicWidth: true, + margin: const EdgeInsets.symmetric( + horizontal: 4.0, + vertical: 4.0, + ), + iconPadding: 4.0, + text: FlowyText.regular( + LocaleKeys.lockPage_lockPage.tr(), + color: color, + fontSize: 12.0, + ), + hoverColor: color.withValues(alpha: 0.1), + leftIcon: FlowySvg( + FlowySvgs.lock_page_fill_s, + blendMode: null, + ), + onTap: () => context.read().add( + const ViewLockStatusEvent.unlock(), + ), + ), + ), + ); + } +} + +class ReLockedPageStatus extends StatelessWidget { + const ReLockedPageStatus({super.key}); + + @override + Widget build(BuildContext context) { + final iconColor = const Color(0xFF8F959E); + return DecoratedBox( + decoration: ShapeDecoration( + shape: RoundedRectangleBorder( + side: BorderSide(color: iconColor), + borderRadius: BorderRadius.circular(6), + ), + color: context.lockedPageButtonBackground, + ), + child: FlowyButton( + useIntrinsicWidth: true, + margin: const EdgeInsets.symmetric( + horizontal: 4.0, + vertical: 4.0, + ), + iconPadding: 4.0, + text: FlowyText.regular( + LocaleKeys.lockPage_reLockPage.tr(), + fontSize: 12.0, + ), + leftIcon: FlowySvg( + FlowySvgs.unlock_page_s, + color: iconColor, + blendMode: null, + ), + onTap: () => context.read().add( + const ViewLockStatusEvent.lock(), + ), + ), + ); + } +} + +extension on BuildContext { + Color get lockedPageButtonBackground { + if (Theme.of(this).brightness == Brightness.light) { + return Colors.white.withValues(alpha: 0.75); + } + return Color(0xB21B1A22); + } +} diff --git a/frontend/appflowy_flutter/linux/packaging/assets/logo.png b/frontend/appflowy_flutter/linux/packaging/assets/logo.png new file mode 100644 index 0000000000..f34332a395 Binary files /dev/null and b/frontend/appflowy_flutter/linux/packaging/assets/logo.png differ diff --git a/frontend/appflowy_flutter/linux/packaging/deb/make_config.yaml b/frontend/appflowy_flutter/linux/packaging/deb/make_config.yaml new file mode 100644 index 0000000000..801a5dbc02 --- /dev/null +++ b/frontend/appflowy_flutter/linux/packaging/deb/make_config.yaml @@ -0,0 +1,36 @@ +display_name: AppFlowy +package_name: appflowy + +maintainer: + name: AppFlowy + email: support@appflowy.io + +keywords: + - AppFlowy + - Office + - Document + - Database + - Note + - Kanban + - Note + +installed_size: 100000 +icon: linux/packaging/assets/logo.png + +generic_name: AppFlowy + +categories: + - Office + - Productivity + +startup_notify: true +essential: false + +section: x11 +priority: optional + +supportedMimeType: x-scheme-handler/appflowy-flutter + +dependencies: + - libnotify-bin + - libkeybinder-3.0-0 diff --git a/frontend/appflowy_flutter/linux/packaging/rpm/make_config.yaml b/frontend/appflowy_flutter/linux/packaging/rpm/make_config.yaml new file mode 100644 index 0000000000..3fcdea03bc --- /dev/null +++ b/frontend/appflowy_flutter/linux/packaging/rpm/make_config.yaml @@ -0,0 +1,33 @@ +display_name: AppFlowy +icon: linux/packaging/assets/logo.png +group: Applications/Office +vendor: AppFlowy +packager: AppFlowy +packagerEmail: support@appflowy.io +license: APGL-3.0 +url: https://github.com/AppFlowy-IO/appflowy + +build_arch: x86_64 + +keywords: + - AppFlowy + - Office + - Document + - Database + - Note + - Kanban + - Note + +generic_name: AppFlowy + +categories: + - Office + - Productivity + +startup_notify: true + +supportedMimeType: x-scheme-handler/appflowy-flutter + +requires: + - libnotify + - keybinder diff --git a/frontend/appflowy_flutter/macos/Podfile.lock b/frontend/appflowy_flutter/macos/Podfile.lock index d5ac7975b9..e06670c5a5 100644 --- a/frontend/appflowy_flutter/macos/Podfile.lock +++ b/frontend/appflowy_flutter/macos/Podfile.lock @@ -3,6 +3,9 @@ PODS: - FlutterMacOS - appflowy_backend (0.0.1): - FlutterMacOS + - auto_updater_macos (0.0.1): + - FlutterMacOS + - Sparkle - bitsdojo_window_macos (0.0.1): - FlutterMacOS - connectivity_plus (0.0.1): @@ -17,7 +20,7 @@ PODS: - flowy_infra_ui (0.0.1): - FlutterMacOS - FlutterMacOS (1.0.0) - - HotKey (0.2.0) + - HotKey (0.2.1) - hotkey_manager (0.0.1): - FlutterMacOS - HotKey @@ -30,8 +33,8 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - ReachabilitySwift (5.2.3) - - screen_retriever (0.0.1): + - ReachabilitySwift (5.2.4) + - screen_retriever_macos (0.0.1): - FlutterMacOS - Sentry/HybridSDK (8.35.1) - sentry_flutter (8.8.0): @@ -43,19 +46,24 @@ PODS: - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS - - sqflite (0.0.3): + - Sparkle (2.6.4) + - sqflite_darwin (0.0.4): - Flutter - FlutterMacOS - super_native_extensions (0.0.1): - FlutterMacOS - url_launcher_macos (0.0.1): - FlutterMacOS + - webview_flutter_wkwebview (0.0.1): + - Flutter + - FlutterMacOS - window_manager (0.2.0): - FlutterMacOS DEPENDENCIES: - app_links (from `Flutter/ephemeral/.symlinks/plugins/app_links/macos`) - appflowy_backend (from `Flutter/ephemeral/.symlinks/plugins/appflowy_backend/macos`) + - auto_updater_macos (from `Flutter/ephemeral/.symlinks/plugins/auto_updater_macos/macos`) - bitsdojo_window_macos (from `Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos`) - connectivity_plus (from `Flutter/ephemeral/.symlinks/plugins/connectivity_plus/macos`) - desktop_drop (from `Flutter/ephemeral/.symlinks/plugins/desktop_drop/macos`) @@ -68,13 +76,14 @@ DEPENDENCIES: - local_notifier (from `Flutter/ephemeral/.symlinks/plugins/local_notifier/macos`) - package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - - screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`) + - screen_retriever_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos`) - sentry_flutter (from `Flutter/ephemeral/.symlinks/plugins/sentry_flutter/macos`) - share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`) - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) - - sqflite (from `Flutter/ephemeral/.symlinks/plugins/sqflite/darwin`) + - sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`) - super_native_extensions (from `Flutter/ephemeral/.symlinks/plugins/super_native_extensions/macos`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) + - webview_flutter_wkwebview (from `Flutter/ephemeral/.symlinks/plugins/webview_flutter_wkwebview/darwin`) - window_manager (from `Flutter/ephemeral/.symlinks/plugins/window_manager/macos`) SPEC REPOS: @@ -82,12 +91,15 @@ SPEC REPOS: - HotKey - ReachabilitySwift - Sentry + - Sparkle EXTERNAL SOURCES: app_links: :path: Flutter/ephemeral/.symlinks/plugins/app_links/macos appflowy_backend: :path: Flutter/ephemeral/.symlinks/plugins/appflowy_backend/macos + auto_updater_macos: + :path: Flutter/ephemeral/.symlinks/plugins/auto_updater_macos/macos bitsdojo_window_macos: :path: Flutter/ephemeral/.symlinks/plugins/bitsdojo_window_macos/macos connectivity_plus: @@ -112,49 +124,54 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos path_provider_foundation: :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin - screen_retriever: - :path: Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos + screen_retriever_macos: + :path: Flutter/ephemeral/.symlinks/plugins/screen_retriever_macos/macos sentry_flutter: :path: Flutter/ephemeral/.symlinks/plugins/sentry_flutter/macos share_plus: :path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos shared_preferences_foundation: :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin - sqflite: - :path: Flutter/ephemeral/.symlinks/plugins/sqflite/darwin + sqflite_darwin: + :path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin super_native_extensions: :path: Flutter/ephemeral/.symlinks/plugins/super_native_extensions/macos url_launcher_macos: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos + webview_flutter_wkwebview: + :path: Flutter/ephemeral/.symlinks/plugins/webview_flutter_wkwebview/darwin window_manager: :path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos SPEC CHECKSUMS: - app_links: 10e0a0ab602ffaf34d142cd4862f29d34b303b2a - appflowy_backend: 865496343de667fc8c600e04b9fd05234e130cf9 - bitsdojo_window_macos: 44e3b8fe3dd463820e0321f6256c5b1c16bb6a00 - connectivity_plus: 18d3c32514c886e046de60e9c13895109866c747 - desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898 - device_info_plus: ce1b7762849d3ec103d0e0517299f2db7ad60720 - file_selector_macos: 54fdab7caa3ac3fc43c9fac4d7d8d231277f8cf2 - flowy_infra_ui: 03301a39ad118771adbf051a664265c61c507f38 + app_links: 9028728e32c83a0831d9db8cf91c526d16cc5468 + appflowy_backend: 464aeb3e5c6966a41641a2111e5ead72ce2695f7 + auto_updater_macos: 3a42f1a06be6981f1a18be37e6e7bf86aa732118 + bitsdojo_window_macos: 7959fb0ca65a3ccda30095c181ecb856fae48ea9 + connectivity_plus: e74b9f74717d2d99d45751750e266e55912baeb5 + desktop_drop: e0b672a7d84c0a6cbc378595e82cdb15f2970a43 + device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76 + file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31 + flowy_infra_ui: 8760ff42a789de40bf5007a5f176b454722a341e FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - HotKey: e96d8a2ddbf4591131e2bb3f54e69554d90cdca6 - hotkey_manager: c32bf0bfe8f934b7bc17ab4ad5c4c142960b023c - irondash_engine_context: da62996ee25616d2f01bbeb85dc115d813359478 - local_notifier: e9506bc66fc70311e8bc7291fb70f743c081e4ff - package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 - ReachabilitySwift: 7f151ff156cea1481a8411701195ac6a984f4979 - screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38 + HotKey: 400beb7caa29054ea8d864c96f5ba7e5b4852277 + hotkey_manager: b443f35f4d772162937aa73fd8995e579f8ac4e2 + irondash_engine_context: 893c7d96d20ce361d7e996f39d360c4c2f9869ba + local_notifier: ebf072651e35ae5e47280ad52e2707375cb2ae4e + package_info_plus: f0052d280d17aa382b932f399edf32507174e870 + path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564 + ReachabilitySwift: 32793e867593cfc1177f5d16491e3a197d2fccda + screen_retriever_macos: 452e51764a9e1cdb74b3c541238795849f21557f Sentry: 1fe34e9c2cbba1e347623610d26db121dcb569f1 - sentry_flutter: a39c2a2d67d5e5b9cb0b94a4985c76dd5b3fc737 - share_plus: 36537c04ce0c3e3f5bd297ce4318b6d5ee5fd6cf - shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 - sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec - super_native_extensions: 85efee3a7495b46b04befcfc86ed12069264ebf3 - url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399 - window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8 + sentry_flutter: e24b397f9a61fa5bbefd8279c3b2242ca86faa90 + share_plus: 510bf0af1a42cd602274b4629920c9649c52f4cc + shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7 + Sparkle: 5f8960a7a119aa7d45dacc0d5837017170bc5675 + sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 + super_native_extensions: c2795d6d9aedf4a79fae25cb6160b71b50549189 + url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673 + webview_flutter_wkwebview: 44d4dee7d7056d5ad185d25b38404436d56c547c + window_manager: 1d01fa7ac65a6e6f83b965471b1a7fdd3f06166c PODFILE CHECKSUM: 0532f3f001ca3110b8be345d6491fff690e95823 diff --git a/frontend/appflowy_flutter/macos/Runner.xcodeproj/project.pbxproj b/frontend/appflowy_flutter/macos/Runner.xcodeproj/project.pbxproj index 1c3367e430..88c451bdd9 100644 --- a/frontend/appflowy_flutter/macos/Runner.xcodeproj/project.pbxproj +++ b/frontend/appflowy_flutter/macos/Runner.xcodeproj/project.pbxproj @@ -29,6 +29,7 @@ 706E045829F286F600B789F4 /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 706E045729F286EC00B789F4 /* libc++.tbd */; }; D7360C6D6177708F7B2D3C9D /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1CD81A6C7244B2318E0BA2E8 /* Pods_Runner.framework */; }; FB4E0E4F2CC9F3F900C57E87 /* libbz2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = FB4E0E4E2CC9F3E900C57E87 /* libbz2.tbd */; }; + FB54062C2D22665000223D60 /* liblzma.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = FB54062B2D22664200223D60 /* liblzma.tbd */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -77,6 +78,7 @@ 7D41C30A3910C3A40B6085E3 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; FB4E0E4E2CC9F3E900C57E87 /* libbz2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libbz2.tbd; path = usr/lib/libbz2.tbd; sourceTree = SDKROOT; }; + FB54062B2D22664200223D60 /* liblzma.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = liblzma.tbd; path = usr/lib/liblzma.tbd; sourceTree = SDKROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -86,6 +88,7 @@ files = ( FB4E0E4F2CC9F3F900C57E87 /* libbz2.tbd in Frameworks */, 706E045829F286F600B789F4 /* libc++.tbd in Frameworks */, + FB54062C2D22665000223D60 /* liblzma.tbd in Frameworks */, D7360C6D6177708F7B2D3C9D /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -171,6 +174,7 @@ D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( + FB54062B2D22664200223D60 /* liblzma.tbd */, FB4E0E4E2CC9F3E900C57E87 /* libbz2.tbd */, 706E045729F286EC00B789F4 /* libc++.tbd */, 1CD81A6C7244B2318E0BA2E8 /* Pods_Runner.framework */, diff --git a/frontend/appflowy_flutter/macos/Runner/AppDelegate.swift b/frontend/appflowy_flutter/macos/Runner/AppDelegate.swift index cad0330b85..c7872aaec9 100644 --- a/frontend/appflowy_flutter/macos/Runner/AppDelegate.swift +++ b/frontend/appflowy_flutter/macos/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return false @@ -16,4 +16,8 @@ class AppDelegate: FlutterAppDelegate { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/frontend/appflowy_flutter/macos/Runner/Configs/AppInfo.xcconfig b/frontend/appflowy_flutter/macos/Runner/Configs/AppInfo.xcconfig index da469610eb..d2b3d7e9b3 100644 --- a/frontend/appflowy_flutter/macos/Runner/Configs/AppInfo.xcconfig +++ b/frontend/appflowy_flutter/macos/Runner/Configs/AppInfo.xcconfig @@ -11,4 +11,4 @@ PRODUCT_NAME = AppFlowy PRODUCT_BUNDLE_IDENTIFIER = io.appflowy.appflowy // The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2024 AppFlowy.IO. All rights reserved. +PRODUCT_COPYRIGHT = Copyright © 2025 AppFlowy.IO. All rights reserved. diff --git a/frontend/appflowy_flutter/macos/Runner/DebugProfile.entitlements b/frontend/appflowy_flutter/macos/Runner/DebugProfile.entitlements index 71949adefe..4c829c7ab0 100644 --- a/frontend/appflowy_flutter/macos/Runner/DebugProfile.entitlements +++ b/frontend/appflowy_flutter/macos/Runner/DebugProfile.entitlements @@ -1,20 +1,20 @@ - - com.apple.security.app-sandbox - - com.apple.security.files.downloads.read-write - - com.apple.security.files.user-selected.read-write - - com.apple.security.network.client - - com.apple.security.network.server - - com.apple.security.temporary-exception.files.absolute-path.read-write - - / - - - + + com.apple.security.app-sandbox + + com.apple.security.files.downloads.read-write + + com.apple.security.files.user-selected.read-write + + com.apple.security.network.client + + com.apple.security.network.server + + com.apple.security.temporary-exception.files.absolute-path.read-write + + / + + + \ No newline at end of file diff --git a/frontend/appflowy_flutter/macos/Runner/Info.plist b/frontend/appflowy_flutter/macos/Runner/Info.plist index cd07887134..cb3d1127a0 100644 --- a/frontend/appflowy_flutter/macos/Runner/Info.plist +++ b/frontend/appflowy_flutter/macos/Runner/Info.plist @@ -1,57 +1,61 @@ - - LSApplicationCategoryType - public.app-category.productivity - CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIconFile - - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleLocalizations - - en - fr - it - zh - - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - APPL - CFBundleShortVersionString - $(FLUTTER_BUILD_NAME) - CFBundleURLTypes - - - CFBundleURLName - - CFBundleURLSchemes - - appflowy-flutter - - - - CFBundleVersion - $(FLUTTER_BUILD_NUMBER) - LSMinimumSystemVersion - $(MACOSX_DEPLOYMENT_TARGET) - NSAppTransportSecurity - NSAllowsArbitraryLoads - + LSApplicationCategoryType + public.app-category.productivity + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLocalizations + + en + fr + it + zh + + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleURLTypes + + + CFBundleURLName + + CFBundleURLSchemes + + appflowy-flutter + + + + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + SUPublicEDKey + Bs++IOmOwYmNTjMMC2jMqLNldP+mndDp/LwujCg2/kw= + SUAllowsAutomaticUpdates + - NSHumanReadableCopyright - $(PRODUCT_COPYRIGHT) - NSMainNibFile - MainMenu - NSPrincipalClass - NSApplication - - + \ No newline at end of file diff --git a/frontend/appflowy_flutter/macos/Runner/Release.entitlements b/frontend/appflowy_flutter/macos/Runner/Release.entitlements index 71949adefe..4c829c7ab0 100644 --- a/frontend/appflowy_flutter/macos/Runner/Release.entitlements +++ b/frontend/appflowy_flutter/macos/Runner/Release.entitlements @@ -1,20 +1,20 @@ - - com.apple.security.app-sandbox - - com.apple.security.files.downloads.read-write - - com.apple.security.files.user-selected.read-write - - com.apple.security.network.client - - com.apple.security.network.server - - com.apple.security.temporary-exception.files.absolute-path.read-write - - / - - - + + com.apple.security.app-sandbox + + com.apple.security.files.downloads.read-write + + com.apple.security.files.user-selected.read-write + + com.apple.security.network.client + + com.apple.security.network.server + + com.apple.security.temporary-exception.files.absolute-path.read-write + + / + + + \ No newline at end of file diff --git a/frontend/appflowy_flutter/macos/build/ios/XCBuildData/PIFCache/project/PROJECT@v11_mod=a7fbf46937053896f73cc7c7ec6baefb_hash=bfdfe7dc352907fc980b868725387e98plugins=1OJSG6M1FOV3XYQCBH7Z29RZ0FPR9XDE1-json b/frontend/appflowy_flutter/macos/build/ios/XCBuildData/PIFCache/project/PROJECT@v11_mod=a7fbf46937053896f73cc7c7ec6baefb_hash=bfdfe7dc352907fc980b868725387e98plugins=1OJSG6M1FOV3XYQCBH7Z29RZ0FPR9XDE1-json new file mode 100644 index 0000000000..87f89b3bbc --- /dev/null +++ b/frontend/appflowy_flutter/macos/build/ios/XCBuildData/PIFCache/project/PROJECT@v11_mod=a7fbf46937053896f73cc7c7ec6baefb_hash=bfdfe7dc352907fc980b868725387e98plugins=1OJSG6M1FOV3XYQCBH7Z29RZ0FPR9XDE1-json @@ -0,0 +1 @@ +{"appPreferencesBuildSettings":{},"buildConfigurations":[{"buildSettings":{"ALWAYS_SEARCH_USER_PATHS":"NO","CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED":"YES","CLANG_ANALYZER_NONNULL":"YES","CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION":"YES_AGGRESSIVE","CLANG_CXX_LANGUAGE_STANDARD":"gnu++14","CLANG_CXX_LIBRARY":"libc++","CLANG_ENABLE_MODULES":"YES","CLANG_ENABLE_OBJC_ARC":"YES","CLANG_ENABLE_OBJC_WEAK":"YES","CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING":"YES","CLANG_WARN_BOOL_CONVERSION":"YES","CLANG_WARN_COMMA":"YES","CLANG_WARN_CONSTANT_CONVERSION":"YES","CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS":"YES","CLANG_WARN_DIRECT_OBJC_ISA_USAGE":"YES_ERROR","CLANG_WARN_DOCUMENTATION_COMMENTS":"YES","CLANG_WARN_EMPTY_BODY":"YES","CLANG_WARN_ENUM_CONVERSION":"YES","CLANG_WARN_INFINITE_RECURSION":"YES","CLANG_WARN_INT_CONVERSION":"YES","CLANG_WARN_NON_LITERAL_NULL_CONVERSION":"YES","CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF":"YES","CLANG_WARN_OBJC_LITERAL_CONVERSION":"YES","CLANG_WARN_OBJC_ROOT_CLASS":"YES_ERROR","CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER":"YES","CLANG_WARN_RANGE_LOOP_ANALYSIS":"YES","CLANG_WARN_STRICT_PROTOTYPES":"YES","CLANG_WARN_SUSPICIOUS_MOVE":"YES","CLANG_WARN_UNGUARDED_AVAILABILITY":"YES_AGGRESSIVE","CLANG_WARN_UNREACHABLE_CODE":"YES","CLANG_WARN__DUPLICATE_METHOD_MATCH":"YES","COPY_PHASE_STRIP":"NO","DEBUG_INFORMATION_FORMAT":"dwarf","ENABLE_STRICT_OBJC_MSGSEND":"YES","ENABLE_TESTABILITY":"YES","GCC_C_LANGUAGE_STANDARD":"gnu11","GCC_DYNAMIC_NO_PIC":"NO","GCC_NO_COMMON_BLOCKS":"YES","GCC_OPTIMIZATION_LEVEL":"0","GCC_PREPROCESSOR_DEFINITIONS":"POD_CONFIGURATION_DEBUG=1 DEBUG=1 $(inherited)","GCC_WARN_64_TO_32_BIT_CONVERSION":"YES","GCC_WARN_ABOUT_RETURN_TYPE":"YES_ERROR","GCC_WARN_UNDECLARED_SELECTOR":"YES","GCC_WARN_UNINITIALIZED_AUTOS":"YES_AGGRESSIVE","GCC_WARN_UNUSED_FUNCTION":"YES","GCC_WARN_UNUSED_VARIABLE":"YES","IPHONEOS_DEPLOYMENT_TARGET":"12.0","MTL_ENABLE_DEBUG_INFO":"INCLUDE_SOURCE","MTL_FAST_MATH":"YES","ONLY_ACTIVE_ARCH":"YES","PRODUCT_NAME":"$(TARGET_NAME)","STRIP_INSTALLED_PRODUCT":"NO","SWIFT_ACTIVE_COMPILATION_CONDITIONS":"DEBUG","SWIFT_OPTIMIZATION_LEVEL":"-Onone","SWIFT_VERSION":"5.0","SYMROOT":"${SRCROOT}/../build"},"guid":"bfdfe7dc352907fc980b868725387e98814b7e2c3bac55ee99d78eaa8d1ec61e","name":"Debug"},{"buildSettings":{"ALWAYS_SEARCH_USER_PATHS":"NO","CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED":"YES","CLANG_ANALYZER_NONNULL":"YES","CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION":"YES_AGGRESSIVE","CLANG_CXX_LANGUAGE_STANDARD":"gnu++14","CLANG_CXX_LIBRARY":"libc++","CLANG_ENABLE_MODULES":"YES","CLANG_ENABLE_OBJC_ARC":"YES","CLANG_ENABLE_OBJC_WEAK":"YES","CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING":"YES","CLANG_WARN_BOOL_CONVERSION":"YES","CLANG_WARN_COMMA":"YES","CLANG_WARN_CONSTANT_CONVERSION":"YES","CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS":"YES","CLANG_WARN_DIRECT_OBJC_ISA_USAGE":"YES_ERROR","CLANG_WARN_DOCUMENTATION_COMMENTS":"YES","CLANG_WARN_EMPTY_BODY":"YES","CLANG_WARN_ENUM_CONVERSION":"YES","CLANG_WARN_INFINITE_RECURSION":"YES","CLANG_WARN_INT_CONVERSION":"YES","CLANG_WARN_NON_LITERAL_NULL_CONVERSION":"YES","CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF":"YES","CLANG_WARN_OBJC_LITERAL_CONVERSION":"YES","CLANG_WARN_OBJC_ROOT_CLASS":"YES_ERROR","CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER":"YES","CLANG_WARN_RANGE_LOOP_ANALYSIS":"YES","CLANG_WARN_STRICT_PROTOTYPES":"YES","CLANG_WARN_SUSPICIOUS_MOVE":"YES","CLANG_WARN_UNGUARDED_AVAILABILITY":"YES_AGGRESSIVE","CLANG_WARN_UNREACHABLE_CODE":"YES","CLANG_WARN__DUPLICATE_METHOD_MATCH":"YES","COPY_PHASE_STRIP":"NO","DEBUG_INFORMATION_FORMAT":"dwarf-with-dsym","ENABLE_NS_ASSERTIONS":"NO","ENABLE_STRICT_OBJC_MSGSEND":"YES","GCC_C_LANGUAGE_STANDARD":"gnu11","GCC_NO_COMMON_BLOCKS":"YES","GCC_PREPROCESSOR_DEFINITIONS":"POD_CONFIGURATION_PROFILE=1 $(inherited)","GCC_WARN_64_TO_32_BIT_CONVERSION":"YES","GCC_WARN_ABOUT_RETURN_TYPE":"YES_ERROR","GCC_WARN_UNDECLARED_SELECTOR":"YES","GCC_WARN_UNINITIALIZED_AUTOS":"YES_AGGRESSIVE","GCC_WARN_UNUSED_FUNCTION":"YES","GCC_WARN_UNUSED_VARIABLE":"YES","IPHONEOS_DEPLOYMENT_TARGET":"12.0","MTL_ENABLE_DEBUG_INFO":"NO","MTL_FAST_MATH":"YES","PRODUCT_NAME":"$(TARGET_NAME)","STRIP_INSTALLED_PRODUCT":"NO","SWIFT_COMPILATION_MODE":"wholemodule","SWIFT_OPTIMIZATION_LEVEL":"-O","SWIFT_VERSION":"5.0","SYMROOT":"${SRCROOT}/../build"},"guid":"bfdfe7dc352907fc980b868725387e98c22f26ca3341c3062f2313dc737070d4","name":"Profile"},{"buildSettings":{"ALWAYS_SEARCH_USER_PATHS":"NO","CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED":"YES","CLANG_ANALYZER_NONNULL":"YES","CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION":"YES_AGGRESSIVE","CLANG_CXX_LANGUAGE_STANDARD":"gnu++14","CLANG_CXX_LIBRARY":"libc++","CLANG_ENABLE_MODULES":"YES","CLANG_ENABLE_OBJC_ARC":"YES","CLANG_ENABLE_OBJC_WEAK":"YES","CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING":"YES","CLANG_WARN_BOOL_CONVERSION":"YES","CLANG_WARN_COMMA":"YES","CLANG_WARN_CONSTANT_CONVERSION":"YES","CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS":"YES","CLANG_WARN_DIRECT_OBJC_ISA_USAGE":"YES_ERROR","CLANG_WARN_DOCUMENTATION_COMMENTS":"YES","CLANG_WARN_EMPTY_BODY":"YES","CLANG_WARN_ENUM_CONVERSION":"YES","CLANG_WARN_INFINITE_RECURSION":"YES","CLANG_WARN_INT_CONVERSION":"YES","CLANG_WARN_NON_LITERAL_NULL_CONVERSION":"YES","CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF":"YES","CLANG_WARN_OBJC_LITERAL_CONVERSION":"YES","CLANG_WARN_OBJC_ROOT_CLASS":"YES_ERROR","CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER":"YES","CLANG_WARN_RANGE_LOOP_ANALYSIS":"YES","CLANG_WARN_STRICT_PROTOTYPES":"YES","CLANG_WARN_SUSPICIOUS_MOVE":"YES","CLANG_WARN_UNGUARDED_AVAILABILITY":"YES_AGGRESSIVE","CLANG_WARN_UNREACHABLE_CODE":"YES","CLANG_WARN__DUPLICATE_METHOD_MATCH":"YES","COPY_PHASE_STRIP":"NO","DEBUG_INFORMATION_FORMAT":"dwarf-with-dsym","ENABLE_NS_ASSERTIONS":"NO","ENABLE_STRICT_OBJC_MSGSEND":"YES","GCC_C_LANGUAGE_STANDARD":"gnu11","GCC_NO_COMMON_BLOCKS":"YES","GCC_PREPROCESSOR_DEFINITIONS":"POD_CONFIGURATION_RELEASE=1 $(inherited)","GCC_WARN_64_TO_32_BIT_CONVERSION":"YES","GCC_WARN_ABOUT_RETURN_TYPE":"YES_ERROR","GCC_WARN_UNDECLARED_SELECTOR":"YES","GCC_WARN_UNINITIALIZED_AUTOS":"YES_AGGRESSIVE","GCC_WARN_UNUSED_FUNCTION":"YES","GCC_WARN_UNUSED_VARIABLE":"YES","IPHONEOS_DEPLOYMENT_TARGET":"12.0","MTL_ENABLE_DEBUG_INFO":"NO","MTL_FAST_MATH":"YES","PRODUCT_NAME":"$(TARGET_NAME)","STRIP_INSTALLED_PRODUCT":"NO","SWIFT_COMPILATION_MODE":"wholemodule","SWIFT_OPTIMIZATION_LEVEL":"-O","SWIFT_VERSION":"5.0","SYMROOT":"${SRCROOT}/../build"},"guid":"bfdfe7dc352907fc980b868725387e9828903703a9fe9e3707306e58aab67b51","name":"Release"}],"classPrefix":"","defaultConfigurationName":"Release","developmentRegion":"en","groupTree":{"children":[{"fileType":"text.script.ruby","guid":"bfdfe7dc352907fc980b868725387e98d0b25d39b515a574839e998df229c3cb","path":"../Podfile","sourceTree":"SOURCE_ROOT","type":"file"},{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98f986fc29daf4cb723e5ecd0e77c9cc3a","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/app_links-6.3.3/ios/Classes/AppLinksPlugin.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e983499ae993d0615bc4a25c6d23d299cd2","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/app_links-6.3.3/ios/Classes/AppLinksPlugin.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9878bdb70529628b051bfb14170b6ce281","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/app_links-6.3.3/ios/Classes/SwiftAppLinksPlugin.swift","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98803a16064d6b26357b9fa2d9c81eb2c0","name":"Classes","path":"Classes","sourceTree":"","type":"group"},{"children":[{"fileType":"text.xml","guid":"bfdfe7dc352907fc980b868725387e982bcf9ac9f04ccd93893e9ae54d152c11","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/app_links-6.3.3/ios/Resources/PrivacyInfo.xcprivacy","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e980af336cd97bd48a5f76247c45af3ca5a","name":"Resources","path":"Resources","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98d4c688735e4b7c4c9af25f0254256c5a","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e980f47fa79356e5b78e85637df4eab1e8f","name":"app_links","path":"app_links","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9852951db13a823ccb98a790f496fab4e3","name":"plugins","path":"plugins","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e988786e23fb077ded3affc0f7a37fc275c","name":".symlinks","path":".symlinks","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9874ef26f64fe74d7c02d1a94bcde1184c","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e989187ad07f57154eca7d638acb8377adb","name":"appflowy_flutter","path":"appflowy_flutter","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98dc7eee9f7cdf2e623ebc316ac5388a1e","name":"frontend","path":"frontend","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e982c3c6bda68e418bfa0a7d818fbfb0037","name":"AppFlowy","path":"AppFlowy","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98a15f02f386a528bc8eb605ae37642455","name":"samples","path":"samples","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98cbbda7998cd576c276e2258fb3bad5a5","name":"dev","path":"dev","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e982b881f7fdcc02480bc57ddb51bd8d98f","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98aa491e8e0f7d7e2c84ecbe06c2f4df77","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98ae796e67846c10ae19147ab24013260d","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98ee7362a7cb62b913a6f351fa670031fb","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98c70d2ff23f2582de73eade3b63ca6732","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98cfdf5bb4bb8df40c98fd16d64963a3be","name":"..","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/app_links-6.3.3/ios","sourceTree":"","type":"group"},{"children":[{"fileType":"text.script.ruby","guid":"bfdfe7dc352907fc980b868725387e9827cc495d5b0d43a98c370d69de3ff8ae","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/app_links-6.3.3/ios/app_links.podspec","sourceTree":"","type":"file"},{"fileType":"text","guid":"bfdfe7dc352907fc980b868725387e98b1016bc412809827cc7f8ffd7be68d60","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/app_links-6.3.3/LICENSE","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e988f23ea9e443884f6d6a834c279bb42c4","name":"Pod","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e989d5bbbb2531f246d5e384ddd39c9ef94","path":"app_links.modulemap","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e985bbf5bd8a81b79ac8b94165aebec6742","path":"app_links-dummy.m","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e98a0510b51afa43c66fac3cfa1b303b58d","path":"app_links-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e988921fa59229e94f886b598374e0a0a6c","path":"app_links-prefix.pch","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e988a8ca854307b43eb2b99c9daa9a1cc96","path":"app_links-umbrella.h","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e986a31ee17a0bf46ce08a50fbab9d7e0d7","path":"app_links.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e981b9d938e8177ba8be7381344ebbf3f72","path":"app_links.release.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e98faf0d598eef28587701cbceba0a82824","path":"ResourceBundle-app_links_ios_privacy-app_links-Info.plist","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e9834af3cd6a85915e92c7086a5cef194bd","name":"Support Files","path":"../../../../Pods/Target Support Files/app_links","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e985fe1a1ae80c7c29a049e0b0f5a968eb6","name":"app_links","path":"../.symlinks/plugins/app_links/ios","sourceTree":"","type":"group"},{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98c9b1d2b694569060c7a89ce432ae59aa","path":"../../../../../../packages/appflowy_backend/ios/Classes/AppFlowyBackendPlugin.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98d2eb541b9cb3760f4d8a0de1aceac0cd","path":"../../../../../../packages/appflowy_backend/ios/Classes/AppFlowyBackendPlugin.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98ff9d3e6fbf7f3ebdf3b2d98a4d52480e","path":"../../../../../../packages/appflowy_backend/ios/Classes/AppFlowyBackendPlugin.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98082f3bd8728bb955d3b231cc205df22a","path":"../../../../../../packages/appflowy_backend/ios/Classes/binding.h","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e981cf16cdf61f891cb9befc3392b13ff5f","name":"Classes","path":"Classes","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e980ba42de9b682ddbb7d9ecb3a91add63c","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98ce8a30ee373383f69298444d24489306","name":"appflowy_backend","path":"appflowy_backend","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98c86a47e2309e0b005ed79419dd093f0d","name":"plugins","path":"plugins","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98503452eddbee5272ed788d0cc749960e","name":".symlinks","path":".symlinks","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98055cce552f828e105a78ac03f0bdd805","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98ca2c764817f57c4676d84d72558b3899","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98f5501b2ed32cd958d3563e60f001d703","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98b9c0603d95a0886bda3e008ea3e3501a","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9877a3c2776a0dd48b2c60d087afb651a0","name":"..","path":"../../../../../packages/appflowy_backend/ios","sourceTree":"","type":"group"},{"children":[{"fileType":"archive.ar","guid":"bfdfe7dc352907fc980b868725387e9836c8d2e47d81f8a6899c11848d60e876","path":"../../../../../packages/appflowy_backend/ios/libdart_ffi.a","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98d5592f7a99092768eeefc4cfc096cae8","name":"Frameworks","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"text.script.ruby","guid":"bfdfe7dc352907fc980b868725387e98c2aceac4d9f7a88a7f2aef9ddb559c68","path":"../../../../../packages/appflowy_backend/ios/appflowy_backend.podspec","sourceTree":"","type":"file"},{"fileType":"text","guid":"bfdfe7dc352907fc980b868725387e985e17269d373b81baa61bd43ba6a542e5","path":"../../../../../packages/appflowy_backend/LICENSE","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e9850866a0df8df8e8e5c4a2fae74fa1e1f","name":"Pod","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e98e87c56b9467409c44163e34bc882ac5d","path":"appflowy_backend.modulemap","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98e158e9b03f83f13bbdf8668ff066134b","path":"appflowy_backend-dummy.m","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e98eaf420deb60cd2d5a76ad867f2138dae","path":"appflowy_backend-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9805ed6ca977fdc6df3bcf5eed84819eb9","path":"appflowy_backend-prefix.pch","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e983bb1fa0b0f13951c6fa653a914b3fa2c","path":"appflowy_backend-umbrella.h","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e986cd74f83369370fbe7e403ef8053e619","path":"appflowy_backend.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e98403ef0361dbe02708d9acfefd0464e16","path":"appflowy_backend.release.xcconfig","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98a1e14bed5ed1f1fb81e768bd14f444fe","name":"Support Files","path":"../../../../Pods/Target Support Files/appflowy_backend","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9835d1d3e8ff3e04e32db8f29bce41a67d","name":"appflowy_backend","path":"../.symlinks/plugins/appflowy_backend/ios","sourceTree":"","type":"group"},{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98624a11fdf6f56961a285723dfd21440e","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/connectivity_plus-5.0.2/ios/Classes/ConnectivityPlusPlugin.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e985824c2dcc3b51cb92cca4c0ee6b76711","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/connectivity_plus-5.0.2/ios/Classes/ConnectivityPlusPlugin.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e980ce7873e1e41d09d3e0e844e23814078","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/connectivity_plus-5.0.2/ios/Classes/ConnectivityProvider.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98880b04837ff2e7c369e7e0eb127f9146","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/connectivity_plus-5.0.2/ios/Classes/PathMonitorConnectivityProvider.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e986c4b5d0d184e7bdaae9f3f567209b6a8","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/connectivity_plus-5.0.2/ios/Classes/ReachabilityConnectivityProvider.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e982d898374f337072929ea1137cd9b0531","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/connectivity_plus-5.0.2/ios/Classes/SwiftConnectivityPlusPlugin.swift","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98a0e254b7ac4017377847f08d93859c98","name":"Classes","path":"Classes","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98403bc21ffba3f9ff7918b7c9d3ed9571","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98f8845f64df2ea83ab8319d64b1bbadde","name":"connectivity_plus","path":"connectivity_plus","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e989eb115a9dec07b1f45a9c5d5afae3331","name":"plugins","path":"plugins","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9811c71b5edc42403c378790dde71cd61e","name":".symlinks","path":".symlinks","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e988ba6e533cf0750afe861cf587836d63c","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98337f70e0b3205d77252416fe9d53ade5","name":"appflowy_flutter","path":"appflowy_flutter","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98d3ba4a1d262c8736c1a3dd2bbee95feb","name":"frontend","path":"frontend","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98935b95f888c988c238c3ba19998f568c","name":"AppFlowy","path":"AppFlowy","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9815214da764f00e2aace6e3402b97ff27","name":"samples","path":"samples","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98c4748e70a6f1ff450d8ad89293faf1ad","name":"dev","path":"dev","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e987ddf25614c5494e3e2e964d3362a7e13","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98652b1ddb86551418439feb3a3cde66c8","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98413da6f3e8f068df77eefa2354cc1260","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98b18d54474e5f2acd3f7e93b85008ecaa","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98ff9bc5085e73af685f00aee655d65cbd","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98ec4569066db3377ae3957a0893989f6a","name":"..","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/connectivity_plus-5.0.2/ios","sourceTree":"","type":"group"},{"children":[{"fileType":"text.script.ruby","guid":"bfdfe7dc352907fc980b868725387e981911c6f4ab7da1f59fb48cce8d267a76","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/connectivity_plus-5.0.2/ios/connectivity_plus.podspec","sourceTree":"","type":"file"},{"fileType":"text","guid":"bfdfe7dc352907fc980b868725387e982050d0252ea66aa742807a457832653a","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/connectivity_plus-5.0.2/LICENSE","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e986076caee38a9da764b3dfa1e4e1a5d41","name":"Pod","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e986c1079ccb9ad2a0dd836bbde26dfebf0","path":"connectivity_plus.modulemap","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98b6ddb885fad9a4542a6329a470fa2fe4","path":"connectivity_plus-dummy.m","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e98e592f2c2e44ef3deac3cd02784cc784d","path":"connectivity_plus-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98d207fb51488c7ea7ac1275e911a94150","path":"connectivity_plus-prefix.pch","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9898468587a6cab520cdcb8933d0b368ca","path":"connectivity_plus-umbrella.h","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e9818c5888400978b2fa22d952059454061","path":"connectivity_plus.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e98d82fe1b7b4db7bc85ab18441f1e2c0ce","path":"connectivity_plus.release.xcconfig","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e984b570fd876e00e3c1622ee7a13633dbe","name":"Support Files","path":"../../../../Pods/Target Support Files/connectivity_plus","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98a5f07478f75bc3b752964b6b533c114d","name":"connectivity_plus","path":"../.symlinks/plugins/connectivity_plus/ios","sourceTree":"","type":"group"},{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98ba19832f3a1fec7b3b56e3071bf4c9f7","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/device_info_plus-10.1.2/ios/Classes/FPPDeviceInfoPlusPlugin.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98589f97f59a6ebf70206fa946d33c6a98","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/device_info_plus-10.1.2/ios/Classes/FPPDeviceInfoPlusPlugin.m","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e9830307b7eb9685258ae248d413d28a51f","name":"Classes","path":"Classes","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98b1c115a5b698e82de643427263904f6e","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98b72e875a2313b2fd273ee6413267e0b7","name":"device_info_plus","path":"device_info_plus","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98a016968d1537de5fee216f39b9b13a4b","name":"plugins","path":"plugins","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e984163ac08562702d9111e13bc58eadee3","name":".symlinks","path":".symlinks","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9863be3299f9535e1f37edeca566c56956","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98ec71f83543e78b02bb8878f2bdaa1770","name":"appflowy_flutter","path":"appflowy_flutter","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e983b57caee95bb0defd7fbea09e4630e95","name":"frontend","path":"frontend","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9866f39d80aa059eb566807578dd56d3e0","name":"AppFlowy","path":"AppFlowy","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98d5774272732a1c387c19dcb20953a259","name":"samples","path":"samples","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9884b715eaf409b020791c09bd1a45ac40","name":"dev","path":"dev","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e989064cf80f6cbf753a3f2f76769676869","name":"..","path":"..","sourceTree":"","type":"group"},{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"fileType":"text.xml","guid":"bfdfe7dc352907fc980b868725387e98b80404716ec61d84484617356e734544","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/device_info_plus-10.1.2/ios/PrivacyInfo.xcprivacy","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e9853d1bd10368491358c120edf0879a1c1","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98cd41f321f927f1501da3ce4c3e1705d3","name":"device_info_plus","path":"device_info_plus","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98c8a696a7575bdfd695b50552bda53e63","name":"plugins","path":"plugins","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e986fad17d5ebb6a1b1e363579811099dac","name":".symlinks","path":".symlinks","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9896b7c1d7f56d6b335d559dde146c1187","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98b7caa4a8157f551fd5157e38236212ff","name":"appflowy_flutter","path":"appflowy_flutter","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98eb0be9618fdb3c31fc76e6c391bb5a54","name":"frontend","path":"frontend","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98d79e90f30e8071ea0755a235e7bbe47a","name":"AppFlowy","path":"AppFlowy","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98f7e502581e9e563f0e716961fe919778","name":"samples","path":"samples","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98ceb019bbffa37850d4328a3b5e80e961","name":"dev","path":"../dev","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98d1e6ecf8a8f5e7866d4a3bf6a028da56","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9803607fd62a02c7946a1d3477b61e1422","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e984cfdc50cf761e86a346d06e7bbf2d3a1","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98e53458045fdd4d428512566882c92e7e","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98a82b79305f187eb48310a393f4bfe813","name":"..","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/device_info_plus-10.1.2/ios","sourceTree":"","type":"group"},{"children":[{"fileType":"text.script.ruby","guid":"bfdfe7dc352907fc980b868725387e987f003eaf8659e7fff511891b3f2afc0d","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/device_info_plus-10.1.2/ios/device_info_plus.podspec","sourceTree":"","type":"file"},{"fileType":"text","guid":"bfdfe7dc352907fc980b868725387e983862a4e9dba3759d8615b149cba8a0dc","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/device_info_plus-10.1.2/LICENSE","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e982a7f5deb6a64ae5e5a8c21df8be555e2","name":"Pod","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e9800ef6f27822088506da77df111adaf81","path":"device_info_plus.modulemap","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98d3729b6804108439237d0ad0b05ddce2","path":"device_info_plus-dummy.m","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e984decbb29f8ccfc95e475df5348ee3959","path":"device_info_plus-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98051abdaf3129a419e9bb3b8c7a83e64b","path":"device_info_plus-prefix.pch","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98e913211adfbc9d70bfc43b4439f4332c","path":"device_info_plus-umbrella.h","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e984bb4748a26339d307020f1110f05e895","path":"device_info_plus.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e981f4d52f92d8b0f360ee8bef71882f85a","path":"device_info_plus.release.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e98f354426d7c8f682caf04a755240d2fde","path":"ResourceBundle-device_info_plus_privacy-device_info_plus-Info.plist","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98623cfe9c4e66d55a221902e7792e5d43","name":"Support Files","path":"../../../../Pods/Target Support Files/device_info_plus","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98ba853655f8a54c79510e0b66012fac89","name":"device_info_plus","path":"../.symlinks/plugins/device_info_plus/ios","sourceTree":"","type":"group"},{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98e3b3f804d927f2bf6183213a182625c0","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/file_picker-8.1.4/ios/Classes/FileInfo.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98e16f08255892d0f684af6ee4b4380824","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/file_picker-8.1.4/ios/Classes/FileInfo.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98f56ed42bf21b1a52ac8fcb9a82336a65","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/file_picker-8.1.4/ios/Classes/FilePickerPlugin.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e988e93662f755da0279387c09ed20fcb67","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/file_picker-8.1.4/ios/Classes/FilePickerPlugin.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e982542f63e48192694c2301b53dd8da392","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/file_picker-8.1.4/ios/Classes/FileUtils.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e987347d2eccffbba5b92a6737ac7c7f11f","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/file_picker-8.1.4/ios/Classes/FileUtils.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e984b1a288da1e6fe981b46f8315944e21d","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/file_picker-8.1.4/ios/Classes/ImageUtils.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98e04840a4cc59ee085ade0b92a852884e","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/file_picker-8.1.4/ios/Classes/ImageUtils.m","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98ed38e9ec1e8cd7f7427a8e3752fa5961","name":"Classes","path":"Classes","sourceTree":"","type":"group"},{"children":[{"fileType":"text.xml","guid":"bfdfe7dc352907fc980b868725387e9830eb81718ca8a4637d18322144cb97c2","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/file_picker-8.1.4/ios/Resources/PrivacyInfo.xcprivacy","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e980cdda343b1e3945e28451531e9c1a605","name":"Resources","path":"Resources","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9845180a5dc5686acdc4e9429fa972713e","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9836c9feed71b513f9919701e702f5ef43","name":"file_picker","path":"file_picker","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98e5fd1b4108172e277e33c8a87d3fcd70","name":"plugins","path":"plugins","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9880d01b963c8ef608a0ad04583a5fc312","name":".symlinks","path":".symlinks","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9805619a0f02043e6fdb7a2be76189cfff","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9849b67c3d88226e7c72c75fa15e3bbdc0","name":"appflowy_flutter","path":"appflowy_flutter","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98e1d9d867fa533872b45a7339a773d9b3","name":"frontend","path":"frontend","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98b0187227de80dc084fabcf302004870b","name":"AppFlowy","path":"AppFlowy","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9825d0e9fb8adf9bdf05bf7db5c5fc456d","name":"samples","path":"samples","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9800d40be9558dad2dacf5f93047196827","name":"dev","path":"dev","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98abd6b198ade79f4872ee96e030a87e36","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9862b34fd8ad582ae69aae1fce3d396520","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98ffed80c94d5fea9d76f4fe35d4478984","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9852117d814a38fe3eba010475bafd632a","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98e72e01936d9a14e0e9b869c88ad45424","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9875e49cbfeae37a120af486d9eb2d93c5","name":"..","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/file_picker-8.1.4/ios","sourceTree":"","type":"group"},{"children":[{"fileType":"text.script.ruby","guid":"bfdfe7dc352907fc980b868725387e98e67d477f82d32d5c7c9d30d2e81d066f","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/file_picker-8.1.4/ios/file_picker.podspec","sourceTree":"","type":"file"},{"fileType":"text","guid":"bfdfe7dc352907fc980b868725387e9808d1f81b0d4082e0417db2f18a2acfb6","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/file_picker-8.1.4/LICENSE","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98cbd123e02025168eb151b7d71e556d22","name":"Pod","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e98b0953817803e455c620122efd668eb5f","path":"file_picker.modulemap","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9837a1507700518fa7f49a60aac4b7767c","path":"file_picker-dummy.m","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e98d8e8a876a8ca0ce915464fbc27689c44","path":"file_picker-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98273092b724e2281270954671470d86c2","path":"file_picker-prefix.pch","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98330b1e95388e708f5ad38ed7ce90e28c","path":"file_picker-umbrella.h","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e98893e38e9593d6a06c8522268bf23390a","path":"file_picker.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e981a76198b195c337f9587084c94a43437","path":"file_picker.release.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e982406cc43be2014b06d264ea164e97c61","path":"ResourceBundle-file_picker_ios_privacy-file_picker-Info.plist","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98a98dabe6e24ef65809527fcab892dd86","name":"Support Files","path":"../../../../Pods/Target Support Files/file_picker","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98f62c63ccec2525926d94e2db6b738061","name":"file_picker","path":"../.symlinks/plugins/file_picker/ios","sourceTree":"","type":"group"},{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e982640e5da9d4da0b1c1284d679e8bfff4","path":"../../../../../../packages/flowy_infra_ui/ios/Classes/FlowyInfraUIPlugin.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e982c61e60ddf03cc04542492a7725f11f3","path":"../../../../../../packages/flowy_infra_ui/ios/Classes/FlowyInfraUIPlugin.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98a2f3125bf6a20381c479cb67f97aa968","path":"../../../../../../packages/flowy_infra_ui/ios/Classes/SwiftFlowyInfraUIPlugin.swift","sourceTree":"","type":"file"},{"children":[{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e984ba1576a81594fe8f17aea9519f8b1fd","path":"../../../../../../../packages/flowy_infra_ui/ios/Classes/Event/KeyboardEventHandler.swift","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98d5424bf8b4f526e22486e2a27f3268a5","name":"Event","path":"Event","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e988d8845aeb0c1ed01cfe07a5f22cc0ec9","name":"Classes","path":"Classes","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9803f83e7a1d900c14ec7e6905f0092826","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98bf611f0f52fc18d7226f7e7f09c38245","name":"flowy_infra_ui","path":"flowy_infra_ui","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e981fb46775118dd702b7316a553c95e90e","name":"plugins","path":"plugins","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98616f2715911b1a74d97ddaada4344fbc","name":".symlinks","path":".symlinks","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e984cd1c794c9b9ad19f2e4f0ca3f026819","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98b37b705e2925a02d2062aaafe5099089","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e982add91273b39db2a9cf969fe86dca06a","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e983b3dd8aad5aabbc2496c663812f03cfd","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98d656d9598f5d14273b8b8981109e8b9e","name":"..","path":"../../../../../packages/flowy_infra_ui/ios","sourceTree":"","type":"group"},{"children":[{"fileType":"text.script.ruby","guid":"bfdfe7dc352907fc980b868725387e989d6db7e7b482c3241d9a1acce4813ee8","path":"../../../../../packages/flowy_infra_ui/ios/flowy_infra_ui.podspec","sourceTree":"","type":"file"},{"fileType":"text","guid":"bfdfe7dc352907fc980b868725387e989fd87faba1a0f13d4d7494191f208760","path":"../../../../../packages/flowy_infra_ui/LICENSE","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98145bf30753fca245e5d38777385d9388","name":"Pod","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e9893b48c5d4d9ce2656928a0ee3ff7944f","path":"flowy_infra_ui.modulemap","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e985398a052a839596d179b48db347a52c5","path":"flowy_infra_ui-dummy.m","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e98c1829b249c76d5ac78b6563b1ee7fde3","path":"flowy_infra_ui-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e985b5e2f476270884826aa113914031988","path":"flowy_infra_ui-prefix.pch","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e983e584fb8f2aede8db256844ee817b410","path":"flowy_infra_ui-umbrella.h","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e98892689e861aa6009551af6801e83c338","path":"flowy_infra_ui.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e98f6af4fb94ad21206a16cbaf1e6453010","path":"flowy_infra_ui.release.xcconfig","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e982cc4e6d82b8278ce278988c26691765e","name":"Support Files","path":"../../../../Pods/Target Support Files/flowy_infra_ui","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98f43591bd9ec58ad67ffe4453fafc4a1a","name":"flowy_infra_ui","path":"../.symlinks/plugins/flowy_infra_ui/ios","sourceTree":"","type":"group"},{"children":[{"children":[{"fileType":"text.script.ruby","guid":"bfdfe7dc352907fc980b868725387e98fb18cc16183813fcd8641d3cadfad33b","path":"Flutter.podspec","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98d37e3d81bea52e1b4801db4886715c89","name":"Pod","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e9812cdcc535f32a8a76cc3d8e883d2013f","path":"Flutter.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e98048531a74a78f7613c93ba91f15c7ef8","path":"Flutter.release.xcconfig","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98152d9dfbdddbef1340635c08e31152c6","name":"Support Files","path":"../Pods/Target Support Files/Flutter","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e988b26d3d01bc6aaf3315f6d51c851186d","name":"Flutter","path":"../Flutter","sourceTree":"","type":"group"},{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98b785d8b1f51c643229e4fdd18985825e","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/fluttertoast-8.2.10/ios/Classes/FluttertoastPlugin.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9877147b007839a2216fd57593e0f3ba5f","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/fluttertoast-8.2.10/ios/Classes/FluttertoastPlugin.m","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e987efa3ca5a1d26929ed3b109a26806afa","name":"Classes","path":"Classes","sourceTree":"","type":"group"},{"children":[{"fileType":"text.xml","guid":"bfdfe7dc352907fc980b868725387e98792f001acf47168aaffa10afd959b5c4","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/fluttertoast-8.2.10/ios/Resources/PrivacyInfo.xcprivacy","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e9812c06060cb56dec48c82c3b93bd2fdf2","name":"Resources","path":"Resources","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e989a85c4c34db915b09efc04ad74e71906","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98879342f1ae53be318ccc851c8fb5f741","name":"fluttertoast","path":"fluttertoast","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e986e74552a0b437b7d398a0e712dfea078","name":"plugins","path":"plugins","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98cd6a60c3b14da76ce2370d2a8be6b094","name":".symlinks","path":".symlinks","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98e29b7bd0bc91b1bd5e1a2b480473e1d7","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e984dd71cdc0b7c0e396503163601d414ef","name":"appflowy_flutter","path":"appflowy_flutter","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9892ba1e9027f479ab7d1c9354ba3a2aee","name":"frontend","path":"frontend","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98d4cad64b7e33d505b2ecf5d631c9d3e7","name":"AppFlowy","path":"AppFlowy","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e987932e250d513fa720967e0dd955cb5c2","name":"samples","path":"samples","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9869aa178a8c06888c2f7f224a2918a492","name":"dev","path":"dev","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98316da1c1d27426250efb3febcdf6e95a","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e989a6847ce3a370c3b3d860bf1b58a643e","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9898b42eaa404b9ed45fdaf7f308bef4aa","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98b9d4812f1ec13b817bb1728f24b63ef3","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9850c5079f1f3a336ba56135604b1311cd","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e980ef2416841219f34ad24eb0617d29faa","name":"..","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/fluttertoast-8.2.10/ios","sourceTree":"","type":"group"},{"children":[{"fileType":"text.script.ruby","guid":"bfdfe7dc352907fc980b868725387e982ae0f7d0422d2f75f6cdde13de9e63ab","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/fluttertoast-8.2.10/ios/fluttertoast.podspec","sourceTree":"","type":"file"},{"fileType":"text","guid":"bfdfe7dc352907fc980b868725387e983d3e3470809b19b23bd82e0a81446281","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/fluttertoast-8.2.10/LICENSE","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98d1afec4a601b8594f84cb4f864f71af4","name":"Pod","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e983d17b3300e70f7d2c18f92e0d276131b","path":"fluttertoast.modulemap","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e981f77f9386b6ff408a5830dc5ec967554","path":"fluttertoast-dummy.m","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e98f000b535e938ba4dfcadc2d08b7f9dcb","path":"fluttertoast-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e980af04a50d29871944a910af21b08eb54","path":"fluttertoast-prefix.pch","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98d7bc0cf610c976df935aac7605febe19","path":"fluttertoast-umbrella.h","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e984e7d38849f0179ae896bfe33295f4bcb","path":"fluttertoast.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e98bfeefd631cf811ef5ce08bdcc7b5e371","path":"fluttertoast.release.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e98740356b6d05058396c4af19281498c88","path":"ResourceBundle-fluttertoast_privacy-fluttertoast-Info.plist","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98781d0486e386be35e6ed9b7fe0e393a9","name":"Support Files","path":"../../../../Pods/Target Support Files/fluttertoast","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e988aa84318e78c044eca9d3adcb2083544","name":"fluttertoast","path":"../.symlinks/plugins/fluttertoast/ios","sourceTree":"","type":"group"},{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"fileType":"text.xml","guid":"bfdfe7dc352907fc980b868725387e98d61d652dffc4f8948dc5e51433b55422","path":"../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/image_picker_ios-0.8.12+2/ios/image_picker_ios/Sources/image_picker_ios/Resources/PrivacyInfo.xcprivacy","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e984a37fbaf4a9886ee0471de51a32ce11c","name":"Resources","path":"Resources","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98593629a4be85977669ea4c059a1d10a3","name":"image_picker_ios","path":"image_picker_ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98f1b4186d564806fb6d451c19a178c28c","name":"Sources","path":"Sources","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98d5d0ee78f468d5980cfa1ea4ad6dc829","name":"image_picker_ios","path":"image_picker_ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e985041391a222e0b4847d8b2a02427be61","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9875530726d790ea59a3b2315529b92cc3","name":"image_picker_ios","path":"image_picker_ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e989ea0a989d0c2a93f9b189cf1469cea87","name":"plugins","path":"plugins","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98391dbed2b1e45fe5718c27685e1d7c67","name":".symlinks","path":".symlinks","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e986721fa987b7c82532939c240860d8345","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98fb63c7bf2a24aed477a61c0970de0960","name":"appflowy_flutter","path":"appflowy_flutter","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98b2bbb2e48ebc4b126f757c2703266204","name":"frontend","path":"frontend","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9817ce3811d81e530356a343c80efe8ad4","name":"AppFlowy","path":"AppFlowy","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9849f3c66c3da79f5b15d867daabb3187b","name":"samples","path":"samples","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98d3fa3916bdb222eafea4d7820d6eb9e8","name":"dev","path":"dev","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98cd7d949543e498a3b27db6a1893718cd","name":"..","path":".","sourceTree":"","type":"group"},{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98e7820fbe6a5f106704f492a88059c536","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/image_picker_ios-0.8.12+2/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerImageUtil.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e982369589aa7940b6e167faf588a3802a0","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/image_picker_ios-0.8.12+2/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerMetaDataUtil.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e983f29585ff52e44e910a275777a9f20c5","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/image_picker_ios-0.8.12+2/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerPhotoAssetUtil.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9859a809cba5c8a38df54a2381d8b2365c","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/image_picker_ios-0.8.12+2/ios/image_picker_ios/Sources/image_picker_ios/FLTImagePickerPlugin.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98de28739697251ea419e62df80ffef732","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/image_picker_ios-0.8.12+2/ios/image_picker_ios/Sources/image_picker_ios/FLTPHPickerSaveImageToPathOperation.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98919c321c361ef3f5f9d9f7385fcfcbc8","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/image_picker_ios-0.8.12+2/ios/image_picker_ios/Sources/image_picker_ios/messages.g.m","sourceTree":"","type":"file"},{"children":[{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9850445506038bd7f616867a4a123a3ac1","path":"../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/image_picker_ios-0.8.12+2/ios/image_picker_ios/Sources/image_picker_ios/include/image_picker_ios-umbrella.h","sourceTree":"","type":"file"},{"children":[{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9883e47689f14ae5fdf581d0fab5f920e0","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/image_picker_ios-0.8.12+2/ios/image_picker_ios/Sources/image_picker_ios/include/image_picker_ios/FLTImagePickerImageUtil.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9831e620cfa600d0005b2000f1d54a69f4","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/image_picker_ios-0.8.12+2/ios/image_picker_ios/Sources/image_picker_ios/include/image_picker_ios/FLTImagePickerMetaDataUtil.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e987f45fcb71f8e934254d2b14ac634b539","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/image_picker_ios-0.8.12+2/ios/image_picker_ios/Sources/image_picker_ios/include/image_picker_ios/FLTImagePickerPhotoAssetUtil.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98fe75cfd0b755ea439e9f1406676fbfcb","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/image_picker_ios-0.8.12+2/ios/image_picker_ios/Sources/image_picker_ios/include/image_picker_ios/FLTImagePickerPlugin.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98d0c46c12ac4b3b637f4201b418d7f26f","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/image_picker_ios-0.8.12+2/ios/image_picker_ios/Sources/image_picker_ios/include/image_picker_ios/FLTImagePickerPlugin_Test.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9808b4daa14bfdb0a669f7e77d74ae3f17","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/image_picker_ios-0.8.12+2/ios/image_picker_ios/Sources/image_picker_ios/include/image_picker_ios/FLTPHPickerSaveImageToPathOperation.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e985d17117714f9b0c4ea80392bb9bccf16","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/image_picker_ios-0.8.12+2/ios/image_picker_ios/Sources/image_picker_ios/include/image_picker_ios/messages.g.h","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e982e942c8a9d3701363338a82b251071ac","name":"image_picker_ios","path":"image_picker_ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98502dd1df3bee9584d607bb1b1a902b26","name":"include","path":"include","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e982056603c1983c7c0b15b4f9d4e839e1e","name":"image_picker_ios","path":"image_picker_ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9892285f318b670b27f7fbf2287b291843","name":"Sources","path":"Sources","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e986a27670c0d2f4e5b9f45ade587b13d2c","name":"image_picker_ios","path":"image_picker_ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e987b8f4a03e01f25f540c609ad50dc4076","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98d97a722bba91079df06c440102468bba","name":"image_picker_ios","path":"image_picker_ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98af8e2dbb1e8185946e7becdd47b97f9e","name":"plugins","path":"plugins","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e989ae881df58521cd150fc286fffe35603","name":".symlinks","path":".symlinks","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9803a91fc4ede7e5b0c2afbd5fab18fee9","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e981a39b88b7383525d9d473bd93d1e2f0b","name":"appflowy_flutter","path":"appflowy_flutter","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9811f7fa0b42b0c8fb654345ec4ebb8e66","name":"frontend","path":"frontend","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98623219eb0a2c578df94d5883bc419325","name":"AppFlowy","path":"AppFlowy","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e981125f09590385ffd9ea5a9fe5aa60ba5","name":"samples","path":"samples","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98d31ae9e961eabf22319ec6a1fca4f113","name":"dev","path":"dev","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9817eb7c23f31f300a1c251c4305fda8c1","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98ec140a00b7550fc8d6fbc0aaceaa7a89","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98bfbf2e646d2c92073ff2e83bebd9af2d","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98f2692e38d39bd674e71f6bb22b891e4a","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9829706d777976177d0bf097f898b5b58f","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e985a01f741d7f1ce665f3e325d6c6a57f8","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98646c57f09cb01b0c762a0d93ca3f7b78","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98221a992b68c7cffb1a8eb5cbfbb297e9","name":"..","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/image_picker_ios-0.8.12+2/ios/image_picker_ios/Sources","sourceTree":"","type":"group"},{"children":[{"fileType":"text.script.ruby","guid":"bfdfe7dc352907fc980b868725387e9853e18fbd58f9c37e8a3e2681a7b69feb","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/image_picker_ios-0.8.12+2/ios/image_picker_ios.podspec","sourceTree":"","type":"file"},{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e9804dde312afcdb12eedb33fc8bd45d59c","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/image_picker_ios-0.8.12+2/ios/image_picker_ios/Sources/image_picker_ios/include/ImagePickerPlugin.modulemap","sourceTree":"","type":"file"},{"fileType":"text","guid":"bfdfe7dc352907fc980b868725387e988990923df59cdef8e5b1e19216f2a97a","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/image_picker_ios-0.8.12+2/LICENSE","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98aa0c16b2297382848b598f9592b0c3f0","name":"Pod","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e98a37a01b5033e0b3fe7a1dbb5d490ef35","path":"image_picker_ios.modulemap","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98fd29ed6fd89520fddcfbd7124ff888d8","path":"image_picker_ios-dummy.m","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e983d86dee1d916fb29b63d5b1bfcc7b5f9","path":"image_picker_ios-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9803f06581ec423d44280cf7868eea5e93","path":"image_picker_ios-prefix.pch","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e9862b8fc1ccbc32fefecbae61cf5888907","path":"image_picker_ios.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e985530ede90a4a8c2c7507499317499697","path":"image_picker_ios.release.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e98bd2861c7f94ca0a221c93e59dabe2757","path":"ResourceBundle-image_picker_ios_privacy-image_picker_ios-Info.plist","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e9859b1bb772d76fea02e84d0016d099c3a","name":"Support Files","path":"../../../../Pods/Target Support Files/image_picker_ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9830cadcc7a1263e98e67a0cf722e5d0ab","name":"image_picker_ios","path":"../.symlinks/plugins/image_picker_ios/ios","sourceTree":"","type":"group"},{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e987800ae9687eb588b086e319ea6177659","path":"../../../../../../../../../../../../../fvm/versions/3.27.4/packages/integration_test/ios/integration_test/Sources/integration_test/FLTIntegrationTestRunner.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98edd6a3512846802c34d22c5afb07300b","path":"../../../../../../../../../../../../../fvm/versions/3.27.4/packages/integration_test/ios/integration_test/Sources/integration_test/IntegrationTestIosTest.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e980fa8dc3bbeb73b3d252e726a70557124","path":"../../../../../../../../../../../../../fvm/versions/3.27.4/packages/integration_test/ios/integration_test/Sources/integration_test/IntegrationTestPlugin.m","sourceTree":"","type":"file"},{"children":[{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e980db9a591d712346350116e799f0e85b9","path":"../../../../../../../../../../../../../../fvm/versions/3.27.4/packages/integration_test/ios/integration_test/Sources/integration_test/include/FLTIntegrationTestRunner.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e984a0d38d682beee02329dbf3bf514cce1","path":"../../../../../../../../../../../../../../fvm/versions/3.27.4/packages/integration_test/ios/integration_test/Sources/integration_test/include/IntegrationTestIosTest.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98ae12dfa62d0f539c9a3638bb6061375d","path":"../../../../../../../../../../../../../../fvm/versions/3.27.4/packages/integration_test/ios/integration_test/Sources/integration_test/include/IntegrationTestPlugin.h","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e9823e5a3226da96ed3914b1c8fcf559df9","name":"include","path":"include","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98bde8b1c5794795e3b29ba817afae8d21","name":"integration_test","path":"integration_test","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98b7f37a3e9ff7a757003a42461c25c365","name":"Sources","path":"Sources","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98ede6ae88c67694e327de9c425a7f6132","name":"integration_test","path":"integration_test","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e987f156cd8efcf8b450a0acccba337affe","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e989dcf9316567af671febf7c01cbe9f506","name":"integration_test","path":"integration_test","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e987cf80e4fed23235d2406a799ca33eeca","name":"plugins","path":"plugins","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9847a104d2bb397b64bd133d45453210d2","name":".symlinks","path":".symlinks","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e989392c96daffc1a0e321f346049c20e7e","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e980176812f8f0fbbedf15c13f3c2ee1f26","name":"appflowy_flutter","path":"appflowy_flutter","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e985aa0f0e528439e82d21295d31624797f","name":"frontend","path":"frontend","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98db8efb64140c28021c5072d8734e4699","name":"AppFlowy","path":"AppFlowy","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e986ba3dab5252afe70f25f2838a01bdd4b","name":"samples","path":"samples","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98b67e71ad7e85fc1ac5853ac3428427de","name":"dev","path":"dev","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e985f23f974cdcdf9412df80483d5680940","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9873283974ea9ea8d983cfb681aeeee604","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98db229454a7e5041b4830de422e60e499","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e989a2ced9ad917530dc3e50756255a1e21","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e986b9379d3571941dbae7d47d880a7a296","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e985055bbeb70e29da915730a29ed24173f","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e988070745f3c2f414eef05f58b72da6f5c","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9863c8c730e09674801ad705a3ac5f0bb5","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e989899d04f24c9ff95f1314d4fabeea094","name":"..","path":"../../../../../../../../../../fvm/versions/3.27.4/packages/integration_test/ios/integration_test/Sources","sourceTree":"","type":"group"},{"children":[{"fileType":"text.script.ruby","guid":"bfdfe7dc352907fc980b868725387e980c15437a062ac65671b286006afd1f2d","path":"../../../../../../../../../../fvm/versions/3.27.4/packages/integration_test/ios/integration_test.podspec","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98d94ca862be564f152946859a75277d34","name":"Pod","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e98201d61b0d848a49c65589ceb7506e08d","path":"integration_test.modulemap","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98af0a4e5755a22cb43543a81c374a2d51","path":"integration_test-dummy.m","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e98a8522fb10db2dfa210c208f905afd9f3","path":"integration_test-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98c0b24b129604eddd217c08a7d0c062db","path":"integration_test-prefix.pch","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9896f6fa393191b60c6487f245272c94e3","path":"integration_test-umbrella.h","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e98105bcf1dafac8dc8d85eaff566c35643","path":"integration_test.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e986a9f0cec960a86a1eea7dbdb4c49fcc4","path":"integration_test.release.xcconfig","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e981a27bfd3655d560ebee82c3ab7c104f3","name":"Support Files","path":"../../../../Pods/Target Support Files/integration_test","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98051dc09e6cf1b3ea30ba9f39d11035d5","name":"integration_test","path":"../.symlinks/plugins/integration_test/ios","sourceTree":"","type":"group"},{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98a8ee44916529381498405522e751f8b0","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/irondash_engine_context-0.5.4/ios/Classes/EngineContextPlugin.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e984c462c42948edabe677ebac748da5d96","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/irondash_engine_context-0.5.4/ios/Classes/EngineContextPlugin.m","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98fc9b7f6e025c71f553fe6444717223ed","name":"Classes","path":"Classes","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98db377227d8253ffe98324018634bc2ba","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e986f2a88ed98a92034ce863ada1219ab5f","name":"irondash_engine_context","path":"irondash_engine_context","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98da61f0e74006e8fbd784bf4a3b970e5f","name":"plugins","path":"plugins","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98635b7210c5b62e20e037994cad1bf111","name":".symlinks","path":".symlinks","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98c64150b3d0970cb99fece561f5a8799c","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98d3fdbea6d8cb166625df79788fcad443","name":"appflowy_flutter","path":"appflowy_flutter","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98cbeb45be878891b685575f6b25a8528b","name":"frontend","path":"frontend","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9847ce44e75f1621ef4e0a6a5ae85f248d","name":"AppFlowy","path":"AppFlowy","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98b2ae8d52765d0e5d9bea0d19cd7aebc9","name":"samples","path":"samples","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98c84ed3eae59cea51b25d355ea7faec4c","name":"dev","path":"dev","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9899ac6782e67161e5aecf52fb2c668a4c","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98710388c0277b6c02dc2bfce47465f2f8","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98c5e6f44e93118427f696de099ce587e1","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98c97afc961fd21222514666e42cbbfd8f","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e984e9cef67c7f53259a28a5a877e4a2131","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e985b651d4fcaab066c39a7ebe7cff5daa9","name":"..","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/irondash_engine_context-0.5.4/ios","sourceTree":"","type":"group"},{"children":[{"fileType":"text.script.ruby","guid":"bfdfe7dc352907fc980b868725387e98b58b99914223d1b29b5e344e157a1987","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/irondash_engine_context-0.5.4/ios/irondash_engine_context.podspec","sourceTree":"","type":"file"},{"fileType":"text","guid":"bfdfe7dc352907fc980b868725387e9830fe1bed9eaa6250033f52ceaadf7d59","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/irondash_engine_context-0.5.4/LICENSE","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e986fa867be18f4a0a3be7766d74146209f","name":"Pod","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e98425d4148b72c406e4fd6672ecc362707","path":"irondash_engine_context.modulemap","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98d84855c0549724120cd6c3172b83cfa2","path":"irondash_engine_context-dummy.m","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e98de048098637f376810dd3bb08c83895c","path":"irondash_engine_context-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98b78febae09cf0c8f6fc7540345bee7a3","path":"irondash_engine_context-prefix.pch","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9899f4dc54067ed61c6ad87cabdfa23817","path":"irondash_engine_context-umbrella.h","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e9859e866190126bed1c816e3db8cf733a3","path":"irondash_engine_context.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e9837cfa75eec5f21f87364f26642dcde07","path":"irondash_engine_context.release.xcconfig","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e9832c8cd0ed43920be52e135a0de0ef859","name":"Support Files","path":"../../../../Pods/Target Support Files/irondash_engine_context","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98abf194361c2fa0e4ef4dbec1a81fd4cf","name":"irondash_engine_context","path":"../.symlinks/plugins/irondash_engine_context/ios","sourceTree":"","type":"group"},{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e987c73d9371bc96dd532ba087fb5fe818f","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/keyboard_height_plugin-0.1.5/ios/Classes/KeyboardHeightPlugin.swift","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98bc635b84f7af43f9a693a5e68a4759e0","name":"Classes","path":"Classes","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98f0eadcb14d473ce9c1a7c484bf311b2e","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98cf80142a4ef1966c0c5c5b502225cf5a","name":"keyboard_height_plugin","path":"keyboard_height_plugin","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e989d420609dd708f812028e4f49f0e9d67","name":"plugins","path":"plugins","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9895cace1167e8f8a1209977ad38dfccfb","name":".symlinks","path":".symlinks","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98422a86793790ea0d327a52c60169ac60","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9830a1c8fc44b202d4661674a7fad9873c","name":"appflowy_flutter","path":"appflowy_flutter","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98e85d16fef38a2779c13639b43f1e3726","name":"frontend","path":"frontend","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9842be4aab321e08d3762331c112720d13","name":"AppFlowy","path":"AppFlowy","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98d8f5a70339a345d74b94ca7b959328bb","name":"samples","path":"samples","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e986d3eca9fadfaabd2c455a69807986068","name":"dev","path":"dev","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98d98f50f9676e73dee28b8bb61da9f8ff","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9807172992c96c01947c7f63e7875b1880","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e982737307bd1600ca8956d8f22af636541","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98854bc6907e9bd1273078ea0de43aa17e","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98d53a1d0636736242ed1151c333b5fc9f","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e980c1dd450aea5e1194ba1fff003416312","name":"..","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/keyboard_height_plugin-0.1.5/ios","sourceTree":"","type":"group"},{"children":[{"fileType":"text.script.ruby","guid":"bfdfe7dc352907fc980b868725387e980bfe355de52abf2bb6aedb754200dccc","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/keyboard_height_plugin-0.1.5/ios/keyboard_height_plugin.podspec","sourceTree":"","type":"file"},{"fileType":"text","guid":"bfdfe7dc352907fc980b868725387e985cbc6607d34630da0312e81033a0eac3","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/keyboard_height_plugin-0.1.5/LICENSE","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98fe47c46f6c2454354c85a993a081845d","name":"Pod","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e98619ee239792a4f0a0de0a4a0b0a159ed","path":"keyboard_height_plugin.modulemap","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98a419ee90c48f680450b1d3ead4ab82d1","path":"keyboard_height_plugin-dummy.m","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e98b22dd52696b4067993e38f2551b63980","path":"keyboard_height_plugin-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98ac24e13278278e052df0627e6f2cfe76","path":"keyboard_height_plugin-prefix.pch","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e988d785c658ed39de0a4c38be45dfbc1bb","path":"keyboard_height_plugin-umbrella.h","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e98b6b8fa71d08d5539059bc6a5868a679d","path":"keyboard_height_plugin.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e985de3091a8d3281d32c2891e218876b88","path":"keyboard_height_plugin.release.xcconfig","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98b6f518b56ca945c4e48ea2f80f2dde0e","name":"Support Files","path":"../../../../Pods/Target Support Files/keyboard_height_plugin","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98de11b38cb40d0a466e962277af5c13cc","name":"keyboard_height_plugin","path":"../.symlinks/plugins/keyboard_height_plugin/ios","sourceTree":"","type":"group"},{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98fa9794d4853209dd9b9dc8e446307a60","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/open_filex-4.6.0/ios/Classes/OpenFilePlugin.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98fda7caf052d83850aa64439013c284eb","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/open_filex-4.6.0/ios/Classes/OpenFilePlugin.m","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e987c5f0895df45e85ab2470a8073a4c8f0","name":"Classes","path":"Classes","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98edc66a76af5791867252ee9a4fb21910","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9895e2c6870f605df8c86b47a953028581","name":"open_filex","path":"open_filex","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e980fa51cd5f979e99f137ab48d9cfe538c","name":"plugins","path":"plugins","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e989c915eac28a038451586120b5e42a4dd","name":".symlinks","path":".symlinks","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9844dffd7af5aba7efc4912e98f2c99fa8","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e982963e1dbef544728f2fbcde398f7ce6b","name":"appflowy_flutter","path":"appflowy_flutter","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9820c60640726eda7bd6b2af35bd76df61","name":"frontend","path":"frontend","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98bb93350f484f56f7d755b99459ba03a1","name":"AppFlowy","path":"AppFlowy","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98e5b26b9b30e5f21dd0e61b63275d1293","name":"samples","path":"samples","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e986605830136cc6fec7889ae13c84cbe94","name":"dev","path":"dev","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e986b903bd75146f71f5101abe9a847888e","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9835c6d6f45f08bbdd25355fae03109fc9","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e989b22dd54825d4836f9aeb5366f4d1942","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98fc1763d383faa8bc59b0d7f9aeef3f0a","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98bc4e9c0a74a0677415efe8c07067e305","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9898e553f7a823f6dc70a807a2f2d230a6","name":"..","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/open_filex-4.6.0/ios","sourceTree":"","type":"group"},{"children":[{"fileType":"text","guid":"bfdfe7dc352907fc980b868725387e98ee0863552468cb4b187a007dc6d9a9b2","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/open_filex-4.6.0/LICENSE","sourceTree":"","type":"file"},{"fileType":"text.script.ruby","guid":"bfdfe7dc352907fc980b868725387e9852adb17f9ae796569014283b63994f43","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/open_filex-4.6.0/ios/open_filex.podspec","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e9881839ee4b95080a8084a269cdb03d9cf","name":"Pod","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e985fad692e094e1d0680167d0e9b810fb5","path":"open_filex.modulemap","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9840ba9a41d5060da79af9271d16ff75b4","path":"open_filex-dummy.m","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e98481c5fe7d76a4040e0ad917154c7e4d5","path":"open_filex-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e983eb20923999fb0272a7d7981812ac419","path":"open_filex-prefix.pch","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98045ac1ed0e248a91c7e040ef0eb573ac","path":"open_filex-umbrella.h","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e98d756e4a333f24f739d0261675ee1a4ba","path":"open_filex.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e989220364d0a7adb8651f9b9241f5c7291","path":"open_filex.release.xcconfig","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e984048ca38c41c417f8623767606347b2e","name":"Support Files","path":"../../../../Pods/Target Support Files/open_filex","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98d744b5aadcc662d9087e0ff8ecfa7db1","name":"open_filex","path":"../.symlinks/plugins/open_filex/ios","sourceTree":"","type":"group"},{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e981d03ed1c16bd8f4f97657768978ceefc","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/package_info_plus-8.1.3/ios/package_info_plus/Sources/package_info_plus/FPPPackageInfoPlusPlugin.m","sourceTree":"","type":"file"},{"fileType":"text.xml","guid":"bfdfe7dc352907fc980b868725387e989c46f3ac5eacaf84d06c89a1618d018a","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/package_info_plus-8.1.3/ios/package_info_plus/Sources/package_info_plus/PrivacyInfo.xcprivacy","sourceTree":"","type":"file"},{"children":[{"children":[{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98b8b9ba080e191e0b180722bf92e5f6d0","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/package_info_plus-8.1.3/ios/package_info_plus/Sources/package_info_plus/include/package_info_plus/FPPPackageInfoPlusPlugin.h","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98434c664522ec8a949437da93121dc4ea","name":"package_info_plus","path":"package_info_plus","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98da4508512de7ed1cf62ba23f6e0a1782","name":"include","path":"include","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e987977607c1ffbf525bd13f89c1d4d9af6","name":"package_info_plus","path":"package_info_plus","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98a38ddce212e7e062f995ed0c81943891","name":"Sources","path":"Sources","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98931f5d8098ad1aa7c93eec26372ba236","name":"package_info_plus","path":"package_info_plus","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98285660b922695a353a0d34c11f224681","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9850cc5da0429ea6a6d2b70a2656fcab30","name":"package_info_plus","path":"package_info_plus","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e987ddc93fc8ade4d1b45b41d4e43f4567c","name":"plugins","path":"plugins","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98265c9ab7615abe783c697b6cd4393241","name":".symlinks","path":".symlinks","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98d03678f78b9690ff2b539e1f76337f73","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e980ad2375b9e2190a33ef70bfb739550e5","name":"appflowy_flutter","path":"appflowy_flutter","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98b7ffa0b49f80dc8ef09e80c7b9ea10d9","name":"frontend","path":"frontend","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9870b15a53dd8bece6cba98fe4fe76a795","name":"AppFlowy","path":"AppFlowy","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98c4af5d8efdfb0599410662c8d5fbef98","name":"samples","path":"samples","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9875f8bf110ce0646b5fb64007eacf9418","name":"dev","path":"dev","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98c89cf11d4dd01c776076b9aba66f6de9","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9856bac1d729bbf1ed2a72d1315cebd361","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9891ba0e8cfe4277c3831c309656922ca3","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e980531ebbd14b9b1db9cb595aee441e728","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e989afbfb7501eabf645dfdd3b2d5371318","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e987d5f78597ba1b1ca57b94d699cac9587","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98e0fd6aabdc57252bbd2b8424bc907ff7","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9890c7045574c1805ae1374327a927bd10","name":"..","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/package_info_plus-8.1.3/ios/package_info_plus/Sources","sourceTree":"","type":"group"},{"children":[{"fileType":"text","guid":"bfdfe7dc352907fc980b868725387e987939da84758bc78ea493a94d1cea8578","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/package_info_plus-8.1.3/LICENSE","sourceTree":"","type":"file"},{"fileType":"text.script.ruby","guid":"bfdfe7dc352907fc980b868725387e981e6125d7e62187e31adc58e8a2ab9ea5","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/package_info_plus-8.1.3/ios/package_info_plus.podspec","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98a53ea27f341ed23389e7d2e47e722d47","name":"Pod","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e989676689969e25b7e2b922fdcc545264e","path":"package_info_plus.modulemap","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e985313e6a90d7b731193c46689110da675","path":"package_info_plus-dummy.m","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e98ecaad6d841c680a103b7c06250152c10","path":"package_info_plus-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e987e968239394f15ed507902e17a5a5d3b","path":"package_info_plus-prefix.pch","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e983b443c6a2f8b2d550ac2cdd9baa30e9c","path":"package_info_plus-umbrella.h","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e9833b0ea6cf67df0b17b6edee85d07e536","path":"package_info_plus.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e98fe3643fe82f3241c56cd3e148f9bfcfb","path":"package_info_plus.release.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e98e9bc7cd75e23035e788a0e7c25bf5266","path":"ResourceBundle-package_info_plus_privacy-package_info_plus-Info.plist","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98597a17b9183cd7dd4bf1dc3afc5c61ea","name":"Support Files","path":"../../../../Pods/Target Support Files/package_info_plus","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98aacab2d91d3b0af7ec103ba0eb959971","name":"package_info_plus","path":"../.symlinks/plugins/package_info_plus/ios","sourceTree":"","type":"group"},{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"fileType":"text.xml","guid":"bfdfe7dc352907fc980b868725387e98fa855961429fcf8f989cbc521f984b6b","path":"../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.1/darwin/path_provider_foundation/Sources/path_provider_foundation/Resources/PrivacyInfo.xcprivacy","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98b4c976140e1e1950199f10d209e74f5b","name":"Resources","path":"Resources","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98eacafab16506b203843ef40c684430b1","name":"path_provider_foundation","path":"path_provider_foundation","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98afc251afea8ca3c7c50e9df03700de10","name":"Sources","path":"Sources","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98e09b0557ec981a89a5d3cdcc6842f0c7","name":"path_provider_foundation","path":"path_provider_foundation","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98848154feecc038860b30933f28fcd571","name":"darwin","path":"darwin","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98e4c4edafb8828d8853578114f39e78ab","name":"path_provider_foundation","path":"path_provider_foundation","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e988219086c27550c56b47393a9c0e39e4c","name":"plugins","path":"plugins","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98e704029916b2a9af5f095363d987f0fe","name":".symlinks","path":".symlinks","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98357eba85aa36d189cd46406d27f5211c","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e987a6b9a2268f0d1806de5375f49c7ad34","name":"appflowy_flutter","path":"appflowy_flutter","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98f47342e61ef54b290a2f053ba08d9dca","name":"frontend","path":"frontend","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98d0b8c28e93e04c0badf7323d031eae30","name":"AppFlowy","path":"AppFlowy","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9837afaa61f9ce1c7f4fd458c2cf5adf98","name":"samples","path":"samples","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98ca9305ea66f0600f4817de50cde8db74","name":"dev","path":"dev","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9808e63fd1bbc402ab215a50d1d16dcd32","name":"..","path":".","sourceTree":"","type":"group"},{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e989546965c4112992540058bf89b75515b","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.1/darwin/path_provider_foundation/Sources/path_provider_foundation/messages.g.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e985086f0886ccfd52bf38850cbdf060b2f","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.1/darwin/path_provider_foundation/Sources/path_provider_foundation/PathProviderPlugin.swift","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98f3577bef4fc38afcab726db64a001531","name":"path_provider_foundation","path":"path_provider_foundation","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e982b0a750596bd71fbc8cfcf1a9c72ca42","name":"Sources","path":"Sources","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98c1402de7d04e960dca8c1c9a29277190","name":"path_provider_foundation","path":"path_provider_foundation","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98b0db58f72b87ea1465b9bbf9fa254cd1","name":"darwin","path":"darwin","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98d11257d51b57c6c8f8403335be4b346e","name":"path_provider_foundation","path":"path_provider_foundation","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98b2fe29e812c42cd3759a2d5cda315ac9","name":"plugins","path":"plugins","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9866a118eb431e0fc65900b0d1ed264fc9","name":".symlinks","path":".symlinks","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98c5612f9fa3a0fea2a9cb1e98a04ed834","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e987ba29a7b05ab240400ab764ea3778d63","name":"appflowy_flutter","path":"appflowy_flutter","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98e6d60acafabcaba2c92afe3f89dccc00","name":"frontend","path":"frontend","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98957bcf5769cd6c1ddb7b1bacb4045c62","name":"AppFlowy","path":"AppFlowy","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9857a303219d5ef02905623f5642388e38","name":"samples","path":"samples","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98d0517ff6d03f19c2b5d8f5ae77998f57","name":"dev","path":"dev","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98c6054740313f5e60a651b62a3a9fddff","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98bb43e5bcfafff3075b881ff2a9506820","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98952ef0fb2ed4fabd37dfc72628c7e3ba","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98c173f0cea5f6ab214f5630fe12869f9b","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98b3e481ae0756598ee53e01ba3ade5dfc","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9815a65875b9784344b15a908a00046200","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9827c3714a1d7d43b116f6f2b348cb54b5","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98600a8661b70b3bcc185abb7a4e9008f4","name":"..","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.1/darwin/path_provider_foundation/Sources","sourceTree":"","type":"group"},{"children":[{"fileType":"text","guid":"bfdfe7dc352907fc980b868725387e98a8b04a5824c42b6f4f609567865faee6","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.1/LICENSE","sourceTree":"","type":"file"},{"fileType":"text.script.ruby","guid":"bfdfe7dc352907fc980b868725387e981bdde8f32acfbdc05bfd1e4b808852a6","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/path_provider_foundation-2.4.1/darwin/path_provider_foundation.podspec","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98d9cf578a3f3bd7e82b6d5e62241a3287","name":"Pod","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e9861842fd411d053b57aced9948ca56491","path":"path_provider_foundation.modulemap","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98cc5a067b653131ca99fde3ba1debf062","path":"path_provider_foundation-dummy.m","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e989dd8857730e4c3db203f2e8d33299b7f","path":"path_provider_foundation-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e980ba8eca3e056e4a5b65fa675f2756611","path":"path_provider_foundation-prefix.pch","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98a687ca6ea8584ff3e8089580a0210540","path":"path_provider_foundation-umbrella.h","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e989a3ec1c60f383374577efaaaba58a590","path":"path_provider_foundation.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e98c312fad1a73dfd52c70c16e3d25e37e6","path":"path_provider_foundation.release.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e98e9d12e6cfc445534a2c16d64aa703cc3","path":"ResourceBundle-path_provider_foundation_privacy-path_provider_foundation-Info.plist","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98f3324a2357137a370c65dc36aa758feb","name":"Support Files","path":"../../../../Pods/Target Support Files/path_provider_foundation","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e986a2b2d2d35d31671cf8d3a967a7db258","name":"path_provider_foundation","path":"../.symlinks/plugins/path_provider_foundation/darwin","sourceTree":"","type":"group"},{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98e318c8a8d2c9dc308e66d48a374d44d9","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/PermissionHandlerEnums.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98d3c436acf3f4f4e33e39114153074d49","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/PermissionHandlerPlugin.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e987887890697cf58754f65bdc3473cc551","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/PermissionHandlerPlugin.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98a74eea3ef4e2a60a3951ba0e47037853","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/PermissionManager.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98f80ba72d7ab8de1bbe80c04c71d79ce6","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/PermissionManager.m","sourceTree":"","type":"file"},{"children":[{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e988f16b65cffacfc53fcc954ce586fc713","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/AppTrackingTransparencyPermissionStrategy.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98c853197e3241e98fb93603a21ed56089","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/AppTrackingTransparencyPermissionStrategy.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98ce0ef02ade9d0a1304458c836d16e93f","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/AssistantPermissionStrategy.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98688a017d26dc611c7c96d1741b382970","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/AssistantPermissionStrategy.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e985e535aca2400f5f683c5e24da33fa945","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/AudioVideoPermissionStrategy.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9822f423a57f83424ebc6bfefdee8b3991","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/AudioVideoPermissionStrategy.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98393b75ed0ceeda793874088622fd76fd","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/BackgroundRefreshStrategy.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98df055a47a4e10ed4075cbf80ce6ec767","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/BackgroundRefreshStrategy.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98df5b697e834c60412ac60dfaf5b92739","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/BluetoothPermissionStrategy.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98b97a3c6c28463a93a507d1b992be86ea","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/BluetoothPermissionStrategy.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e981d3465f51e1a6151315cb6853ef86eb1","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/ContactPermissionStrategy.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98b85c4c9dd82c2d504573d0304be09cbe","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/ContactPermissionStrategy.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98f6cc08933266f6af7683b3abe5dc3ace","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/CriticalAlertsPermissionStrategy.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98e1614978d545aec846c24669228cfa1c","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/CriticalAlertsPermissionStrategy.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e988634e5ad5b489cfb8621854418e2b38e","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/EventPermissionStrategy.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e980b79f1643454a89c62ad82f48b83100d","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/EventPermissionStrategy.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98874ffa55cffa9b07dd0dc545368418e8","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/LocationPermissionStrategy.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98d0f2795f5c06163cd56c66c1973fa5e6","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/LocationPermissionStrategy.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9820710d8f569064b9b4711ed685cbb429","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/MediaLibraryPermissionStrategy.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98922dbc3046dcd8b009793d4a2382c211","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/MediaLibraryPermissionStrategy.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e985b7826319f99ae1623d37b8cf2f4eba5","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/NotificationPermissionStrategy.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9805e45e592399c0af6ed4e7318ba5d2c2","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/NotificationPermissionStrategy.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98a0d58fb5a1923e677273beb359f58ab9","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/PermissionStrategy.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98b1b544d0a39e91ce76da89768ba82cb7","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/PhonePermissionStrategy.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e985eb1cc6a74764d8f7285312afe97653b","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/PhonePermissionStrategy.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e984e680efc7f3195ff924eec54756ed68e","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/PhotoPermissionStrategy.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98b6c7b999fdb1f6499108be37ae374b63","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/PhotoPermissionStrategy.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e987f8fe14bbd3ac1b56df04f30ce3befc7","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/SensorPermissionStrategy.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98af1cc35431393d9bafe812d1459bbdf1","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/SensorPermissionStrategy.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e985c3d41a4498df42a0c6816c971e66b90","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/SpeechPermissionStrategy.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98f22ec1e8227f9be641e5842788c56cf3","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/SpeechPermissionStrategy.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e986e866dd7a325c4b0ac10a86655c960f4","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/StoragePermissionStrategy.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e988cf99d004f1e5c25193675ec100372ac","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/StoragePermissionStrategy.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9812e94ec5545f5483160eaed2e319fd2c","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/UnknownPermissionStrategy.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e984d86125607541fcdd295dcf2c90de152","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/strategies/UnknownPermissionStrategy.m","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98bb6db6682fe6132381afffdd0b60c949","name":"strategies","path":"strategies","sourceTree":"","type":"group"},{"children":[{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e983970b54b6bc58e9c785411c2b48da98a","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/util/Codec.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9852af6ca8a2b5eb428a1585731297f789","path":"../../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Classes/util/Codec.m","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98d5b3b31059ef8de5598d516260c77291","name":"util","path":"util","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98b21a21f20e31c9dffd8a301b181e7499","name":"Classes","path":"Classes","sourceTree":"","type":"group"},{"children":[{"fileType":"text.xml","guid":"bfdfe7dc352907fc980b868725387e98adf4c31a2e50275af41b8a579edc68e7","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/Resources/PrivacyInfo.xcprivacy","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e989ac6ab6d3c4d5bfd6ee635e5bfaf86a2","name":"Resources","path":"Resources","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e986adbf3486d93b28d124272c05d31c535","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9873343d816dbbcd1b184b6d68e52b8f8b","name":"permission_handler_apple","path":"permission_handler_apple","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98e29a61315e2ef4299defb08c834d8658","name":"plugins","path":"plugins","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9813dc0e9e99378f2e9402361650d4f3c8","name":".symlinks","path":".symlinks","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98e01ef4d5c3bed4f78810b1761f0bfd2b","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e982268db3e1a4139e97ecd2061e336e5bb","name":"appflowy_flutter","path":"appflowy_flutter","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e985a0dfbbd565a647af8ec44544f29a21d","name":"frontend","path":"frontend","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98656f043d890499eb834029f3c937d260","name":"AppFlowy","path":"AppFlowy","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98de17afe181ca6321b521909130e6f662","name":"samples","path":"samples","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e986adad874bc346fed5ccaed27e453b741","name":"dev","path":"dev","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e980ff2305908cc93c4a0725655e88bdc78","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9857ae62fcfbdc453c8d905748f5c3feee","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98a7884dba25b1f761bcc93d0154f12971","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9828b593dc8b9a43bd8236b7d7a05a125d","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9840d397c5e3d46385c36e81d47ddb4e1d","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98b5d7000c1c54eb9b83d1cdaabf7aa111","name":"..","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios","sourceTree":"","type":"group"},{"children":[{"fileType":"text","guid":"bfdfe7dc352907fc980b868725387e98eedb8bb38abe4a753d3e5a4a50166ce0","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/LICENSE","sourceTree":"","type":"file"},{"fileType":"text.script.ruby","guid":"bfdfe7dc352907fc980b868725387e98bf01ad6eb9325fb5f924a7a939b9c3d5","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/permission_handler_apple-9.4.5/ios/permission_handler_apple.podspec","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e981b1b5aec77e91493469b21f5697a5678","name":"Pod","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e9895519dd776dc6b6b6a0d5a99939759a6","path":"permission_handler_apple.modulemap","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98d3090640601ef537b4effff19f6956f9","path":"permission_handler_apple-dummy.m","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e98693e4bbf87f0173ce83ad9dad9c4ec64","path":"permission_handler_apple-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98db4011adc2801eb0447df5d29a88d434","path":"permission_handler_apple-prefix.pch","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9872ca1772cd655bc190d399786a965f06","path":"permission_handler_apple-umbrella.h","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e98f0f251835fe2b85ba8e20304af40d5bf","path":"permission_handler_apple.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e98498a3b2ced2fb3f55d9af8975a87d769","path":"permission_handler_apple.release.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e986ea3ec8648859708ff7049b01e77d7e6","path":"ResourceBundle-permission_handler_apple_privacy-permission_handler_apple-Info.plist","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e981e5c39acd14afb0de574df88226e3506","name":"Support Files","path":"../../../../Pods/Target Support Files/permission_handler_apple","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98373a889c847b4c599507bdad83234fe7","name":"permission_handler_apple","path":"../.symlinks/plugins/permission_handler_apple/ios","sourceTree":"","type":"group"},{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9881b60c802c0c2009406bdc039b7f0068","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/saver_gallery-4.0.1/ios/Classes/SaverGalleryPlugin.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98dcbc4e0c213bbd743d9703fa7e437dfc","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/saver_gallery-4.0.1/ios/Classes/SaverGalleryPlugin.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98f7bbe1f859cede2224d42b729d4c355e","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/saver_gallery-4.0.1/ios/Classes/SwiftSaverGalleryPlugin.swift","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98d5b49ba1d17a7857dc59f1175110cc13","name":"Classes","path":"Classes","sourceTree":"","type":"group"},{"children":[{"fileType":"text.xml","guid":"bfdfe7dc352907fc980b868725387e98c89b00fc416882d2d0d7c9b090fa7c9c","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/saver_gallery-4.0.1/ios/Resources/PrivacyInfo.xcprivacy","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e9820ce2023299a06e421e4a78afc6b0b82","name":"Resources","path":"Resources","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9865ece6bf5cc7c6cce51ccbe39d149b83","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98541499aef6d248a8301980d2821058d4","name":"saver_gallery","path":"saver_gallery","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9822ec8e7cdcda0404b791382d92111f01","name":"plugins","path":"plugins","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9864d2f6413ca98b754c7ee8a5171ad1fa","name":".symlinks","path":".symlinks","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98230a7213b882ddae22c49a7606136e55","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e985321a2bb5473af9a5a2bc5bc4f834a04","name":"appflowy_flutter","path":"appflowy_flutter","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98dc31a6400e820a903a3adf3e0472b97d","name":"frontend","path":"frontend","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e986b76ca3af3e88ee3e30fcf7e21bc6d0e","name":"AppFlowy","path":"AppFlowy","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e988541bb9a4c72550c30a45563b09a2e33","name":"samples","path":"samples","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98e4d469fa76bd962f8bbbec14965af39f","name":"dev","path":"dev","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e988ab99b3f5ad06e51078cf9e780a1d69a","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98362dc58617351a4e100aac833e0a9775","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e980dde8a02284f890277e1c2802444a83b","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9863783b60069d614fac80a368d82f00e3","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e989d4a0878fd359b487c9cea0fa7016714","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e980cda4027ee69c106bb442ab08ef4d82b","name":"..","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/saver_gallery-4.0.1/ios","sourceTree":"","type":"group"},{"children":[{"fileType":"text","guid":"bfdfe7dc352907fc980b868725387e98187e4b7080377e470ceccd8012e8f22b","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/saver_gallery-4.0.1/LICENSE","sourceTree":"","type":"file"},{"fileType":"text.script.ruby","guid":"bfdfe7dc352907fc980b868725387e98f1b23c2ea102d15d2d3364c334ab1150","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/saver_gallery-4.0.1/ios/saver_gallery.podspec","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98f1988bfa026ce78ad365f9598fb48ccf","name":"Pod","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e980dce72be9e98a0e7938fea67d5dec3f0","path":"ResourceBundle-saver_gallery-saver_gallery-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e98f8441040a6b86dbd9daae56dbd15f227","path":"saver_gallery.modulemap","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98bc00fdad72f5f3771416bb7da4f843ac","path":"saver_gallery-dummy.m","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e98b3812fe968f19a56dff3b7b21717a930","path":"saver_gallery-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9851858f7e87dff1cd2d14bcfec05bf9e7","path":"saver_gallery-prefix.pch","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98fdb913e186672d6dd019c490a01906ce","path":"saver_gallery-umbrella.h","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e986375a6a5aae83e2825b2fa84412a7b38","path":"saver_gallery.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e986cfaff9f48d79d9aac38b1a4200ab7b7","path":"saver_gallery.release.xcconfig","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e9870bd2de048ce790265ba57145fada872","name":"Support Files","path":"../../../../Pods/Target Support Files/saver_gallery","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98e9f8053722083a22097cca53b68ac915","name":"saver_gallery","path":"../.symlinks/plugins/saver_gallery/ios","sourceTree":"","type":"group"},{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98988b8f30525a0740a56a45f26ec9e5ee","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/sentry_flutter-8.8.0/ios/Classes/SentryFlutter.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98b3f62b7cd428533f32ccbe502d30579a","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/sentry_flutter-8.8.0/ios/Classes/SentryFlutterPlugin.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9826cd8809b1744007cf2cdc49b9ee183e","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/sentry_flutter-8.8.0/ios/Classes/SentryFlutterPlugin.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9814197e8a8c3142283755e74bb1e1a542","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/sentry_flutter-8.8.0/ios/Classes/SentryFlutterPluginApple.swift","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98ed1bfbd13db96f1a5a9a509f62b23475","name":"Classes","path":"Classes","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e988a2e2c7191f3342f69288dc5b6f24eac","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e986b57f5aa6fe19896cfd444c1d7e3374b","name":"sentry_flutter","path":"sentry_flutter","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98e95123df781f7246754488867eecad12","name":"plugins","path":"plugins","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e980c8dbba9160b89547d42fa282bc4d833","name":".symlinks","path":".symlinks","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98408185297f1419b37849ee4ef61dd14a","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98db140395c21d264a6950ef09ad273630","name":"appflowy_flutter","path":"appflowy_flutter","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98a317c3cc4f246a4d86dee21027be9f4b","name":"frontend","path":"frontend","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e982c4e624eda92287d712d01e09bae857c","name":"AppFlowy","path":"AppFlowy","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e988c938458c24c5a8a3de86b13a240257d","name":"samples","path":"samples","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9816b3e0cb5684faafbde3cd04104a8bf1","name":"dev","path":"dev","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e982a1a4fb826ee2604d98a1124cda9655e","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e984814fee408387d1cb1ac9abf04007801","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98646b3421a4027d57df2335874d741e7a","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98d7d7eb47d8b13b56642d25993bad53b7","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9834206fc222aad06497489cefdfcadd01","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98472caaca9c22e04c9586a3fde6ae90f3","name":"..","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/sentry_flutter-8.8.0/ios","sourceTree":"","type":"group"},{"children":[{"fileType":"text","guid":"bfdfe7dc352907fc980b868725387e988942381687e6a83dfb7f7b51b7114eae","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/sentry_flutter-8.8.0/LICENSE","sourceTree":"","type":"file"},{"fileType":"text.script.ruby","guid":"bfdfe7dc352907fc980b868725387e98d7a35162a09b1bafe73fd4acf4dcd74e","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/sentry_flutter-8.8.0/ios/sentry_flutter.podspec","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98c631af1f252c1a6185a574de8e401916","name":"Pod","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e98b2b1e766516fc1973912c9ed7d38fba5","path":"sentry_flutter.modulemap","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98b20710f0afc9a65fd11ac053ab5224c4","path":"sentry_flutter-dummy.m","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e98fb88983eae4d4fb5041df897b5eb48b7","path":"sentry_flutter-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98140ec5a195fd3cd2e6672a247b3c50dc","path":"sentry_flutter-prefix.pch","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e980060be8e96d53611f0f35fc6b03e1144","path":"sentry_flutter-umbrella.h","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e98898ca6b789d6ebda968b7bf2c32c2927","path":"sentry_flutter.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e98eb2509e05e4701c0e7caf39cb9f1a7ad","path":"sentry_flutter.release.xcconfig","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98f6c519cd219bbe493b512e88f6691f63","name":"Support Files","path":"../../../../Pods/Target Support Files/sentry_flutter","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9890b2588f7d4f7005fbfcb636532e8266","name":"sentry_flutter","path":"../.symlinks/plugins/sentry_flutter/ios","sourceTree":"","type":"group"},{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98fd63b0ee8c393f403cd165102963d89a","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/share_plus-10.1.4/ios/share_plus/Sources/share_plus/FPPSharePlusPlugin.m","sourceTree":"","type":"file"},{"fileType":"text.xml","guid":"bfdfe7dc352907fc980b868725387e988387a65c6b9b6cb60327ffbe6c59158a","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/share_plus-10.1.4/ios/share_plus/Sources/share_plus/PrivacyInfo.xcprivacy","sourceTree":"","type":"file"},{"children":[{"children":[{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98cb436b045699de5c33251ecb6224be07","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/share_plus-10.1.4/ios/share_plus/Sources/share_plus/include/share_plus/FPPSharePlusPlugin.h","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e988aa5f3177cec22ad24112deb2bd5df88","name":"share_plus","path":"share_plus","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e989b5ccde2486e01b9f408c52039e15189","name":"include","path":"include","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98fb60a3fc698011e0520a294091444220","name":"share_plus","path":"share_plus","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98c0adb9f898335bb561ebd6a92c8b5764","name":"Sources","path":"Sources","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e987c61ae0ab682495e830ebfec4bb2d992","name":"share_plus","path":"share_plus","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98fec752fcf63dc774ef6492dfb4088824","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98061408887ffc6320474b1b3b9e56e869","name":"share_plus","path":"share_plus","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e981ca0473827b2355cd2af25d5d7d10627","name":"plugins","path":"plugins","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e984fc5d20b622f4b201b585186291a8f67","name":".symlinks","path":".symlinks","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98c1ceacb5959c41905f6d4367a79a62d4","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e988d04b2310b73a2f1377c4bab4917fbb0","name":"appflowy_flutter","path":"appflowy_flutter","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9858dedb5230d6424239e49823b8eaf146","name":"frontend","path":"frontend","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98a939846af6f55181341850124da10438","name":"AppFlowy","path":"AppFlowy","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e987236a4c37717175b816032b6e16c35db","name":"samples","path":"samples","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98eefdcc530f2e92f8f7f62696466d831b","name":"dev","path":"dev","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98e999dc81b190f4772b868a75138aa75e","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e989a1108d6d840a33f47b9c0fc3af3275d","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e988dae09aab63bdbe088b9c18b957886b4","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98cdcb3d9a575e64374dc1a1db82409a54","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e988352975c34e4d7ee8941ab48a3068611","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9864da257bade8dccae972a95edd8d4097","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e981dc40745a0624f3b853ce39f5bd094cd","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98253b8bf66bd8ccf4c977852ef0a7714b","name":"..","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/share_plus-10.1.4/ios/share_plus/Sources","sourceTree":"","type":"group"},{"children":[{"fileType":"text","guid":"bfdfe7dc352907fc980b868725387e981e6f67f07605eeaa639fc9e31c57db8a","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/share_plus-10.1.4/LICENSE","sourceTree":"","type":"file"},{"fileType":"text.script.ruby","guid":"bfdfe7dc352907fc980b868725387e98851d3db594f8658c9786f56478fbb6a5","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/share_plus-10.1.4/ios/share_plus.podspec","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98e42d81eea266b46708846d237e6b9514","name":"Pod","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e986288ba151af560df873b930937f73b07","path":"ResourceBundle-share_plus_privacy-share_plus-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e98f12cd395a2605f620e5facab18ef7617","path":"share_plus.modulemap","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e982db5430b3897df3342086c41d0ab0cc2","path":"share_plus-dummy.m","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e98875a85a28fcc15b9fc0d81e86a96d256","path":"share_plus-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98e15cc77379f3a5d2d644bb59a33c227a","path":"share_plus-prefix.pch","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98f3d67ef42ee4a797c3b450c5f684db3a","path":"share_plus-umbrella.h","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e9874f3142bf7e3eb1d60d86ef7b8277153","path":"share_plus.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e986e07cf0736987a294d8f475ef4545d77","path":"share_plus.release.xcconfig","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98a569546e20b60ea744093ab0ba2e1905","name":"Support Files","path":"../../../../Pods/Target Support Files/share_plus","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e988982bbfb1ebde9b684d127cb40ed59ae","name":"share_plus","path":"../.symlinks/plugins/share_plus/ios","sourceTree":"","type":"group"},{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"fileType":"text.xml","guid":"bfdfe7dc352907fc980b868725387e98bbefd940fe64ae1bdba547246e327226","path":"../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.4/darwin/shared_preferences_foundation/Sources/shared_preferences_foundation/Resources/PrivacyInfo.xcprivacy","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e980424689f89b333dcef6b831656c09796","name":"Resources","path":"Resources","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98d76e5d546741e7c1bff558bc1604fa29","name":"shared_preferences_foundation","path":"shared_preferences_foundation","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9833fd9e8a667e58c3eab9ad18ac331952","name":"Sources","path":"Sources","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98a2f6b7afaf2afa6bd09f4e0f94068d4d","name":"shared_preferences_foundation","path":"shared_preferences_foundation","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98fb40156ec424853fa943291f5b303d0f","name":"darwin","path":"darwin","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98b1e3c36a262a1ed5ad3b185a0227eb67","name":"shared_preferences_foundation","path":"shared_preferences_foundation","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9831ab42991c30549603d3d8ca536224cf","name":"plugins","path":"plugins","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98508ae9e3aa5011f9d4942b2f89441d82","name":".symlinks","path":".symlinks","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e985f334cf968485fb19584ceb692110236","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98138f3662de1a399453cb9534c664235e","name":"appflowy_flutter","path":"appflowy_flutter","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9826b71c8c69e21da06f07c82c6cf362d3","name":"frontend","path":"frontend","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9828e3c8782ecd686251f0f260cb28a2d7","name":"AppFlowy","path":"AppFlowy","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9806bae6027bc7a368204df9fc3614abdb","name":"samples","path":"samples","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98e5fd31bd7e3de1ebf8947d9a8ac4c2ca","name":"dev","path":"dev","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98d049289db0016cf5f53369d79d91af15","name":"..","path":".","sourceTree":"","type":"group"},{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98c588eadd95143f0918dce747cd1a1e44","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.4/darwin/shared_preferences_foundation/Sources/shared_preferences_foundation/messages.g.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e985fee1a88b88d89bbd0b0617cbca27ded","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.4/darwin/shared_preferences_foundation/Sources/shared_preferences_foundation/SharedPreferencesPlugin.swift","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98939dcc4abff9273e20bbabe5b4fd6244","name":"shared_preferences_foundation","path":"shared_preferences_foundation","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98642d561628a9b022c32e461ac6978d8d","name":"Sources","path":"Sources","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98c2f343ab24d82d39b3b8df75288becb2","name":"shared_preferences_foundation","path":"shared_preferences_foundation","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e988e8c1d85da5ff6697e97a0b62ee47e2a","name":"darwin","path":"darwin","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e984be81f35c0b61917ec47fbad62d32af3","name":"shared_preferences_foundation","path":"shared_preferences_foundation","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9803adc645aa9991244006b1d25477fdab","name":"plugins","path":"plugins","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9830ff183ca4264558be2d7888f36dfe2d","name":".symlinks","path":".symlinks","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98b46089f714cf1702ed485a5fc0b2746c","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98dd0a1e90245c099ea267ffdb9fe7727f","name":"appflowy_flutter","path":"appflowy_flutter","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9806173dfed9c5271631519792136dcb97","name":"frontend","path":"frontend","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9896acf7fc50fc7d46cf4fd58ba1a01a8e","name":"AppFlowy","path":"AppFlowy","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9877cd8eaf1b44ad950ab2c1de574d545e","name":"samples","path":"samples","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98a4a438f84238468ae34a00396733c267","name":"dev","path":"dev","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9826990e339d1e6bfb92c43f2df4021253","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9839d6e5e860b74845e418690de949729d","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e980437a510b83f078e975ac6e022596220","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e985a9c0f13a0e24494a94657ac6f06a09e","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98f45a8957e29b9d8d573538c7c2a6660e","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e989bc0f1da02a3a34375b7c9fb790aa6de","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9873814c430069fbcad3cc2b2068a2f2c2","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e982a9fea2f1146b6aa8643c58308630eb8","name":"..","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.4/darwin/shared_preferences_foundation/Sources","sourceTree":"","type":"group"},{"children":[{"fileType":"text","guid":"bfdfe7dc352907fc980b868725387e987d0661ac6c8236d9bcf05223d8b5321c","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.4/LICENSE","sourceTree":"","type":"file"},{"fileType":"text.script.ruby","guid":"bfdfe7dc352907fc980b868725387e988319841cde2f015576fce1144b7c103b","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.5.4/darwin/shared_preferences_foundation.podspec","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98538b20e19bcb07bd154b1117ca2e7f62","name":"Pod","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e98bf7e5ed8e9ee51963dd29079b941aefc","path":"ResourceBundle-shared_preferences_foundation_privacy-shared_preferences_foundation-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e98fe484d482b057065dd2bc365ef0b1cbd","path":"shared_preferences_foundation.modulemap","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e987cfa1a168d224a15e60944154b171acd","path":"shared_preferences_foundation-dummy.m","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e9882ef96f96deea78c69f4201bad0a0f36","path":"shared_preferences_foundation-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98321ebbc91006eb23c9f9908d066f0fb5","path":"shared_preferences_foundation-prefix.pch","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e988ce14ca997c1ee5ad8eed12f49b985ef","path":"shared_preferences_foundation-umbrella.h","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e9863033d3e663dd173908d510d04d9645f","path":"shared_preferences_foundation.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e983dff215e39548f9188a7b8c8d4c24c32","path":"shared_preferences_foundation.release.xcconfig","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e9843a113f040e59beff6b2bad0d9500e2d","name":"Support Files","path":"../../../../Pods/Target Support Files/shared_preferences_foundation","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9815e66823fa48c84facba1c31c881a7f2","name":"shared_preferences_foundation","path":"../.symlinks/plugins/shared_preferences_foundation/darwin","sourceTree":"","type":"group"},{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"fileType":"text.xml","guid":"bfdfe7dc352907fc980b868725387e989d8d7f21b58b6c6ea6cdbf54b4310500","path":"../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.1+1/darwin/sqflite_darwin/Sources/sqflite_darwin/Resources/PrivacyInfo.xcprivacy","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98ff6e5bd448ece42e55969cd7ee2dcd22","name":"Resources","path":"Resources","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e986016d04cf905713b9d5b98ae9bb69fd9","name":"sqflite_darwin","path":"sqflite_darwin","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e982743918f80430a5f6e3fb467f2d4e58a","name":"Sources","path":"Sources","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98db5cfaa6497e017576049d2d766fe43d","name":"sqflite_darwin","path":"sqflite_darwin","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9887c4ce35bf05c88dedb678a8a05f7ddb","name":"darwin","path":"darwin","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98fbfd6a91559f348dcd214ab1cffe04a8","name":"sqflite_darwin","path":"sqflite_darwin","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98a41b93d2bbcfa5fa03eef8097eb619a2","name":"plugins","path":"plugins","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9835810861c4e0c37a8ee85af809b7fae6","name":".symlinks","path":".symlinks","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98e622054adbb0796c1efd972eb720d1ed","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e982499ce39bf8d8ed82a87b79418b6050f","name":"appflowy_flutter","path":"appflowy_flutter","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e984cfa4ad175bfd7a9f0e02542ff45ab26","name":"frontend","path":"frontend","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e986069180160f76ffe39b94ecee9268997","name":"AppFlowy","path":"AppFlowy","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98adeb13adbc566e980f9d252124dcea3f","name":"samples","path":"samples","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9820d6e1cf31e5551a3d68c121e504d8d1","name":"dev","path":"dev","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e985f4b97fdfbc2f7c5f93d9c36aaac6a3a","name":"..","path":".","sourceTree":"","type":"group"},{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98d7c1e03fcf1c567ec11990def452273d","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.1+1/darwin/sqflite_darwin/Sources/sqflite_darwin/SqfliteCursor.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9853d850559207c4384f4df37b5e85a414","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.1+1/darwin/sqflite_darwin/Sources/sqflite_darwin/SqfliteCursor.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98aba81c10b979d23a5281707bb944aebd","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.1+1/darwin/sqflite_darwin/Sources/sqflite_darwin/SqfliteDarwinDatabase.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9868b319ba9a799ba0a2bc5271ebda13ef","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.1+1/darwin/sqflite_darwin/Sources/sqflite_darwin/SqfliteDarwinDatabase.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98ca5fc8019efea2c0a2df87621e6bfd20","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.1+1/darwin/sqflite_darwin/Sources/sqflite_darwin/SqfliteDarwinDatabaseAdditions.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98eaab2d07055d05ca402daaf728b22722","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.1+1/darwin/sqflite_darwin/Sources/sqflite_darwin/SqfliteDarwinDatabaseAdditions.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98f164259be96c4b6bb6608f7ce49cf3de","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.1+1/darwin/sqflite_darwin/Sources/sqflite_darwin/SqfliteDarwinDatabaseQueue.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e985ad6eba05b61ecb94890b1ef3012502a","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.1+1/darwin/sqflite_darwin/Sources/sqflite_darwin/SqfliteDarwinDatabaseQueue.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e982b2273f60e8670b2f5db571354e1ea07","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.1+1/darwin/sqflite_darwin/Sources/sqflite_darwin/SqfliteDarwinDB.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98463be45d0e22ba2b4d01b4ebe090555a","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.1+1/darwin/sqflite_darwin/Sources/sqflite_darwin/SqfliteDarwinImport.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9872cbb441a98d645c1ab53aa8198b5a91","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.1+1/darwin/sqflite_darwin/Sources/sqflite_darwin/SqfliteDarwinResultSet.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e980a984e700108dd111790049ae08ac1e0","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.1+1/darwin/sqflite_darwin/Sources/sqflite_darwin/SqfliteDarwinResultSet.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e984fee7f0226744b7cccabb95aa1a2a58d","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.1+1/darwin/sqflite_darwin/Sources/sqflite_darwin/SqfliteDatabase.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9813f225fcdcd4540ad3a526d2488cee7d","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.1+1/darwin/sqflite_darwin/Sources/sqflite_darwin/SqfliteDatabase.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e987dca200d5c95cb57fe2d64a729f10e59","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.1+1/darwin/sqflite_darwin/Sources/sqflite_darwin/SqfliteImport.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9870809aee28a31c0ce2cfe6427a7a84ca","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.1+1/darwin/sqflite_darwin/Sources/sqflite_darwin/SqfliteOperation.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98a82a52d3958fd262c4d9754ada0841a1","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.1+1/darwin/sqflite_darwin/Sources/sqflite_darwin/SqfliteOperation.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98174fc872150c14b5ae3ef8ed6f674bac","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.1+1/darwin/sqflite_darwin/Sources/sqflite_darwin/SqflitePlugin.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98fe7eed89bf2c775aa63c02aebf8e6b33","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.1+1/darwin/sqflite_darwin/Sources/sqflite_darwin/SqflitePlugin.m","sourceTree":"","type":"file"},{"children":[{"children":[{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98c822bb9bb66315e186526fd41e667547","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.1+1/darwin/sqflite_darwin/Sources/sqflite_darwin/include/sqflite_darwin/SqfliteImportPublic.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98d179bdefff96b31664c33767a93f03d1","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.1+1/darwin/sqflite_darwin/Sources/sqflite_darwin/include/sqflite_darwin/SqflitePluginPublic.h","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e9892e5814da7ab09018c72d77ff35a15cf","name":"sqflite_darwin","path":"sqflite_darwin","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98a8b633bfa4915a0040bf5c00fcd27ba1","name":"include","path":"include","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98b7b37f7cc0762d9ca0708a155f22b8eb","name":"sqflite_darwin","path":"sqflite_darwin","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e989eba8369512e5541562107b2d8b760a0","name":"Sources","path":"Sources","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98e33f20f0d475d4d5413f81e2e96ca687","name":"sqflite_darwin","path":"sqflite_darwin","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e987eef1cebbe3bb46644378bc4631fe02d","name":"darwin","path":"darwin","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98ed219e0fa4be3681e4e695b0532eae32","name":"sqflite_darwin","path":"sqflite_darwin","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e988972fd127184a48f0dc3e6aee302404e","name":"plugins","path":"plugins","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9877fca5587d318fba37442797f6f705b8","name":".symlinks","path":".symlinks","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e983ae2fa1946bb36f5e91787fb46d589c5","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e984f9f6b5fd902d714436e433e3f764dd9","name":"appflowy_flutter","path":"appflowy_flutter","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98dd7c7199347a79274529e6303ba01ef7","name":"frontend","path":"frontend","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98ca3a121f05226c04281b755cc833b917","name":"AppFlowy","path":"AppFlowy","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9893f425a7aea6b4482e64d0a5bc657629","name":"samples","path":"samples","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98e49b35d1a40e4dde1b2357ef1cb8d669","name":"dev","path":"dev","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e987e5e76ea8d51c8b8d79c16a34746f33e","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9854e643717fa41262edb3c3f1d725d61a","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98ed6f9da6b7cdf2cb6886e30e6cdfbc8c","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98de4bc8577c1a9bea6d84ede9bedadcbb","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e987d9613248b511bc9325f7bcd3f3e4dd2","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e988f60c32fce1cfc81f7648e0319f50cb0","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9897a924c3a55a4bbf49fa6473d66861c6","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98c43b6316619d3de45f006103a22d7931","name":"..","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.1+1/darwin/sqflite_darwin/Sources","sourceTree":"","type":"group"},{"children":[{"fileType":"text","guid":"bfdfe7dc352907fc980b868725387e981d8665643615515f27809c8e1ce6963e","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.1+1/LICENSE","sourceTree":"","type":"file"},{"fileType":"net.daringfireball.markdown","guid":"bfdfe7dc352907fc980b868725387e9852fdd6f48c5deae81f51387413e1e002","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.1+1/darwin/README.md","sourceTree":"","type":"file"},{"fileType":"text.script.ruby","guid":"bfdfe7dc352907fc980b868725387e98856a830972b607e532cd4373e4bd4903","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/sqflite_darwin-2.4.1+1/darwin/sqflite_darwin.podspec","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98fe65523c54c78b02ecd1fb77f3e46537","name":"Pod","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e98f79a082bb47738f827607307a0e2452a","path":"ResourceBundle-sqflite_darwin_privacy-sqflite_darwin-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e98c804c2a12db2f8031b9549edefec60e1","path":"sqflite_darwin.modulemap","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e987dd296579fd63723f2385fc3e309a485","path":"sqflite_darwin-dummy.m","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e98dd8f3f1e4afb05338b44768a2eea883a","path":"sqflite_darwin-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9839943824b9f6edc4e040c5c8b2151f82","path":"sqflite_darwin-prefix.pch","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9867c38360f7a8c57f89677b1b8dd0f63b","path":"sqflite_darwin-umbrella.h","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e983c5c591d1e2590f943bcf841c7250c29","path":"sqflite_darwin.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e98ecc2b7f452e75bd86ca68cc787ffd0e6","path":"sqflite_darwin.release.xcconfig","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e9863d40e6f29652cdc2796922cd0db2932","name":"Support Files","path":"../../../../Pods/Target Support Files/sqflite_darwin","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e984e01895d459ef3578b9180bd22234ef2","name":"sqflite_darwin","path":"../.symlinks/plugins/sqflite_darwin/darwin","sourceTree":"","type":"group"},{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98ddf54a4994459d8d533e977b9d3701b4","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/super_native_extensions-0.8.24/ios/Classes/SuperNativeExtensionsPlugin.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98227f3bf9a2096f16c498e39e527dfd82","path":"../../../../../../../../../../../.pub-cache/hosted/pub.dev/super_native_extensions-0.8.24/ios/Classes/SuperNativeExtensionsPlugin.m","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e9812ef15f301a3279be9158ecc5e2ee578","name":"Classes","path":"Classes","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9821ac568b46c2e99c638c4468b42476d3","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98288b3abd52cc5524befa714a293f1d78","name":"super_native_extensions","path":"super_native_extensions","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9847c7f918c61997a2cee2375876483e92","name":"plugins","path":"plugins","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98038dcce1516b7703d28d4d5925b0e167","name":".symlinks","path":".symlinks","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98e27492f09959f12010d60e1537d0abd4","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e989b82d7b34f027e37120d28d733c9bc63","name":"appflowy_flutter","path":"appflowy_flutter","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e984e2d0c4c003b18b59d642ab053cd2e85","name":"frontend","path":"frontend","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98dac882f2bd6878262999058971045bb1","name":"AppFlowy","path":"AppFlowy","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98906dd99ca335afe3dd9a7c6cc05a2048","name":"samples","path":"samples","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9895327431cbeda356cd4093b0b03dde53","name":"dev","path":"dev","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98b3779ad14c3af16a3d17c6cb4a8af745","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98413ff013f5792a21699c27aa303d4851","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98503a720642223ab224d97e9c5f9bbe9d","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9823726b2431178718aa367a59ee6a520c","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98f023da7cab226ce319043a4e226d36dd","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98e550084af2ca7acaf14461ed1f151852","name":"..","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/super_native_extensions-0.8.24/ios","sourceTree":"","type":"group"},{"children":[{"fileType":"text","guid":"bfdfe7dc352907fc980b868725387e988cb8e2ab455bb20dc19f7a3a2246df30","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/super_native_extensions-0.8.24/LICENSE","sourceTree":"","type":"file"},{"fileType":"text.script.ruby","guid":"bfdfe7dc352907fc980b868725387e9896d5871a48fd625e7d723f390e6a98b6","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/super_native_extensions-0.8.24/ios/super_native_extensions.podspec","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98eeb382127790a1793c33c9b12ba2e097","name":"Pod","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e98a18f45e0ed141c453cff3a3c4527ecbd","path":"super_native_extensions.modulemap","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e985c10bd2540461e46ce7f11028e018d75","path":"super_native_extensions-dummy.m","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e98fb6de20b66e6b8dbd451f64bf906989c","path":"super_native_extensions-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98fdcda392f627466e5edccd1508970413","path":"super_native_extensions-prefix.pch","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98c698b70fb576aa4fe26ace0b9b8afcdd","path":"super_native_extensions-umbrella.h","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e98ecc61835f7006374d2df8ec0b73025d7","path":"super_native_extensions.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e98960e413e1bdfa694f48dc871e7ef827a","path":"super_native_extensions.release.xcconfig","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98f219566d0916d6a77685c33cd16653c8","name":"Support Files","path":"../../../../Pods/Target Support Files/super_native_extensions","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98f569f35312eec5d67d042173116f99c8","name":"super_native_extensions","path":"../.symlinks/plugins/super_native_extensions/ios","sourceTree":"","type":"group"},{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"fileType":"text.xml","guid":"bfdfe7dc352907fc980b868725387e988380eb098928f2df260922e9d961ad9b","path":"../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/url_launcher_ios-6.3.2/ios/url_launcher_ios/Sources/url_launcher_ios/Resources/PrivacyInfo.xcprivacy","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98247df5322ea5db3a84214881c52ee25d","name":"Resources","path":"Resources","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9887c8fcbc6272382fe1137437a8cfd2aa","name":"url_launcher_ios","path":"url_launcher_ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e987ab2c88ca66fa6efb7003fcd0a5fecbd","name":"Sources","path":"Sources","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98aca3ef13270c3be3fdb24dff41e0219a","name":"url_launcher_ios","path":"url_launcher_ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e988826612973646a4053217e2324d0644f","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e980d552426f9b9a7580f9c3386dc024b81","name":"url_launcher_ios","path":"url_launcher_ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e987cd73363ca2db27832a5adec82341972","name":"plugins","path":"plugins","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98fefce717b106b030e9dc126dd095e251","name":".symlinks","path":".symlinks","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9899ed1fd19213f07006bd2b9430d7772b","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9863db1d0c1fc2b855158b6fa51dde21fd","name":"appflowy_flutter","path":"appflowy_flutter","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e988e2efb7aff436f31626c71ead15daeef","name":"frontend","path":"frontend","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98c34f07f7b163887aa57af2d6c5172187","name":"AppFlowy","path":"AppFlowy","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98f1d44c68f08f6c906df9811d7caf2ef3","name":"samples","path":"samples","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9832553b1b2fc5c507feabcb35c959e868","name":"dev","path":"dev","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e984fb77cebbaaabf4e807dd6ebc12b73f9","name":"..","path":".","sourceTree":"","type":"group"},{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98490bb9f80fa5964176076d4f1e6394fa","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/url_launcher_ios-6.3.2/ios/url_launcher_ios/Sources/url_launcher_ios/Launcher.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98daf626000fc0d232b897ba1dda9c0f31","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/url_launcher_ios-6.3.2/ios/url_launcher_ios/Sources/url_launcher_ios/messages.g.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e985162a45777def5ce41cdd4f46ff104a9","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/url_launcher_ios-6.3.2/ios/url_launcher_ios/Sources/url_launcher_ios/URLLauncherPlugin.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e983b3ce4248a360b7aac31c20aa78daf2e","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/url_launcher_ios-6.3.2/ios/url_launcher_ios/Sources/url_launcher_ios/URLLaunchSession.swift","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e987ea293b0366abc71ebb96acf17297ef7","name":"url_launcher_ios","path":"url_launcher_ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9888d93807e2dc1f667f941643f134e44c","name":"Sources","path":"Sources","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e981798185d018f93fe0298b8aba5d63560","name":"url_launcher_ios","path":"url_launcher_ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e981019612be8bea02a74431d38ce3b9cbd","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9880eb5ac131bdc80edb26f033f8a72fce","name":"url_launcher_ios","path":"url_launcher_ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9851161dc629e1fb4d2d3fd8bd10ee4ca2","name":"plugins","path":"plugins","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e989fc545117a2c4837792115a214e84a3e","name":".symlinks","path":".symlinks","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98cae5b473355e5f3f56b441bc383d1a87","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98025d2d7ef7d681723bfdf777175c2ffd","name":"appflowy_flutter","path":"appflowy_flutter","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9834b9faca7fd3324a1c2164a4cf7e5313","name":"frontend","path":"frontend","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e988303e4a8f1f82c7fb4ea2702bb5cb5dd","name":"AppFlowy","path":"AppFlowy","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e984516cc538c760100f78348eb10326053","name":"samples","path":"samples","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98732e82463b828bce4818d57c1ea975ab","name":"dev","path":"dev","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9880fbcdef07ddfda2f3153be29f969c0f","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e981e967b61253c26da24779f0e1571787a","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e983de9bb36b851937d715c96cb7d26dc7a","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98611614762b8213684ec434869762ea22","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e988cb0470925262a8de3be403c8ae31ff0","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9833562037aa4c75db5121f347c9e53091","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98e70d73185437e27f145504e3dc9f1033","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98ed5927b53a4d1ccd1f9684043eea4526","name":"..","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/url_launcher_ios-6.3.2/ios/url_launcher_ios/Sources","sourceTree":"","type":"group"},{"children":[{"fileType":"text","guid":"bfdfe7dc352907fc980b868725387e98a5500bec173c13a85e52bd5fff791d8f","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/url_launcher_ios-6.3.2/LICENSE","sourceTree":"","type":"file"},{"fileType":"text.script.ruby","guid":"bfdfe7dc352907fc980b868725387e9856b8b14381dcf0263199d3e9429dbbba","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/url_launcher_ios-6.3.2/ios/url_launcher_ios.podspec","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98cceb9263cc3f6dd5b9212b964cfa7054","name":"Pod","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e98604688695bcf046b591ad88eaebd26fc","path":"ResourceBundle-url_launcher_ios_privacy-url_launcher_ios-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e987be471ca7fbb33b50a518eea5dcb87e8","path":"url_launcher_ios.modulemap","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9871fa081d7f580f4936897eb46aae0878","path":"url_launcher_ios-dummy.m","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e986f18d2a83f59c045caefbc68524cb3d3","path":"url_launcher_ios-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98ad745689d7ee76ea50404b77fa12b6f0","path":"url_launcher_ios-prefix.pch","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98eec495e5daf9423acf950402315f3522","path":"url_launcher_ios-umbrella.h","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e98b3b3d080e11ff54026ba894393f62b3a","path":"url_launcher_ios.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e980bab71536c4bda286b310a48eedac214","path":"url_launcher_ios.release.xcconfig","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98938e093d7e3b952e819ce9ac1448e73b","name":"Support Files","path":"../../../../Pods/Target Support Files/url_launcher_ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9833186a359a29a050b940ca2dbdf952b9","name":"url_launcher_ios","path":"../.symlinks/plugins/url_launcher_ios/ios","sourceTree":"","type":"group"},{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"fileType":"text.xml","guid":"bfdfe7dc352907fc980b868725387e98273c41ad57101f59fb22ee04a3de8ec9","path":"../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/Resources/PrivacyInfo.xcprivacy","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e9877cdee8b856454ff8e4213d5b4b8e81e","name":"Resources","path":"Resources","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e982e8fed289e7a93127c4e71b0a59be8c1","name":"webview_flutter_wkwebview","path":"webview_flutter_wkwebview","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e989c9e91c05d6fcd60cecbf709fcfae9f8","name":"Sources","path":"Sources","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98e5bb761b833aa1c82ed6c79ade71f8d2","name":"webview_flutter_wkwebview","path":"webview_flutter_wkwebview","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98f9d8e7c800ff72bd4f22b25217d72bd2","name":"darwin","path":"darwin","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98c7188e90eaef7902fa09bbd3b6c34138","name":"webview_flutter_wkwebview","path":"webview_flutter_wkwebview","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e980d69590ab194d9defd371868c962c163","name":"plugins","path":"plugins","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e987e1eeba79f29d46cbd63b8f82bc2f113","name":".symlinks","path":".symlinks","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e982e575334bd02bdf114450e05cddb31e6","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e987403e6bff419acd09288ab9193a47d46","name":"appflowy_flutter","path":"appflowy_flutter","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e983974551ef8940d0a77ce7e5dba345c15","name":"frontend","path":"frontend","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e984bffff6dbf90752f811d1b628435611f","name":"AppFlowy","path":"AppFlowy","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e980016f1720faf508de4c79265c93531f1","name":"samples","path":"samples","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e985409f0ee94bca73500af103a1f36ec69","name":"dev","path":"dev","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98863ea06f124e08ce56a9dc6093f14a5e","name":"..","path":".","sourceTree":"","type":"group"},{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"children":[{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9898207ddf6fb048e55cbc82f1767304ad","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/FLTWebViewFlutterPlugin.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98c78e7356e2edc959fdb6dcf759d9ea32","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/FWFDataConverters.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e984555345bd3de9a732d0775aeaa250a8a","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/FWFGeneratedWebKitApis.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98dbff9619978e59ca5bd2bd9b78df76e8","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/FWFHTTPCookieStoreHostApi.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e983ed9e6db451958c4b973973bda4af232","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/FWFInstanceManager.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9897e653c33b1ca27c845f24161d5df94b","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/FWFNavigationDelegateHostApi.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98d512e4282ea1aaea1f31ec22635e6a73","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/FWFObjectHostApi.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9863ec51aa8f06ccdd336b2b840124b5bd","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/FWFPreferencesHostApi.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9852d8f233325638788b9c883bef4c434e","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/FWFScriptMessageHandlerHostApi.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9839386e23f7ec2e92ef671f9a65fe9467","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/FWFScrollViewDelegateHostApi.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98554327c6feb943c123568d30776d18b5","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/FWFScrollViewHostApi.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e988abd8337fa8780181224b1c59e11080a","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/FWFUIDelegateHostApi.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98bbd5fcab06bd2b4eb52aea88b514bbf5","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/FWFUIViewHostApi.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98d8f2fa4ff0132b374c1eca3de40fe4ce","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/FWFURLAuthenticationChallengeHostApi.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98cdfda639596827302a7f54cea81404f7","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/FWFURLCredentialHostApi.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e982d6b13259c9952197ed43ec8ae880592","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/FWFURLHostApi.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e984f11e46b828b92865af10c3b4c4c9d68","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/FWFURLProtectionSpaceHostApi.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e984b54bebd3894ad50c5db03675b718387","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/FWFUserContentControllerHostApi.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98e0160a9358f35f9d1c0b5a60a3d0f682","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/FWFWebsiteDataStoreHostApi.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98b6eb829538253c8fd04f8ad89b11abf3","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/FWFWebViewConfigurationHostApi.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98a1bb5ceebb3ce6eafef856cbadc7f5d2","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/FWFWebViewFlutterWKWebViewExternalAPI.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98a83be649d9b3f6edd971f560f1bdc837","path":"../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/FWFWebViewHostApi.m","sourceTree":"","type":"file"},{"children":[{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98dfb923094c09a8b2984e29f4ed12750a","path":"../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/include/webview-umbrella.h","sourceTree":"","type":"file"},{"children":[{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9854b0c1f62864b38122a6cd2adf415fc8","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/include/webview_flutter_wkwebview/FLTWebViewFlutterPlugin.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98dba23cc94d8fa0e09dc855fe286cf011","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/include/webview_flutter_wkwebview/FWFDataConverters.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98ba40413987e8fe9a668fab4d2f62e968","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/include/webview_flutter_wkwebview/FWFGeneratedWebKitApis.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98b41a55f1ff6a49c7cb1bfc6754b0048a","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/include/webview_flutter_wkwebview/FWFHTTPCookieStoreHostApi.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98aa71e1ea8eea7861996ce4eea6fa83c7","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/include/webview_flutter_wkwebview/FWFInstanceManager.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e982f56450de8c5ce26b62883f42bd50aff","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/include/webview_flutter_wkwebview/FWFInstanceManager_Test.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98bdc5d2d37bbc00acdd9d5e37557c61aa","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/include/webview_flutter_wkwebview/FWFNavigationDelegateHostApi.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9876af14ebe27607bf4d780619a0507e5a","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/include/webview_flutter_wkwebview/FWFObjectHostApi.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9812c7ccf1eea1c9c80d378418ff91f3ef","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/include/webview_flutter_wkwebview/FWFPreferencesHostApi.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98ad06b47c00b9bddb782d0454f9545c23","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/include/webview_flutter_wkwebview/FWFScriptMessageHandlerHostApi.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e989c04773599c925e49e8e9df79338c2cb","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/include/webview_flutter_wkwebview/FWFScrollViewDelegateHostApi.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e982c3bc52c27128b5552e2f492ee8e9d38","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/include/webview_flutter_wkwebview/FWFScrollViewHostApi.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e983638c2d3e148c4aff7f56dae69d5bd44","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/include/webview_flutter_wkwebview/FWFUIDelegateHostApi.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9852648a53c3238ae232bc1cf732368733","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/include/webview_flutter_wkwebview/FWFUIViewHostApi.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e983ac2710f7f8419957a975656f4f11010","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/include/webview_flutter_wkwebview/FWFURLAuthenticationChallengeHostApi.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98f29772b4f240416b82bfca4c7240cc52","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/include/webview_flutter_wkwebview/FWFURLCredentialHostApi.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98287942d628c319dfb58242b7a056e501","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/include/webview_flutter_wkwebview/FWFURLHostApi.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e984b2e4dc50da302b519989286271f986f","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/include/webview_flutter_wkwebview/FWFURLProtectionSpaceHostApi.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98da68ab6d947fd76c2248fff2397f9474","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/include/webview_flutter_wkwebview/FWFUserContentControllerHostApi.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98db1f999a296f5d4b787f4f9b4b82b565","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/include/webview_flutter_wkwebview/FWFWebsiteDataStoreHostApi.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98635d8b53208d8a44fe1b27f33c27ad74","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/include/webview_flutter_wkwebview/FWFWebViewConfigurationHostApi.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98de08c86939fbb40b81087f28b271e5b5","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/include/webview_flutter_wkwebview/FWFWebViewFlutterWKWebViewExternalAPI.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98984ca0249b9df8b5209a6dcb1c869dfc","path":"../../../../../../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/include/webview_flutter_wkwebview/FWFWebViewHostApi.h","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98d786586788316e81a204e06a860d9b2d","name":"webview_flutter_wkwebview","path":"webview_flutter_wkwebview","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e986d305bdf63057a110d9a04a946a6a0b3","name":"include","path":"include","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e985af659c04f6cdf4a7c73cb8559d95a55","name":"webview_flutter_wkwebview","path":"webview_flutter_wkwebview","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98e1e70587e6aba44c0bf6b4fa49bed627","name":"Sources","path":"Sources","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e983b00ae6fdbc30a34dfacc53e5e678acb","name":"webview_flutter_wkwebview","path":"webview_flutter_wkwebview","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9865432e0f8cd5ef04acb4722323bfaef7","name":"darwin","path":"darwin","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e981fe60fe58df7074f3f027dfb308c785b","name":"webview_flutter_wkwebview","path":"webview_flutter_wkwebview","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98299b41c4840e81d29fd9defe63f562c1","name":"plugins","path":"plugins","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98a1a72cddbe31b7904461fb30eabe3f41","name":".symlinks","path":".symlinks","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9866cb0dd0dbb6d8ae7c42bf7e70385636","name":"ios","path":"ios","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e982c9395928d2b28bc4832e7eaf6338061","name":"appflowy_flutter","path":"appflowy_flutter","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e981cf382f136f655ee4ef53b31836e9976","name":"frontend","path":"frontend","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e982695540da12cb3688e9cd89de55b4c12","name":"AppFlowy","path":"AppFlowy","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e989cea8ab7b2bc9aafea94b071fcb36fe9","name":"samples","path":"samples","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e987cb1234016d9a45a8e6ebfa24a1ca6b6","name":"dev","path":"dev","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e981ba95ad9f21e528ba007f114552e8faa","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e989c9004908f380362c00015c90fe116d5","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98fe5984c239273e4752feee9c1cd0d417","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98eb7ac77a35fceda15f40d2e6dfe455e1","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98481664ecd25a98fbf478061e4214182c","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e984d34857cedf24e0ffba417d4ddb7a5a7","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98f104ab4138053278b0c742e5da3a37b6","name":"..","path":"..","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e986ba8636d82c00ef31adc8e2a02c78383","name":"..","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources","sourceTree":"","type":"group"},{"children":[{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e98e4b951171365bdf1469e58e30bb095be","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview/Sources/webview_flutter_wkwebview/include/FlutterWebView.modulemap","sourceTree":"","type":"file"},{"fileType":"text","guid":"bfdfe7dc352907fc980b868725387e98f15216db275bd0a141b390a80247ad56","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/LICENSE","sourceTree":"","type":"file"},{"fileType":"text.script.ruby","guid":"bfdfe7dc352907fc980b868725387e98319f1ecfeb379cf849cfb0c9a15fbdf7","path":"../../../../../../../../../../.pub-cache/hosted/pub.dev/webview_flutter_wkwebview-3.17.0/darwin/webview_flutter_wkwebview.podspec","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98d1afcef86fa9f9ff1253aefecce2db52","name":"Pod","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e980dc040649dc9e2eab53c3da3c40c35b0","path":"ResourceBundle-webview_flutter_wkwebview_privacy-webview_flutter_wkwebview-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e989d9a8732910c4132ba5e9ee6c49a6fd0","path":"webview_flutter_wkwebview.modulemap","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e985debbc2cab2d792fa92242781697aa86","path":"webview_flutter_wkwebview-dummy.m","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e98f10ce8a5ece3a15020d640ce3ed6466e","path":"webview_flutter_wkwebview-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9818b0186c11c004e985a36edb09183861","path":"webview_flutter_wkwebview-prefix.pch","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e98e3c8f3c679654f0d8322c0bfe86cb48c","path":"webview_flutter_wkwebview.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e98de306c3f6c2c14f312be69bcd973767d","path":"webview_flutter_wkwebview.release.xcconfig","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98fb0231873e76e3fdd25c08c4f7142e58","name":"Support Files","path":"../../../../Pods/Target Support Files/webview_flutter_wkwebview","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98daffe5a2e168f85d5660f57f2cc4f3d1","name":"webview_flutter_wkwebview","path":"../.symlinks/plugins/webview_flutter_wkwebview/darwin","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e9880465010b585dd5bc5b7af0a27d58067","name":"Development Pods","path":"","sourceTree":"","type":"group"},{"children":[{"children":[{"fileType":"wrapper.framework","guid":"bfdfe7dc352907fc980b868725387e984bcd4feee9e1dfb73f639f9bac23b2e2","path":"Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/AVFoundation.framework","sourceTree":"DEVELOPER_DIR","type":"file"},{"fileType":"wrapper.framework","guid":"bfdfe7dc352907fc980b868725387e987724d547599f4408ee3b3d56fa903a20","path":"Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/AVKit.framework","sourceTree":"DEVELOPER_DIR","type":"file"},{"fileType":"wrapper.framework","guid":"bfdfe7dc352907fc980b868725387e98c18474134a48ed556f51c824bfca3246","path":"Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/CoreTelephony.framework","sourceTree":"DEVELOPER_DIR","type":"file"},{"fileType":"wrapper.framework","guid":"bfdfe7dc352907fc980b868725387e9885770c7fb25aa0db0cfd09c5443f9797","path":"Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/Foundation.framework","sourceTree":"DEVELOPER_DIR","type":"file"},{"fileType":"wrapper.framework","guid":"bfdfe7dc352907fc980b868725387e9802c3ee0032b21aafbeccc5b7db734fb2","path":"Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/ImageIO.framework","sourceTree":"DEVELOPER_DIR","type":"file"},{"fileType":"wrapper.framework","guid":"bfdfe7dc352907fc980b868725387e984497b71acede5531fd260c9ac8b3d9e1","path":"Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/Photos.framework","sourceTree":"DEVELOPER_DIR","type":"file"},{"fileType":"wrapper.framework","guid":"bfdfe7dc352907fc980b868725387e98d671fdb5a7bd1c3267d3e6f35907cbca","path":"Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/QuartzCore.framework","sourceTree":"DEVELOPER_DIR","type":"file"},{"fileType":"wrapper.framework","guid":"bfdfe7dc352907fc980b868725387e9872b55968a8ae9b74a90d6bfa9882dd5a","path":"Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/SystemConfiguration.framework","sourceTree":"DEVELOPER_DIR","type":"file"},{"fileType":"wrapper.framework","guid":"bfdfe7dc352907fc980b868725387e98d159ebe55fadf57df5691badabf215cd","path":"Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/UIKit.framework","sourceTree":"DEVELOPER_DIR","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e9827102c11d594e53de5adfd4435cd953d","name":"iOS","path":"","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e986f57df5597ed36b645cb934c885be56d","name":"Frameworks","path":"","sourceTree":"","type":"group"},{"children":[{"children":[{"children":[{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e980f256812a9858de27fb60bd1accdd32c","path":"Sources/DKImagePickerController/View/Cell/DKAssetGroupCellItemProtocol.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9837ba02f2eab7de1730b72c879d53f332","path":"Sources/DKImagePickerController/View/Cell/DKAssetGroupDetailBaseCell.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e981791c992ccf9564106a690e1d96420b8","path":"Sources/DKImagePickerController/View/Cell/DKAssetGroupDetailCameraCell.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e981149a1d26875e8b68800278ef1ef0dd2","path":"Sources/DKImagePickerController/View/Cell/DKAssetGroupDetailImageCell.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98c8875336f053558b1f9ed7c43e262ddd","path":"Sources/DKImagePickerController/View/DKAssetGroupDetailVC.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98164873936b2ccd15bdc608cc60c2cc65","path":"Sources/DKImagePickerController/View/Cell/DKAssetGroupDetailVideoCell.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98a7cecd4700762c7a109513bfb52989bb","path":"Sources/DKImagePickerController/View/DKAssetGroupGridLayout.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e989c224ca64eb47c09a922a3d927cf7f10","path":"Sources/DKImagePickerController/View/DKAssetGroupListVC.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e986c20dce043f12383acfeb98f96470e02","path":"Sources/DKImagePickerController/DKImageAssetExporter.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9855672fea159780d4dd23a1f5a30a7837","path":"Sources/DKImagePickerController/DKImageExtensionController.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98261f313efe36b40a087a9d35206c9607","path":"Sources/DKImagePickerController/DKImagePickerController.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e982975067e23a41db0b8f6979b4a4a3b55","path":"Sources/DKImagePickerController/DKImagePickerControllerBaseUIDelegate.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98d331501c9a3bbd1cec21da6d1bed6a79","path":"Sources/DKImagePickerController/View/DKPermissionView.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9851006020f4c7fc277370bdaceee539fd","path":"Sources/DKImagePickerController/DKPopoverViewController.swift","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98f1abbceda566cfe605dbb72a81ac8f8e","name":"Core","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98e10cad38a7b8b791b5523321d2112ef0","path":"Sources/DKImageDataManager/Model/DKAsset.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e985cdfbee7f0fa63af790094c590a82f83","path":"Sources/DKImageDataManager/Model/DKAsset+Export.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e982173071859389679a6c45171b90987da","path":"Sources/DKImageDataManager/Model/DKAsset+Fetch.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98a89a84fdf4f961d13d26db8520129232","path":"Sources/DKImageDataManager/Model/DKAssetGroup.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98e418cf226491ab81be949451bec00d12","path":"Sources/DKImageDataManager/DKImageBaseManager.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9878488e740dd474df94f5eec1f4ebdef4","path":"Sources/DKImageDataManager/DKImageDataManager.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98e01ac6181d083bc327440f955529b52b","path":"Sources/DKImageDataManager/DKImageGroupDataManager.swift","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e981e366accc57fcc9cc1a70ecd199c50e7","name":"ImageDataManager","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e982a05c3bf490f4a4639be6133ce46fc50","path":"Sources/Extensions/DKImageExtensionGallery.swift","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e9878b271eaafac7e3a83dd4dcb67d7b63e","name":"PhotoGallery","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9892de1f95603b3309408b855b150222e8","path":"Sources/DKImagePickerController/Resource/DKImagePickerControllerResource.swift","sourceTree":"","type":"file"},{"children":[{"fileType":"folder","guid":"bfdfe7dc352907fc980b868725387e982cb145326ee0fe9b023de780deed78c8","path":"Sources/DKImagePickerController/Resource/Resources/ar.lproj","sourceTree":"","type":"file"},{"fileType":"folder","guid":"bfdfe7dc352907fc980b868725387e98aa1b813ba9ab60abaff79984557a95ae","path":"Sources/DKImagePickerController/Resource/Resources/Base.lproj","sourceTree":"","type":"file"},{"fileType":"folder","guid":"bfdfe7dc352907fc980b868725387e980c9106c6deeaa489bece52872ddc8302","path":"Sources/DKImagePickerController/Resource/Resources/da.lproj","sourceTree":"","type":"file"},{"fileType":"folder","guid":"bfdfe7dc352907fc980b868725387e98638a8ead3c4921071ce058b31e0789b1","path":"Sources/DKImagePickerController/Resource/Resources/de.lproj","sourceTree":"","type":"file"},{"fileType":"folder","guid":"bfdfe7dc352907fc980b868725387e9808c6c81d6544958ee5022314e9257bea","path":"Sources/DKImagePickerController/Resource/Resources/en.lproj","sourceTree":"","type":"file"},{"fileType":"folder","guid":"bfdfe7dc352907fc980b868725387e989d1190414344f8164f52c639a0b3923e","path":"Sources/DKImagePickerController/Resource/Resources/es.lproj","sourceTree":"","type":"file"},{"fileType":"folder","guid":"bfdfe7dc352907fc980b868725387e98a3612de5bfe6103968b0ad0e24321c06","path":"Sources/DKImagePickerController/Resource/Resources/fr.lproj","sourceTree":"","type":"file"},{"fileType":"folder","guid":"bfdfe7dc352907fc980b868725387e9840393198352d2a493eca64a5e8366781","path":"Sources/DKImagePickerController/Resource/Resources/hu.lproj","sourceTree":"","type":"file"},{"fileType":"folder.assetcatalog","guid":"bfdfe7dc352907fc980b868725387e9866b7b2a063b357116fff6128faab6010","path":"Sources/DKImagePickerController/Resource/Resources/Images.xcassets","sourceTree":"","type":"file"},{"fileType":"folder","guid":"bfdfe7dc352907fc980b868725387e98389b54d175b1ebff57cec1e50f863fcc","path":"Sources/DKImagePickerController/Resource/Resources/it.lproj","sourceTree":"","type":"file"},{"fileType":"folder","guid":"bfdfe7dc352907fc980b868725387e98653c7ebf4d1cd10720ef59b098ac5602","path":"Sources/DKImagePickerController/Resource/Resources/ja.lproj","sourceTree":"","type":"file"},{"fileType":"folder","guid":"bfdfe7dc352907fc980b868725387e98104b68b4ff0a25a5296d0b83fcc56453","path":"Sources/DKImagePickerController/Resource/Resources/ko.lproj","sourceTree":"","type":"file"},{"fileType":"folder","guid":"bfdfe7dc352907fc980b868725387e98d31edcc28dfbbe3d8ce637b5afc30729","path":"Sources/DKImagePickerController/Resource/Resources/nb-NO.lproj","sourceTree":"","type":"file"},{"fileType":"folder","guid":"bfdfe7dc352907fc980b868725387e984482abef551eb7f8c2328ba24695640c","path":"Sources/DKImagePickerController/Resource/Resources/nl.lproj","sourceTree":"","type":"file"},{"fileType":"folder","guid":"bfdfe7dc352907fc980b868725387e987d2482c68c58caea8c964ddc88375bce","path":"Sources/DKImagePickerController/Resource/Resources/pt_BR.lproj","sourceTree":"","type":"file"},{"fileType":"folder","guid":"bfdfe7dc352907fc980b868725387e98e82421c9939f56bd2bac60c90c0972a4","path":"Sources/DKImagePickerController/Resource/Resources/ru.lproj","sourceTree":"","type":"file"},{"fileType":"folder","guid":"bfdfe7dc352907fc980b868725387e98641e96c7b9979000ba15756d8c4088c5","path":"Sources/DKImagePickerController/Resource/Resources/tr.lproj","sourceTree":"","type":"file"},{"fileType":"folder","guid":"bfdfe7dc352907fc980b868725387e987dbf20be783e65771c0b6b10146e0526","path":"Sources/DKImagePickerController/Resource/Resources/ur.lproj","sourceTree":"","type":"file"},{"fileType":"folder","guid":"bfdfe7dc352907fc980b868725387e98496e8767ebc06f02f3a190467b76c669","path":"Sources/DKImagePickerController/Resource/Resources/vi.lproj","sourceTree":"","type":"file"},{"fileType":"folder","guid":"bfdfe7dc352907fc980b868725387e9851d4a9e151fbaba3b43b0c1c328e7692","path":"Sources/DKImagePickerController/Resource/Resources/zh-Hans.lproj","sourceTree":"","type":"file"},{"fileType":"folder","guid":"bfdfe7dc352907fc980b868725387e989af334eda3f4b74b226c99b858e3a8ee","path":"Sources/DKImagePickerController/Resource/Resources/zh-Hant.lproj","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e9804dfbef9d9e61df9bb559e2872b5da24","name":"Resources","path":"","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98d02568085c3a0b85378f96f4eb289f1a","name":"Resource","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e985e388c776c1b334623438d45d3541462","path":"DKImagePickerController.modulemap","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e982407d1e8008282cbe7def1a4ffcc63a5","path":"DKImagePickerController-dummy.m","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e98ea7538dde57b4e0f1e4dd4e75d1c0d0c","path":"DKImagePickerController-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98b27014ca8a98af66a8fbde29e88273ac","path":"DKImagePickerController-prefix.pch","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e989c1f453bb5d9dee5b1b66f4e8eb4eb4d","path":"DKImagePickerController-umbrella.h","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e9806dc5717c6a31d76aad6bd6ece1d0e8a","path":"DKImagePickerController.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e982c698840c90027623e3da75a6ca7c080","path":"DKImagePickerController.release.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e987e62c3ecefb84e0040e723e52f3a31b8","path":"ResourceBundle-DKImagePickerController-DKImagePickerController-Info.plist","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e983adef97a715cd560344faddd2136ca7c","name":"Support Files","path":"../Target Support Files/DKImagePickerController","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e986a64e2ff11892e14ae7fcb90015c6a37","name":"DKImagePickerController","path":"DKImagePickerController","sourceTree":"","type":"group"},{"children":[{"children":[{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9863e802284c73bd79713e7ebbd9884a03","path":"DKPhotoGallery/DKPhotoGallery.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e980341a413639d3324d70cd433b477ad56","path":"DKPhotoGallery/DKPhotoGalleryContentVC.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e989de872349f4afb6a3ec10554d20a0824","path":"DKPhotoGallery/Transition/DKPhotoGalleryInteractiveTransition.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9891931bbad7bcd3c15555c1ab3b5e9eda","path":"DKPhotoGallery/DKPhotoGalleryScrollView.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e989304b9d8408d53cfd4dc626445b7aa78","path":"DKPhotoGallery/Transition/DKPhotoGalleryTransitionController.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9851ce307e43c7abf9a92db60b62d92eed","path":"DKPhotoGallery/Transition/DKPhotoGalleryTransitionDismiss.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98bb524791f17b91cf8d8e69a04b9d6241","path":"DKPhotoGallery/Transition/DKPhotoGalleryTransitionPresent.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9817d140dcad4deb3eb9494e9b79f6a54f","path":"DKPhotoGallery/DKPhotoIncrementalIndicator.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98b08fe598889329654b388a853ab11345","path":"DKPhotoGallery/DKPhotoPreviewFactory.swift","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98abec4317337fcb2bdb4f60b9acc75558","name":"Core","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e981fe52c027e9b931957c809c3731fe5e8","path":"DKPhotoGallery/DKPhotoGalleryItem.swift","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98790064f3a8ebed4c57f29d168a40864a","name":"Model","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98c8c44b9bad588a8cbce8ae16059cc044","path":"DKPhotoGallery/Preview/PDFPreview/DKPDFView.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e989def67d6c082310eb82979d6e6048439","path":"DKPhotoGallery/Preview/ImagePreview/DKPhotoBaseImagePreviewVC.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98e64926604b2aa420c550979dce302c11","path":"DKPhotoGallery/Preview/DKPhotoBasePreviewVC.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e985f43f00297e493a6c1ff3f01846dd6ab","path":"DKPhotoGallery/Preview/DKPhotoContentAnimationView.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e982b1b24b667eeb0aa9df1641d1afbf02f","path":"DKPhotoGallery/Preview/ImagePreview/DKPhotoImageDownloader.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98b7c4392f573762b73d261ae2c1ad25f5","path":"DKPhotoGallery/Preview/ImagePreview/DKPhotoImagePreviewVC.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98036bb4411bb8d063d059db4e041547ed","path":"DKPhotoGallery/Preview/ImagePreview/DKPhotoImageUtility.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98b73272e18580fd7065bff4ab280cf9d1","path":"DKPhotoGallery/Preview/ImagePreview/DKPhotoImageView.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98b16bf7005681538906dcee4437e1937d","path":"DKPhotoGallery/Preview/PDFPreview/DKPhotoPDFPreviewVC.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e989a757ab8e8a9fc630d1a57794cb67b77","path":"DKPhotoGallery/Preview/PlayerPreview/DKPhotoPlayerPreviewVC.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e988a849dc79b3d29aaf31294b33a51b25b","path":"DKPhotoGallery/Preview/DKPhotoProgressIndicator.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98a03d72595a015012babd53f62ce9fe7b","path":"DKPhotoGallery/Preview/DKPhotoProgressIndicatorProtocol.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9895fb6b2018a434a37a846c5e29fe4c20","path":"DKPhotoGallery/Preview/QRCode/DKPhotoQRCodeResultVC.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9806b510aa1c18127530918269f3b10ecd","path":"DKPhotoGallery/Preview/QRCode/DKPhotoWebVC.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e988b5a7957a4c4d63e718fe2f2bfa1a142","path":"DKPhotoGallery/Preview/PlayerPreview/DKPlayerView.swift","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98a4f4a3f314a53e8278137c97d48a2a7d","name":"Preview","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e982c920e4b6a548594014fc19d89752af0","path":"DKPhotoGallery/Resource/DKPhotoGalleryResource.swift","sourceTree":"","type":"file"},{"children":[{"fileType":"folder","guid":"bfdfe7dc352907fc980b868725387e98322362f55daedd42cf2628aab1e7ee5a","path":"DKPhotoGallery/Resource/Resources/Base.lproj","sourceTree":"","type":"file"},{"fileType":"folder","guid":"bfdfe7dc352907fc980b868725387e98257fc87c20dddd559c3a65cb29ef2a8c","path":"DKPhotoGallery/Resource/Resources/en.lproj","sourceTree":"","type":"file"},{"fileType":"folder.assetcatalog","guid":"bfdfe7dc352907fc980b868725387e98632a77eb6e8a5c194970ffbb837e3163","path":"DKPhotoGallery/Resource/Resources/Images.xcassets","sourceTree":"","type":"file"},{"fileType":"folder","guid":"bfdfe7dc352907fc980b868725387e981cdbb40ea77c1fcafa54f43ddd5ead0e","path":"DKPhotoGallery/Resource/Resources/zh-Hans.lproj","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e9833f8b21cf1e493b32aa79c353bb36c59","name":"Resources","path":"","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e987e4621e932260350f9fe18d776062789","name":"Resource","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e98669eec05ea75fd44fb0e2666721dbfac","path":"DKPhotoGallery.modulemap","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e986ea1aeb64b4ee3b54e278e3a7146573c","path":"DKPhotoGallery-dummy.m","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e984490f039b0eb3468d94b0ab2e6a714c8","path":"DKPhotoGallery-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9827c39759d14ae68710c6e6f3caaa10e9","path":"DKPhotoGallery-prefix.pch","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e986024dcc5015a387313d8adc4bce7b36a","path":"DKPhotoGallery-umbrella.h","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e98b1d81c0ffe868a64db997ed9da144cf0","path":"DKPhotoGallery.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e988204bc98e0d67983074132a4f5665a6b","path":"DKPhotoGallery.release.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e987ab489cc4f30435cd7ded82f0c6eae68","path":"ResourceBundle-DKPhotoGallery-DKPhotoGallery-Info.plist","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e987ec59054c088e47e413206915ef9028f","name":"Support Files","path":"../Target Support Files/DKPhotoGallery","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e986c674d50d3b10e235f4f35f7a35d5ae8","name":"DKPhotoGallery","path":"DKPhotoGallery","sourceTree":"","type":"group"},{"children":[{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9896448ec1f9cbf14857b3fe6b95caa011","path":"Sources/Reachability.swift","sourceTree":"","type":"file"},{"children":[{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e989bacf7eba26d0bb486de3cbfe5ea13c2","path":"ReachabilitySwift.modulemap","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98b6244bc1a6a40e6115703cb3d00b8446","path":"ReachabilitySwift-dummy.m","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e987b7b6b5547766ec62d38737cd24e68cd","path":"ReachabilitySwift-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98547e39e19bf28c52f14233768cc21bd9","path":"ReachabilitySwift-prefix.pch","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e988aeea5b451523377c4d21395c5903a7c","path":"ReachabilitySwift-umbrella.h","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e98901baae58940cfd27e962f9d5011362f","path":"ReachabilitySwift.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e989a874d2419abc53ad8b0cd3d2a5318ae","path":"ReachabilitySwift.release.xcconfig","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e9896d1894bae44836917da92d3aab54f93","name":"Support Files","path":"../Target Support Files/ReachabilitySwift","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98bebab75b96c37c53dc67122f6031b002","name":"ReachabilitySwift","path":"ReachabilitySwift","sourceTree":"","type":"group"},{"children":[{"children":[{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98cbbfeea198fc4bce239c47fc57d8a961","path":"SDWebImage/Private/NSBezierPath+SDRoundedCorners.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98a4610931d9a303cf4a5413361d1ada97","path":"SDWebImage/Private/NSBezierPath+SDRoundedCorners.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e980e908f1ceaaa33a36d5403f550e8b9aa","path":"SDWebImage/Core/NSButton+WebCache.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9817f5ea7520f6058c0504b71e2865c594","path":"SDWebImage/Core/NSButton+WebCache.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e984f06f7f319f35e24d3947bb38a46b4b9","path":"SDWebImage/Core/NSData+ImageContentType.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e987b53a8893f5f3cdd6faadb0d3bfc4d62","path":"SDWebImage/Core/NSData+ImageContentType.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9890edfb9455d8d050d856d19de294b76c","path":"SDWebImage/Core/NSImage+Compatibility.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e987853d28d16fa30841c1a55b61c447d01","path":"SDWebImage/Core/NSImage+Compatibility.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98c8d140d68f858c8de679df2d49e39ce1","path":"SDWebImage/Core/SDAnimatedImage.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98b0ebe90ca974f5cad05396d5c0c407cc","path":"SDWebImage/Core/SDAnimatedImage.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9809df879cdb20a2d85e42e0ec9467d874","path":"SDWebImage/Core/SDAnimatedImagePlayer.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98e2ed6b92aae530671fb6a85f4968036a","path":"SDWebImage/Core/SDAnimatedImagePlayer.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98f116af502c254813038fb0fa9b6d7e43","path":"SDWebImage/Core/SDAnimatedImageRep.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e987a40b9365faa88cb247c44544207a3ce","path":"SDWebImage/Core/SDAnimatedImageRep.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e985026f7f77e36035ebbdce01168f1c703","path":"SDWebImage/Core/SDAnimatedImageView.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98d1c51ae1c8f49cab13fbed609b95490d","path":"SDWebImage/Core/SDAnimatedImageView.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9853c881de2f36eb982e91b6111edd2159","path":"SDWebImage/Core/SDAnimatedImageView+WebCache.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98229f3dff826db691d58cbf3df3ae4b2c","path":"SDWebImage/Core/SDAnimatedImageView+WebCache.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98a7fb3d4eb5f455ce911e0e84b77ee5df","path":"SDWebImage/Private/SDAssociatedObject.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9889c0b922f6710e9bbaad47d90e458796","path":"SDWebImage/Private/SDAssociatedObject.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9870a50afed1d5bb331637a22e1b2cdab6","path":"SDWebImage/Private/SDAsyncBlockOperation.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e989a88b4352ddd2268b71af2cececb468f","path":"SDWebImage/Private/SDAsyncBlockOperation.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9894060044116c365175a4d8c9d018b47b","path":"SDWebImage/Private/SDDeviceHelper.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98379407cdd5ec5f0842afe1dfb96cddaf","path":"SDWebImage/Private/SDDeviceHelper.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9820bbf68e6d4b7e185246c728464b69b4","path":"SDWebImage/Core/SDDiskCache.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98556c8c3201769e199eb76c9fc9d5a0de","path":"SDWebImage/Core/SDDiskCache.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9822703fb114a75f5c5f2f254bb3f6484c","path":"SDWebImage/Private/SDDisplayLink.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e982e5995722797bf81f987a7b34e8d29db","path":"SDWebImage/Private/SDDisplayLink.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98688ef71815752ca7f6167d44ae846d81","path":"SDWebImage/Private/SDFileAttributeHelper.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98d4442f69c6514bc98fae925092574979","path":"SDWebImage/Private/SDFileAttributeHelper.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98cc8572110877f8beb99b8973afcea556","path":"SDWebImage/Core/SDGraphicsImageRenderer.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98d990503ee20a63ae3a6ac274eb15e500","path":"SDWebImage/Core/SDGraphicsImageRenderer.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98a81bf725f34f12d4633981ff1a9be615","path":"SDWebImage/Core/SDImageAPNGCoder.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98a93d56dd2776df0d9323eaf235ac4573","path":"SDWebImage/Core/SDImageAPNGCoder.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98063a7a83ef11a2830cff9aef0f246a46","path":"SDWebImage/Private/SDImageAssetManager.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9878a035a3947c0bfb2c74de160efc90f8","path":"SDWebImage/Private/SDImageAssetManager.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98ba45c50b7e93c71a1094293aeb130683","path":"SDWebImage/Core/SDImageAWebPCoder.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e988b1062c96ec96331da01cae06e95e9bb","path":"SDWebImage/Core/SDImageAWebPCoder.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98adff88ced90105be82ec5c254de38c39","path":"SDWebImage/Core/SDImageCache.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98b59fb4d17416bbf73623c9ca148d4781","path":"SDWebImage/Core/SDImageCache.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98da4185ba0b8e0c270b9045f1c0ea7f34","path":"SDWebImage/Core/SDImageCacheConfig.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98f690a5caa9ac98baa8bfaca0d8119a97","path":"SDWebImage/Core/SDImageCacheConfig.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e984fc8806592e30baa20155c119711a319","path":"SDWebImage/Core/SDImageCacheDefine.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98aa465b740f26e7c857d665d9e948f68f","path":"SDWebImage/Core/SDImageCacheDefine.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98025e98798e009017d38c6b8f9f98dd3b","path":"SDWebImage/Core/SDImageCachesManager.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e989babcbb0bec790231c9ea03d45485895","path":"SDWebImage/Core/SDImageCachesManager.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9848a18db96f9cc4192f5855b2c6ec65fc","path":"SDWebImage/Private/SDImageCachesManagerOperation.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e988c4bfa1a1a41475204073a9c37cc4138","path":"SDWebImage/Private/SDImageCachesManagerOperation.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9878c179b604ae00d511bcfe7b1c26229a","path":"SDWebImage/Core/SDImageCoder.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98e79c00adee724aeb4ee07faf25c36816","path":"SDWebImage/Core/SDImageCoder.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9899370db25d5ccec85c5bc3841d9150c3","path":"SDWebImage/Core/SDImageCoderHelper.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9864cb7481f2ac08dacad300433c1e0e01","path":"SDWebImage/Core/SDImageCoderHelper.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98466aa14d7abf59b152f093b8602bd34b","path":"SDWebImage/Core/SDImageCodersManager.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98a351c522c640e5c3836b0b3b0ff15805","path":"SDWebImage/Core/SDImageCodersManager.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e989938d9775335a6ffdff595f50234b88b","path":"SDWebImage/Core/SDImageFrame.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98a8ca264bae9407c5ab570cb5c4eaa6e8","path":"SDWebImage/Core/SDImageFrame.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e989a78ff5eec8840469dfa52c4df920d84","path":"SDWebImage/Core/SDImageGIFCoder.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e984d22bab198a56a739526a9f9fac69088","path":"SDWebImage/Core/SDImageGIFCoder.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98b137bd091f2422235254ba87eeeebb86","path":"SDWebImage/Core/SDImageGraphics.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e987ec2e6e40d48d5e1c573c77074e16edf","path":"SDWebImage/Core/SDImageGraphics.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9817f302504ade0c532160f76082a200fc","path":"SDWebImage/Core/SDImageHEICCoder.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98cec361cb5581e3f861f0c9ebc7eb79b3","path":"SDWebImage/Core/SDImageHEICCoder.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9863c1633045c8f4e6a7fefb974e14ca36","path":"SDWebImage/Core/SDImageIOAnimatedCoder.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98728975404c77f683b561feae9c1d429f","path":"SDWebImage/Core/SDImageIOAnimatedCoder.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9840158ea8f0a0e2c720c2f9a0c43bc29e","path":"SDWebImage/Private/SDImageIOAnimatedCoderInternal.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e981a3f021a5e50a888a5ae1abc4e2e57fe","path":"SDWebImage/Core/SDImageIOCoder.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9810ea71fbe3808e0193ef3ebfe97b71ed","path":"SDWebImage/Core/SDImageIOCoder.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98e3114679d79d3ddb667c3e6fb257d6fb","path":"SDWebImage/Core/SDImageLoader.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98d8bfeefcb91662f7491bdee227ce85b8","path":"SDWebImage/Core/SDImageLoader.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e987a235e20b10d513a95f2fe5a4faa9fb2","path":"SDWebImage/Core/SDImageLoadersManager.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98ae292dc3f6734ffa8a30d1eb750b312a","path":"SDWebImage/Core/SDImageLoadersManager.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98555b647b7b68b3c59175202ef82bc955","path":"SDWebImage/Core/SDImageTransformer.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98379f432747c66b556608cfcb163dd08a","path":"SDWebImage/Core/SDImageTransformer.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e984b63b84d1f79f14a6dc3a12cac2809ed","path":"SDWebImage/Private/SDInternalMacros.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98aad7fdfe00389f16c787afd556ef9f8e","path":"SDWebImage/Private/SDInternalMacros.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98209eac57b1854edf591b16e3f80fec69","path":"SDWebImage/Core/SDMemoryCache.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e982b6d0c6ce7e156d1ba21129855543471","path":"SDWebImage/Core/SDMemoryCache.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9898c0fd4390df5133c07e348bdbd55d9c","path":"SDWebImage/Private/SDmetamacros.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e988e65ef0d735983c43262cb848c637768","path":"SDWebImage/Private/SDWeakProxy.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98c1a6e6955d89f3463af21ab50142f735","path":"SDWebImage/Private/SDWeakProxy.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9802c2dd1c8e6745d6ad8dfa6ac15a50df","path":"WebImage/SDWebImage.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98583254b6e10a610161162b2462c9a243","path":"SDWebImage/Core/SDWebImageCacheKeyFilter.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98d67745e1c95b9a3b0b6afc23ba0e53d1","path":"SDWebImage/Core/SDWebImageCacheKeyFilter.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98e6013c78eea89a230352d0a58977ee65","path":"SDWebImage/Core/SDWebImageCacheSerializer.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98c798c0650901d3f454e84b38b32b7c14","path":"SDWebImage/Core/SDWebImageCacheSerializer.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e987bcdc97ee957f7d3dcc46ccd3df087d6","path":"SDWebImage/Core/SDWebImageCompat.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98175d683f5876f4bef28a5c568a03ee0e","path":"SDWebImage/Core/SDWebImageCompat.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98a1f302e052441cf2364ea9fdbb633665","path":"SDWebImage/Core/SDWebImageDefine.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9801960d18f6952523241592bfcf8df74f","path":"SDWebImage/Core/SDWebImageDefine.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98da783a4512ad557f5bed33b07819428b","path":"SDWebImage/Core/SDWebImageDownloader.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e988bf2ba3046f2a1dc82c3ae3236bb5c29","path":"SDWebImage/Core/SDWebImageDownloader.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e987349aeb8db30aa64526eda7973f155b2","path":"SDWebImage/Core/SDWebImageDownloaderConfig.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e987233e0726650facd69de48131fbfb887","path":"SDWebImage/Core/SDWebImageDownloaderConfig.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e981f0f6fc0eca1c4df41989a0e01130390","path":"SDWebImage/Core/SDWebImageDownloaderDecryptor.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98b1e56d48b1b3c795cd043d008ac2d69f","path":"SDWebImage/Core/SDWebImageDownloaderDecryptor.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e986735d6c802483fa77c3db72c66398b93","path":"SDWebImage/Core/SDWebImageDownloaderOperation.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e986a5410248346eb3a5db888eeaf016ed3","path":"SDWebImage/Core/SDWebImageDownloaderOperation.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e980f2c0526259c08fb215f33283d062669","path":"SDWebImage/Core/SDWebImageDownloaderRequestModifier.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e986771283dce17b4fb9bd4b5fe643ed3b9","path":"SDWebImage/Core/SDWebImageDownloaderRequestModifier.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98ae78c144f7dd1ffa4151c834e1856244","path":"SDWebImage/Core/SDWebImageDownloaderResponseModifier.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98cd959e9548caf1d3b10d572543028967","path":"SDWebImage/Core/SDWebImageDownloaderResponseModifier.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e981f2f1b5fb7e7a0259d587693c70e17dc","path":"SDWebImage/Core/SDWebImageError.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98d7b5236ded30e2d2a5ff450a32f7bd62","path":"SDWebImage/Core/SDWebImageError.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98db171e7f43254466428cd7d160d4bc66","path":"SDWebImage/Core/SDWebImageIndicator.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e988059612109acc8adb78283f1ba5f6c8e","path":"SDWebImage/Core/SDWebImageIndicator.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e989a4f6041648958d4aff810b46b83ec9d","path":"SDWebImage/Core/SDWebImageManager.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e985ec38f9cb9f5d4978c0311d1008e04cc","path":"SDWebImage/Core/SDWebImageManager.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e982c27b087e8cd840abc3ef59829b80efa","path":"SDWebImage/Core/SDWebImageOperation.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e989ab8963206759569d84f30727fa4cba2","path":"SDWebImage/Core/SDWebImageOperation.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98b72cfadd9fa624c843ceaca86f14fba7","path":"SDWebImage/Core/SDWebImageOptionsProcessor.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98b5e7d24f1e11dbac304e87bddaff0bab","path":"SDWebImage/Core/SDWebImageOptionsProcessor.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e982a13b238680429b9cb2b8bf0839d5f79","path":"SDWebImage/Core/SDWebImagePrefetcher.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e985fdfabd035ec7a467b035a8ce350edd5","path":"SDWebImage/Core/SDWebImagePrefetcher.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e987fbe1b7021e7099d47e3d29bb0d246b0","path":"SDWebImage/Core/SDWebImageTransition.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98d898bb0c82f19d5c90770a13e1992bc4","path":"SDWebImage/Core/SDWebImageTransition.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98e46d3f1b4958c657b86076222797d146","path":"SDWebImage/Private/SDWebImageTransitionInternal.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e989b6a95fea066801733ba0d9eb7781a8d","path":"SDWebImage/Core/UIButton+WebCache.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98a7d08c8d1537b994f19071149f068bb7","path":"SDWebImage/Core/UIButton+WebCache.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9845d7911932c79e171dd089fd4628b5b8","path":"SDWebImage/Private/UIColor+SDHexString.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98008deca9be811cf53f205b0ddcc4983a","path":"SDWebImage/Private/UIColor+SDHexString.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e986bb62f370bbe234b6bd0d7c77da71782","path":"SDWebImage/Core/UIImage+ExtendedCacheData.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98aa1127430a2307c0512ba13f3695fe9e","path":"SDWebImage/Core/UIImage+ExtendedCacheData.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98ccd7d7396bf3843012af71140da2a49c","path":"SDWebImage/Core/UIImage+ForceDecode.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9863a4e0085b4eca2b418099edfec4340a","path":"SDWebImage/Core/UIImage+ForceDecode.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98b5c221940cf71976a9792c00ded2a77c","path":"SDWebImage/Core/UIImage+GIF.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98d05fd41d05faa2360aeaf46fb5065514","path":"SDWebImage/Core/UIImage+GIF.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98662eda270b9081b770cb1f2a02e387b2","path":"SDWebImage/Core/UIImage+MemoryCacheCost.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98fd8dd526fbbf1c35d004173853ebc817","path":"SDWebImage/Core/UIImage+MemoryCacheCost.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e986357ed01b566bb1d1be0954a632e35e0","path":"SDWebImage/Core/UIImage+Metadata.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e986a263fb34dde4d01069f152b021094c7","path":"SDWebImage/Core/UIImage+Metadata.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98deca6db8fdc22a81efbb6cd75f7c9144","path":"SDWebImage/Core/UIImage+MultiFormat.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e981f8ce8389cf620585da7cd0d5ae68c6d","path":"SDWebImage/Core/UIImage+MultiFormat.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98f79a5a836cf1d081150335f28fe35761","path":"SDWebImage/Core/UIImage+Transform.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98c1367cac0329507a751b00959f8b1fe9","path":"SDWebImage/Core/UIImage+Transform.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9836fad0c1818f3ebd7ea94ecdb2cda58b","path":"SDWebImage/Core/UIImageView+HighlightedWebCache.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98e869a02fe4a657b9ddb0f148af11b090","path":"SDWebImage/Core/UIImageView+HighlightedWebCache.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98fc1f6b2f5b528239f7eb374a16707dd2","path":"SDWebImage/Core/UIImageView+WebCache.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e987a0efb32908f89fd6eaf93a60386fe37","path":"SDWebImage/Core/UIImageView+WebCache.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98d835b0cd54e8e5c3eefa756ca6a8cf4f","path":"SDWebImage/Core/UIView+WebCache.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98da978efa2c20cf80e6a593952a64f214","path":"SDWebImage/Core/UIView+WebCache.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e982999b5735c33706130ed303f0efe7372","path":"SDWebImage/Core/UIView+WebCacheOperation.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e983963a49bc38ac28ca13543440440738c","path":"SDWebImage/Core/UIView+WebCacheOperation.m","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e986e6118052c889416524a66863865f2d2","name":"Core","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e98b569f5963060c97f2f2ba38385fe0047","path":"SDWebImage.modulemap","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98e582145da4c7bad45fd0fea722620980","path":"SDWebImage-dummy.m","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e98f69556d85c791f9f7bb4864067304e9e","path":"SDWebImage-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98a5d7cf445f11b8a851603791bb80dccd","path":"SDWebImage-prefix.pch","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9849c916368f02890a1ae21d78a5082dce","path":"SDWebImage-umbrella.h","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e98cbf68d1f922bc4817004dd6a2700f369","path":"SDWebImage.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e982a978c9504e7f820a9a62749b4639eb7","path":"SDWebImage.release.xcconfig","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98c5c4f2160b2c64c56cc35e1563f69dfa","name":"Support Files","path":"../Target Support Files/SDWebImage","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e989ab633a406a0ba749c8183ca48f70ca1","name":"SDWebImage","path":"SDWebImage","sourceTree":"","type":"group"},{"children":[{"children":[{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98198cb7323cf38f49c099a19af424e38e","path":"Sources/Swift/Metrics/BucketsMetricsAggregator.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9820b096789e7bd1bfa4b58fa13bc5b50f","path":"Sources/Swift/Metrics/CounterMetric.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98853fa02d04d0430ad3f2d7b333874af4","path":"Sources/Swift/Metrics/DistributionMetric.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98ac0780da839edf981809953008673060","path":"Sources/Swift/Metrics/EncodeMetrics.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e982bd4f7e001b4dd1cf079308ca514554b","path":"Sources/Swift/Metrics/GaugeMetric.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9824d6ee3483a76d2eb18b2c33833c5c8a","path":"Sources/Swift/Tools/HTTPHeaderSanitizer.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98382df2b05839d9ea3f03c148af3ae692","path":"Sources/Swift/Metrics/LocalMetricsAggregator.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e981b4b3945f043de125e7b8c8aaeacc7c3","path":"Sources/Swift/Metrics/Metric.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9830b09011eccca71f62b2f66238867f76","path":"Sources/Swift/Metrics/MetricsAggregator.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98aa15c1b3d311498677dcd94b75280f47","path":"Sources/Sentry/include/NSArray+SentrySanitize.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e982ad87d7afe24ffd2d6bd213296aea811","path":"Sources/Sentry/NSArray+SentrySanitize.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98f4b859c713f2344c6fc1477b46cbafeb","path":"Sources/Sentry/include/NSLocale+Sentry.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e987f7b4c8e450d6bc934891b572509dd5c","path":"Sources/Sentry/NSLocale+Sentry.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98b9346543c1b98901f22f3c7e23aff838","path":"Sources/Swift/Extensions/NSLock.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e989b00bcbca3893520fc4f39ff18d0b1f5","path":"Sources/Sentry/include/NSMutableDictionary+Sentry.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98902ebd6036a41aaacac357ee5ccce1b9","path":"Sources/Sentry/NSMutableDictionary+Sentry.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9885b82b46b9dd005b768f8ef3c751040f","path":"Sources/Swift/Extensions/NumberExtensions.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98a716e75ccb528744d3ffde348c74054d","path":"Sources/Sentry/include/HybridPublic/PrivateSentrySDKOnly.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.cpp.objcpp","guid":"bfdfe7dc352907fc980b868725387e9810ac796ebca563e59fec2d96f029829c","path":"Sources/Sentry/PrivateSentrySDKOnly.mm","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e980ed4d648fc835701dd40045ac4f0eb42","path":"Sources/Sentry/include/HybridPublic/PrivatesHeader.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9825d3d80b5d5f47d8c3577be50d208f17","path":"Sources/Sentry/Public/Sentry.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9881e78b26fce5be69601e8f39debf1815","path":"Sources/Sentry/include/SentryANRTracker.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e988933b4bc3332ec51f502704c510ed48f","path":"Sources/Sentry/SentryANRTracker.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98c4aec8c696347d8a5fd3da2c9ae3b34b","path":"Sources/Sentry/include/SentryANRTrackerV2.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98143f06533f29ffe1f4e62e249ee7c61a","path":"Sources/Sentry/SentryANRTrackerV2.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9805b65a9fd5a17f8804adb0010a2bb994","path":"Sources/Swift/Integrations/ANR/SentryANRTrackerV2Delegate.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98a9377495765c6b05f4938e987beaecc4","path":"Sources/Sentry/include/SentryANRTrackingIntegration.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9803a9b0b910a8e69ce5d48707b2f7b0ff","path":"Sources/Sentry/SentryANRTrackingIntegration.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9853bb97ef1af68a2e8a6cd65845220a21","path":"Sources/Sentry/include/SentryANRTrackingIntegrationV2.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98f85156689aeaf18e00084edae9890433","path":"Sources/Sentry/SentryANRTrackingIntegrationV2.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e982f3eaf81b6a498c005395bd74ff9b40c","path":"Sources/Sentry/include/HybridPublic/SentryAppStartMeasurement.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98573cf1fba6ec7158ac9e5d30872d3358","path":"Sources/Sentry/SentryAppStartMeasurement.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98da6d3a4aa155bc2b99c1c5776fa1d7ab","path":"Sources/Sentry/include/SentryAppStartTracker.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9858a59bcb56abe22c97af586957612f8a","path":"Sources/Sentry/SentryAppStartTracker.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9897bfe385980cf04d6052b9a9a48626bd","path":"Sources/Sentry/include/SentryAppStartTrackingIntegration.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e984c7e16723e7d6c5950d220f08a0f3a1b","path":"Sources/Sentry/SentryAppStartTrackingIntegration.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98711c5fa9ee620eb44232227aea5b4980","path":"Sources/Sentry/include/SentryAppState.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98e102ca54129581ea6ac1fd19ca2f900d","path":"Sources/Sentry/SentryAppState.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98f4aff5850320b2892171d2f9050e54f0","path":"Sources/Sentry/include/SentryAppStateManager.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98945bba539ff28910dddfef800d776212","path":"Sources/Sentry/SentryAppStateManager.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98c7abc40702780507f67178f200c91e33","path":"Sources/Sentry/include/SentryAsynchronousOperation.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98f2c93c0f5d5274544f463c3dd147b69e","path":"Sources/Sentry/SentryAsynchronousOperation.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e9848c4b2580de96341fc0bfc5e6780e93a","path":"Sources/Sentry/SentryAsyncSafeLog.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98503f56b704c70eeeaa87f393ca606f46","path":"Sources/Sentry/SentryAsyncSafeLog.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9839b12ad72defb9e70cd9c490eab0f56b","path":"Sources/Sentry/Public/SentryAttachment.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9817625a85b70840a0b0718c986516528e","path":"Sources/Sentry/SentryAttachment.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98999febd8a8da745b5ea4843c406440a6","path":"Sources/Sentry/include/SentryAttachment+Private.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e980c19c6b2f6ae9fa9cbfb42827276a528","path":"Sources/Sentry/include/SentryAutoBreadcrumbTrackingIntegration.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e982375cf366eb9fa65b8bba986be274ede","path":"Sources/Sentry/SentryAutoBreadcrumbTrackingIntegration.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e986e0e69f1089a15308ed825159b9e98f7","path":"Sources/Sentry/include/SentryAutoSessionTrackingIntegration.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98a4037e5e9e7feba04419a743f2be779b","path":"Sources/Sentry/SentryAutoSessionTrackingIntegration.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.cpp.cpp","guid":"bfdfe7dc352907fc980b868725387e981de7f79c30c8772c5fa58d8ea6d1b1b6","path":"Sources/Sentry/SentryBacktrace.cpp","sourceTree":"","type":"file"},{"fileType":"sourcecode.cpp.h","guid":"bfdfe7dc352907fc980b868725387e9891ba718e9d3cff3ab8d9bfee8bd0462b","path":"Sources/Sentry/include/SentryBacktrace.hpp","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e989f61aa00b8a1516208dca7f64cd6a75c","path":"Sources/Sentry/Public/SentryBaggage.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98b20f7baef1201383ed42eb1c42b1ef2a","path":"Sources/Sentry/SentryBaggage.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e983e4f391d969af2e5ce4e2e2dd4a41dfb","path":"Sources/Swift/Helper/SentryBaggageSerialization.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e985a62059eff00244af5c87c6ddd480877","path":"Sources/Sentry/include/SentryBaseIntegration.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e987a9f6073313425fbba5130148b9b3a65","path":"Sources/Sentry/SentryBaseIntegration.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98ccba6be3c8cd08597e295bd9856dd7e3","path":"Sources/Sentry/include/HybridPublic/SentryBinaryImageCache.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98963cbdfa2428d743619bf4f2c4cdf206","path":"Sources/Sentry/SentryBinaryImageCache.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98963224f46996165eb8c62129394cfe81","path":"Sources/Sentry/Public/SentryBreadcrumb.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98e6b4f1738c9e7b78c11959faf476bb67","path":"Sources/Sentry/SentryBreadcrumb.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98647433b4e086798b973e947311127f7c","path":"Sources/Sentry/include/HybridPublic/SentryBreadcrumb+Private.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98b337f47392c6e73cb95920efdda0b675","path":"Sources/Sentry/include/SentryBreadcrumbDelegate.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98fd297ac79b8c53f88805194c653eb324","path":"Sources/Sentry/include/SentryBreadcrumbTracker.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98917b84e096fb7a3cee094b304c0d1f1e","path":"Sources/Sentry/SentryBreadcrumbTracker.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e987869ca5f51cd67f10fb06cf6d2a072e1","path":"Sources/Sentry/include/SentryBuildAppStartSpans.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98b16fdba02a928575106b9d1ac1686733","path":"Sources/Sentry/SentryBuildAppStartSpans.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9899a99bf57fcbf0c333b505bdb2e414cc","path":"Sources/Sentry/include/SentryByteCountFormatter.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98e29dfdbdcbf120e148ffa7d4a4c248a4","path":"Sources/Sentry/SentryByteCountFormatter.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98be06b9e44be8b3f68cdabea1eab410fb","path":"Sources/Sentry/Public/SentryClient.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e981f87b3406b001d130316dbc8ada38a00","path":"Sources/Sentry/SentryClient.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e987b01f905126992881fc2dce8d8608866","path":"Sources/Sentry/include/SentryClient+Private.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e987f499c7f3f52d80336b1b533a1394782","path":"Sources/Sentry/include/SentryClientReport.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98a4dc6d699fd7d93e90a761eda46c4f64","path":"Sources/Sentry/SentryClientReport.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9830ba7417bb270438177deb73683c5536","path":"Sources/Sentry/include/SentryCompiler.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e985f16ffcf4dfbc88b10ce30fd3e633708","path":"Sources/Sentry/include/SentryConcurrentRateLimitsDictionary.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98869ae30e47af8e56237caf99320cf284","path":"Sources/Sentry/SentryConcurrentRateLimitsDictionary.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98f66f182cfe191dbc0994d7db36647922","path":"Sources/Sentry/include/SentryContinuousProfiler.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.cpp.objcpp","guid":"bfdfe7dc352907fc980b868725387e98fe03e1800d75ed025acc7c9f643b8f04","path":"Sources/Sentry/Profiling/SentryContinuousProfiler.mm","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9860162861b3210d454479d7e1ac71d658","path":"Sources/Sentry/include/SentryCoreDataSwizzling.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9828efb6a05eb4ef1519bb559204168266","path":"Sources/Sentry/SentryCoreDataSwizzling.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9813f1af6eada9adbb72a6632c8d2b2829","path":"Sources/Sentry/include/SentryCoreDataTracker.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e980e9d7ced294ae24069ba05792c403f21","path":"Sources/Sentry/SentryCoreDataTracker.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98373aa59d4a768c9ebed68430e50f9fd4","path":"Sources/Sentry/include/SentryCoreDataTrackingIntegration.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98d656120ee1d06ad362116f5cd2c4da69","path":"Sources/Sentry/SentryCoreDataTrackingIntegration.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98971753585da08da03da7583249217f27","path":"Sources/Sentry/include/SentryCPU.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98e71ce65934ced440e8087b210d26e9dd","path":"Sources/SentryCrash/Recording/SentryCrash.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9810ab6f5523c4427b61de387291c7a269","path":"Sources/SentryCrash/Recording/SentryCrash.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e983e0adaf7be093e8acf73fd7ca3cd8020","path":"Sources/SentryCrash/Recording/SentryCrashBinaryImageCache.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e986c59b016150adc6042a09462cf5f2949","path":"Sources/SentryCrash/Recording/SentryCrashBinaryImageCache.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98ba2cf898e20c56ff809039433c33cfd0","path":"Sources/Sentry/include/SentryCrashBinaryImageProvider.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e982f6e693d7a2699f92a735b00a3b748ec","path":"Sources/SentryCrash/Recording/SentryCrashC.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e988880cf5ad48383745a739a9820534574","path":"Sources/SentryCrash/Recording/SentryCrashC.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e981c394d73a0449af8d0295e4f249392e4","path":"Sources/SentryCrash/Recording/SentryCrashCachedData.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e984943986cd47c7fbf04e945d0f7ce23dc","path":"Sources/SentryCrash/Recording/SentryCrashCachedData.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e985af7183f551d475ec21df7099e52ba19","path":"Sources/SentryCrash/Recording/Tools/SentryCrashCPU.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9819e75d68bf8cdd116d83607aa0c87f45","path":"Sources/SentryCrash/Recording/Tools/SentryCrashCPU.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98a8b934d46e8ace1a43b18243a1180d46","path":"Sources/SentryCrash/Recording/Tools/SentryCrashCPU_Apple.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e986e6252e39b90f3ce76d83e5d6c3974b2","path":"Sources/SentryCrash/Recording/Tools/SentryCrashCPU_arm.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e98a418c37e4b8f0cae47ee4e0ef8a63b52","path":"Sources/SentryCrash/Recording/Tools/SentryCrashCPU_arm64.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e9854f64306da17dca29bd7ff5a1c475bbe","path":"Sources/SentryCrash/Recording/Tools/SentryCrashCPU_x86_32.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e9858b9351f994445a2cced9788b61b0857","path":"Sources/SentryCrash/Recording/Tools/SentryCrashCPU_x86_64.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e9815c5e7deb3496c383b03ad2cb8c63c54","path":"Sources/SentryCrash/Recording/Tools/SentryCrashDate.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98920a4695f60aba5f7a2d55e19953a7c3","path":"Sources/SentryCrash/Recording/Tools/SentryCrashDate.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e982e916154b2f4a8ee629c521943af2be5","path":"Sources/SentryCrash/Recording/Tools/SentryCrashDebug.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98fa25002e2a2a304eb735465f026645f3","path":"Sources/SentryCrash/Recording/Tools/SentryCrashDebug.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9862a562ca672b78057b80f0d6b1381746","path":"Sources/Sentry/include/SentryCrashDefaultBinaryImageProvider.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e986d3ae0f245877d1a34a61568db103431","path":"Sources/Sentry/SentryCrashDefaultBinaryImageProvider.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e989f539a8dd7e053b0be13bac966b57726","path":"Sources/Sentry/include/SentryCrashDefaultMachineContextWrapper.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98834119e584475e9bbe8dde34e6124eed","path":"Sources/Sentry/SentryCrashDefaultMachineContextWrapper.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e987c323ed9eabfe9ca185b337c9308d491","path":"Sources/SentryCrash/Recording/SentryCrashDoctor.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98e86a5d681f8907d2cfc0b4615cae7aa6","path":"Sources/SentryCrash/Recording/SentryCrashDoctor.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e98872202ab332a20c50d416271f9d2372a","path":"Sources/SentryCrash/Recording/Tools/SentryCrashDynamicLinker.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98ba5fbfcc911db7d4ae22629456fd9144","path":"Sources/SentryCrash/Recording/Tools/SentryCrashDynamicLinker.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98328deee97d6c62d2e5b511634b98f69a","path":"Sources/Sentry/Public/SentryCrashExceptionApplication.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98ced46e42add5fe763f983e5bb41852eb","path":"Sources/Sentry/SentryCrashExceptionApplication.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e9851df5069bd644d351f378d5a24e99531","path":"Sources/SentryCrash/Recording/Tools/SentryCrashFileUtils.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98ee672a6996837477d96028b68c1168d1","path":"Sources/SentryCrash/Recording/Tools/SentryCrashFileUtils.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e9884856fe388c064570227a322157015ee","path":"Sources/SentryCrash/Recording/Tools/SentryCrashID.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9895df3868e47b3489c27cba402f91a418","path":"Sources/SentryCrash/Recording/Tools/SentryCrashID.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98eafd27653478c04bf42d5a09c9475684","path":"Sources/SentryCrash/Installations/SentryCrashInstallation.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98785810b1ca7d160b372c70ed55188bdb","path":"Sources/SentryCrash/Installations/SentryCrashInstallation.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98789ed62e82862651822eb9e968b4183b","path":"Sources/SentryCrash/Installations/SentryCrashInstallation+Private.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e987b19143bc7df2526be6323a84f13d0cd","path":"Sources/Sentry/include/SentryCrashInstallationReporter.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9843bd4e1a8c710f4388f1611823aa8703","path":"Sources/Sentry/SentryCrashInstallationReporter.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98fa237d860d80a5137ce637c70f1d45ae","path":"Sources/Sentry/include/SentryCrashIntegration.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e984f9135a1bacbd9b7d700d830329cfa18","path":"Sources/Sentry/SentryCrashIntegration.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98ed40eb6a8415d9209bc656e3e9003d20","path":"Sources/Sentry/include/SentryCrashIsAppImage.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e983ace9e6326466c6006d7903e835f2298","path":"Sources/SentryCrash/Recording/Tools/SentryCrashJSONCodec.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e984db0ba7ef8f9c62a4768e1244e0175a5","path":"Sources/SentryCrash/Recording/Tools/SentryCrashJSONCodec.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e980949e62d558d1222c68ac80103de072a","path":"Sources/SentryCrash/Recording/Tools/SentryCrashJSONCodecObjC.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98fb551adfbd11861983f13d4ea4ac5672","path":"Sources/SentryCrash/Recording/Tools/SentryCrashJSONCodecObjC.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e98ebe01a592e2d14b2852a1fb5fbd2aec6","path":"Sources/SentryCrash/Recording/Tools/SentryCrashMach.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98e63f4dd5624ba45cbd9550973bc7a64b","path":"Sources/SentryCrash/Recording/Tools/SentryCrashMach.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e98ac0ec3dc837141d75865164b98357241","path":"Sources/SentryCrash/Recording/Tools/SentryCrashMachineContext.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e982d7b304e56d6074933295f9cbe624568","path":"Sources/SentryCrash/Recording/Tools/SentryCrashMachineContext.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98b5dcb1a00343dc6a7015edc3f087c60a","path":"Sources/SentryCrash/Recording/Tools/SentryCrashMachineContext_Apple.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9890a37d86794ca14855675168cd3383ad","path":"Sources/Sentry/include/SentryCrashMachineContextWrapper.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e982bc91af71f89cf0a1a1788830d8adeba","path":"Sources/SentryCrash/Recording/Tools/SentryCrashMemory.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98bad5c89792bfd17e382bb695ad8ed3f7","path":"Sources/SentryCrash/Recording/Tools/SentryCrashMemory.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e98cb3e637b4b9c0fd20f7ea80210d8b3c8","path":"Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98f6864c5d37412b769927b5d5c74fe602","path":"Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e981c4aa5985300ab845f08441631c136e0","path":"Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_AppState.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e986db33830a858c9794d31e24b7b5cdb20","path":"Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_AppState.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.cpp.cpp","guid":"bfdfe7dc352907fc980b868725387e98928789958ea08294471edb78122163bf","path":"Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_CPPException.cpp","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98d88503a787c4bef8d3f64a16f8ac7431","path":"Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_CPPException.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e985631641a5bf876e0a4464325cd89f9f0","path":"Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_MachException.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98f9cccecbb59fac96ef00765633935089","path":"Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_MachException.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e981f0f66e36982c6f19b8196b5549c1151","path":"Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_NSException.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9854c3e488a79f2baa48bf1bde77c46cd4","path":"Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_NSException.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e98fc0ee9ce543fb5029fa8d77a33a2a927","path":"Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_Signal.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98ef5045f7ebe869b4e0ded07a2e0b854f","path":"Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_Signal.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e986d521257b90b2beb37d3cbeb2b852f91","path":"Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_System.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e989b790d6fb8d01f7933ddbca6b6c2e119","path":"Sources/SentryCrash/Recording/Monitors/SentryCrashMonitor_System.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98b15723d60c434030589a9c72cc466027","path":"Sources/SentryCrash/Recording/Monitors/SentryCrashMonitorContext.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e984023b76db5ad50719b293030bedfa158","path":"Sources/SentryCrash/Recording/Monitors/SentryCrashMonitorType.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98e0d4db171ade52ab9810b83e9e46027a","path":"Sources/SentryCrash/Recording/Monitors/SentryCrashMonitorType.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9858f76db325762543559f151e9cde7092","path":"Sources/SentryCrash/Recording/Tools/SentryCrashNSErrorUtil.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98036351eb99bc61418fa7904beb886a4b","path":"Sources/SentryCrash/Recording/Tools/SentryCrashNSErrorUtil.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e98ec8643356b925433575de2c29645e403","path":"Sources/SentryCrash/Recording/Tools/SentryCrashObjC.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98d43045f94dfb5f8084c33ff0795a4fb3","path":"Sources/SentryCrash/Recording/Tools/SentryCrashObjC.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e983ff24760fdf3ff3241a74acaab8abd66","path":"Sources/SentryCrash/Recording/Tools/SentryCrashObjCApple.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98e52be506e681f88f6f6ca68d2f91cc4e","path":"Sources/SentryCrash/Recording/Tools/SentryCrashPlatformSpecificDefines.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e98ebb3d6360d98f69e909523c8039f5805","path":"Sources/SentryCrash/Recording/SentryCrashReport.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9882eca2c2727b3da70dc9991523763b33","path":"Sources/SentryCrash/Recording/SentryCrashReport.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9857e1affb989c53e044956e4452fdbcea","path":"Sources/Sentry/include/SentryCrashReportConverter.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e982deeb46c4817dcdcd9ac3ff4d08481d4","path":"Sources/Sentry/SentryCrashReportConverter.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e982aad48539d3cdacf06eba4d8e46a2584","path":"Sources/SentryCrash/Recording/SentryCrashReportFields.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e981e9d85b65beaeba8501a1c62b8febe5b","path":"Sources/SentryCrash/Reporting/Filters/SentryCrashReportFilter.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e980d8271e53cbfcf85f6cd065e708f2de2","path":"Sources/SentryCrash/Reporting/Filters/SentryCrashReportFilterBasic.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9840d849ddebfb811a2974576cbaeecf80","path":"Sources/SentryCrash/Reporting/Filters/SentryCrashReportFilterBasic.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e98a9d5de5d3dfe92644037d71ee5190757","path":"Sources/SentryCrash/Recording/SentryCrashReportFixer.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98f69b87a8cdb3abcbac109f20e35935f7","path":"Sources/SentryCrash/Recording/SentryCrashReportFixer.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9875fc97b0b53f66ab6b047f69319bae1f","path":"Sources/Sentry/include/SentryCrashReportSink.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98f0c689d1974688e39dbfb4912fa84a98","path":"Sources/Sentry/SentryCrashReportSink.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e9896c2e55b5af11c135ee2e941bdd0d7f7","path":"Sources/SentryCrash/Recording/SentryCrashReportStore.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9837c90d38984770cab39b73a4ada41ded","path":"Sources/SentryCrash/Recording/SentryCrashReportStore.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98f92815bb8d5e910d80c83c261ef2665a","path":"Sources/SentryCrash/Recording/SentryCrashReportVersion.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e982b8d6627048f9dc97cf8b1ea4105f91b","path":"Sources/SentryCrash/Recording/SentryCrashReportWriter.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98d078af0694b1dfb7d9ccde999b44e4fb","path":"Sources/Sentry/include/SentryCrashScopeObserver.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98078b4b6983c2612f332996e0802c4c78","path":"Sources/Sentry/SentryCrashScopeObserver.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e98fc0f9f2e8fb1eb0f1596f10d50e88f2f","path":"Sources/SentryCrash/Recording/Tools/SentryCrashSignalInfo.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e983af198c0f2730501f9ce1047fe9febf5","path":"Sources/SentryCrash/Recording/Tools/SentryCrashSignalInfo.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e982f50e0ec35d87b0e7665d862c74ad244","path":"Sources/SentryCrash/Recording/Tools/SentryCrashStackCursor.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e980ab8a94243d149d379bbd54e70a367d2","path":"Sources/SentryCrash/Recording/Tools/SentryCrashStackCursor.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e989255de6ce3a4e6431e337169ea12941c","path":"Sources/SentryCrash/Recording/Tools/SentryCrashStackCursor_Backtrace.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98227f0ab7c55a7b2a8c57667847371c5c","path":"Sources/SentryCrash/Recording/Tools/SentryCrashStackCursor_Backtrace.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e981ded049e9550dae6bfea340e4044795f","path":"Sources/SentryCrash/Recording/Tools/SentryCrashStackCursor_MachineContext.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e985481f9043d0f6eec88a8931509d27ca7","path":"Sources/SentryCrash/Recording/Tools/SentryCrashStackCursor_MachineContext.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98172b9a0d48d2f67d576708e2c15c5681","path":"Sources/SentryCrash/Recording/Tools/SentryCrashStackCursor_SelfThread.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e980331a56274b42b435802d99adc0f0030","path":"Sources/SentryCrash/Recording/Tools/SentryCrashStackCursor_SelfThread.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98616e193ad92d875887b6acd5f702b997","path":"Sources/Sentry/include/SentryCrashStackEntryMapper.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98fcbe81bbd35a2c2ad89d2e20c97dcc80","path":"Sources/Sentry/SentryCrashStackEntryMapper.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e98cac8c3e4cc76215e854ea8e9fd0b4fbd","path":"Sources/SentryCrash/Recording/Tools/SentryCrashString.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e982bc1b6754e56222800d995cd75f8c2bb","path":"Sources/SentryCrash/Recording/Tools/SentryCrashString.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e98e542917dc6ed22f0dc8928e137282679","path":"Sources/SentryCrash/Recording/Tools/SentryCrashSymbolicator.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9842aa558befabfc401dc6c66d86529e41","path":"Sources/SentryCrash/Recording/Tools/SentryCrashSymbolicator.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e9805e8aec9ac44b4f557bd1c2c0ed33d7e","path":"Sources/SentryCrash/Recording/Tools/SentryCrashSysCtl.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e987d038ee2ee6d13d961dcc8f8cef0e8f9","path":"Sources/SentryCrash/Recording/Tools/SentryCrashSysCtl.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e9814ac97981888d650562ae1699fbed050","path":"Sources/SentryCrash/Recording/Tools/SentryCrashThread.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e983761e96361f55650fcff3ec415c306c0","path":"Sources/SentryCrash/Recording/Tools/SentryCrashThread.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e98cf8824f371b9de403b95fafde5e63a58","path":"Sources/SentryCrash/Recording/Tools/SentryCrashUUIDConversion.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9824542f73d2e98c60530e9c276356c21d","path":"Sources/SentryCrash/Recording/Tools/SentryCrashUUIDConversion.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98f8bc45a2584bffecf7c9393d70e3ac1b","path":"Sources/SentryCrash/Reporting/Filters/Tools/SentryCrashVarArgs.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98d8def6fa7b0dff52e0288684681cd235","path":"Sources/Sentry/include/SentryCrashWrapper.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98844dfbd5d2a8a41ef38196b8c228710e","path":"Sources/Sentry/SentryCrashWrapper.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98beaf5cf161791d03e65fca4463133522","path":"Sources/Swift/Helper/SentryCurrentDateProvider.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9820c8946792aadcff4c6b7911cd3509b1","path":"Sources/Sentry/include/SentryDataCategory.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9897ecba504c62f71a990fd084fdd0aa08","path":"Sources/Sentry/include/SentryDataCategoryMapper.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98993a135e0861037578f65e78f6f19fa5","path":"Sources/Sentry/SentryDataCategoryMapper.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98fb2c2fb737cef61e3aa32bc173200734","path":"Sources/Sentry/include/SentryDateUtil.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e980da3c3a8a5a8e75a2b495f25b864ffff","path":"Sources/Sentry/SentryDateUtil.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e982cfb6141f563ef3b224e37c23dfd6ee7","path":"Sources/Sentry/include/SentryDateUtils.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e981e501be4e9e4ce930febba0a67b92f83","path":"Sources/Sentry/SentryDateUtils.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e986de643aa7e4d68dd257a682eb4ecf094","path":"Sources/Sentry/Public/SentryDebugImageProvider.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e988714d874b4c7d300f8820a9568aac3b9","path":"Sources/Sentry/SentryDebugImageProvider.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98ccd67327f2cef046d47a060a50e2b033","path":"Sources/Sentry/Public/SentryDebugMeta.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98d62e6ab2a2d7467600f6feb7e2671a52","path":"Sources/Sentry/SentryDebugMeta.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98057cb7e081eab9c13bc45a99e2046c64","path":"Sources/Sentry/include/SentryDefaultObjCRuntimeWrapper.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98755befdde596ed98608c0f49e465f2e9","path":"Sources/Sentry/SentryDefaultObjCRuntimeWrapper.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e987fb8b09d68a8675e669ccaa2b3d70f8b","path":"Sources/Sentry/include/SentryDefaultRateLimits.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98284e598b5703764339c9c4dd2d39c047","path":"Sources/Sentry/SentryDefaultRateLimits.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98a8b66c1f8b8fe6afdb3bd5083ac798dd","path":"Sources/Sentry/Public/SentryDefines.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e982b9ddd57b7d448702427527f15078729","path":"Sources/Sentry/include/SentryDelayedFrame.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98b80297ad046a9e2ee2fc01b38f886866","path":"Sources/Sentry/SentryDelayedFrame.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98c933c980d9fd2d6b18ab7660be826a32","path":"Sources/Sentry/include/SentryDelayedFramesTracker.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9883f0c58fdda69214b888d7a89386438d","path":"Sources/Sentry/SentryDelayedFramesTracker.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e988e3fca574186ef9a3fbf60c7936614dd","path":"Sources/Sentry/include/HybridPublic/SentryDependencyContainer.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e988e075ecfc8e39865eebcf99287896107","path":"Sources/Sentry/SentryDependencyContainer.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98ab472310b921cb357b3403f16d6bcc91","path":"Sources/Sentry/include/SentryDevice.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.cpp.objcpp","guid":"bfdfe7dc352907fc980b868725387e98ce8faf244cb4f73da113703dbb0fd493","path":"Sources/Sentry/SentryDevice.mm","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98f496c08b830fbf3baf100455494f579e","path":"Sources/SentryCrash/Reporting/Filters/Tools/SentryDictionaryDeepSearch.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98ed54e2f0091c3b6700b4882495308ca7","path":"Sources/SentryCrash/Reporting/Filters/Tools/SentryDictionaryDeepSearch.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98c50f7a6b19aff7f4ca9db7611b185ffc","path":"Sources/Sentry/include/SentryDiscardedEvent.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e982b532fff8e4b1e30be209e64ad5f2328","path":"Sources/Sentry/SentryDiscardedEvent.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e989c9e6c345c44f79bfb78251b2dbb8c7b","path":"Sources/Sentry/include/SentryDiscardReason.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98c183e6d53e8117f062c3f4b7cd156530","path":"Sources/Sentry/include/SentryDiscardReasonMapper.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e986c8a3f333ed0b95751e7fbf78d7c98b9","path":"Sources/Sentry/SentryDiscardReasonMapper.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98bba374cd8c0dc6a0b88cd9da50d55613","path":"Sources/Sentry/include/SentryDispatchFactory.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98b61addc447754e7e5ec64e70a8a8d043","path":"Sources/Sentry/SentryDispatchFactory.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98181ef37baebf7620fdb3ed512d9c5b2c","path":"Sources/Sentry/include/SentryDispatchQueueWrapper.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98fdfd3d660615791f5b19cb7a270b3d33","path":"Sources/Sentry/SentryDispatchQueueWrapper.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98edef8192fe71dc5c3aa5e7605d8bc231","path":"Sources/Sentry/include/SentryDispatchSourceWrapper.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98ad912bcb51aa320ac7d9ae0d6b1c29d0","path":"Sources/Sentry/SentryDispatchSourceWrapper.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e985d9e7a648cec7e7fc0ad3cf75672ea68","path":"Sources/Sentry/include/SentryDisplayLinkWrapper.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98cebef9f2b8583ad43ec280bc3f601765","path":"Sources/Sentry/include/SentryDisplayLinkWrapper.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98eb4e19f7af34fa2f92906307734634ab","path":"Sources/Sentry/Public/SentryDsn.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98bc58b953a2bf19f0ba887c35a848c43d","path":"Sources/Sentry/SentryDsn.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98629100bfbc75470514774b17e16c6ed2","path":"Sources/Swift/Helper/SentryEnabledFeaturesBuilder.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98f5d9dabdb733a787122fe8ba4ca78980","path":"Sources/Sentry/include/HybridPublic/SentryEnvelope.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e989ef35f84679b5286380254cf3782d3fc","path":"Sources/Sentry/SentryEnvelope.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e982792cab6ec953388de02a2ff6935ac57","path":"Sources/Sentry/include/SentryEnvelope+Private.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98f6cedb8fc664cf2a66556a10e0939328","path":"Sources/Sentry/include/SentryEnvelopeAttachmentHeader.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e987e289e99946da7963f37deadbe959fdd","path":"Sources/Sentry/SentryEnvelopeAttachmentHeader.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98141370ca334e2965d382e9f5cee9eb40","path":"Sources/Sentry/Public/SentryEnvelopeItemHeader.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e987942c3b2f4ab61de1e45f7316786b84c","path":"Sources/Sentry/SentryEnvelopeItemHeader.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98781a30887bd009417ad1a4112c2fb3fe","path":"Sources/Sentry/include/HybridPublic/SentryEnvelopeItemType.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9895aa5c265104e54b26110f7557073d7b","path":"Sources/Sentry/include/SentryEnvelopeRateLimit.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98d5a56c7cb96529d8791d765d11773537","path":"Sources/Sentry/SentryEnvelopeRateLimit.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98bafc78f7024b690ecf9471384a9902b4","path":"Sources/Sentry/Public/SentryError.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.cpp.objcpp","guid":"bfdfe7dc352907fc980b868725387e98ca1115e3b61c2229c1c80bd36f06de1b","path":"Sources/Sentry/SentryError.mm","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98413720c3decab33490a7c5172007d190","path":"Sources/Sentry/Public/SentryEvent.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e983743f3d27289e139c0b954aaa4e0628a","path":"Sources/Sentry/SentryEvent.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98a8f47a674006a0bd05b6739dc62b4f4f","path":"Sources/Sentry/include/SentryEvent+Private.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e987159465a79e7a721e0255b49a250409e","path":"Sources/Sentry/Public/SentryException.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9895825e81ff4e9350316aa59835550564","path":"Sources/Sentry/SentryException.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9862683b73d213711a5794050fbedb940e","path":"Sources/Swift/SentryExperimentalOptions.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98a207c835059cd1e43b5179b933097323","path":"Sources/Sentry/SentryExtraContextProvider.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98e15159df27cf33be5cf5b699238e167f","path":"Sources/Sentry/SentryExtraContextProvider.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98e61558513e2bb03c67587242c1ae2320","path":"Sources/Swift/Helper/SentryFileContents.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9891743b33ff8051d960b0188178ff8d18","path":"Sources/Sentry/include/SentryFileIOTrackingIntegration.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9811f125cb4ea42b0ee92dca25f4b88671","path":"Sources/Sentry/SentryFileIOTrackingIntegration.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98c2c1ff1a63d933d631b4428ce2e38740","path":"Sources/Sentry/include/SentryFileManager.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e987dc757a9b6a393b594b0e7f493805852","path":"Sources/Sentry/SentryFileManager.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98c2ef2bd076cd4dd059f7e298bf852014","path":"Sources/Sentry/include/HybridPublic/SentryFormatter.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9852b362bca7cc0c9e4f0ac4357b8926c2","path":"Sources/Sentry/Public/SentryFrame.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98cb0c8f2e66fc06982ca30a2cf3eb9bfc","path":"Sources/Sentry/SentryFrame.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e981a64e72c9b8c3c97fd22013edcdae5ca","path":"Sources/Sentry/include/SentryFrameRemover.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9887e6e078e6301374b2fef621090a7c44","path":"Sources/Sentry/SentryFrameRemover.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98dba5b9e2f053ff10581a7a52a1336881","path":"Sources/Swift/Integrations/FramesTracking/SentryFramesDelayResult.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e980aa82f1dc751a60b99dfdc678c1d417c","path":"Sources/Sentry/include/HybridPublic/SentryFramesTracker.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9851e95210c8e69b336ec75546cb826a45","path":"Sources/Sentry/SentryFramesTracker.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98efaa72ef40eaa86dff924769099fe63f","path":"Sources/Sentry/include/SentryFramesTrackingIntegration.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98732b4c467888ee6e063ecfd31db00fc1","path":"Sources/Sentry/SentryFramesTrackingIntegration.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9891ed9d2029d00cffa36a1f5fc06398a1","path":"Sources/Sentry/Public/SentryGeo.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e988d6d449e12ef4dade75902629ee9c5e5","path":"Sources/Sentry/SentryGeo.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98ca8e4cce263f1f47c0f162e4c586c4f8","path":"Sources/Sentry/include/SentryGlobalEventProcessor.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98ed988e9fdf3783f895274a6a759ea0e0","path":"Sources/Sentry/SentryGlobalEventProcessor.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e986e3e378fc6f7cc204b68b3caa7236f27","path":"Sources/Sentry/include/SentryHttpDateParser.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98a9f385e05164f46553c2ee2cf481b7d4","path":"Sources/Sentry/SentryHttpDateParser.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98c3bfc35223f1b5b6943049d2fb6c4389","path":"Sources/Sentry/Public/SentryHttpStatusCodeRange.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98768627ada7e32a0631bc8af27cb1d1ff","path":"Sources/Sentry/SentryHttpStatusCodeRange.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98cca6fefe9cb49705f068665278f8ac02","path":"Sources/Sentry/include/SentryHttpStatusCodeRange+Private.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e989de0a39761e8a1050f2257cbe8713a04","path":"Sources/Sentry/include/SentryHttpTransport.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e985ee2ceb1a7cef57f920d5af9053a5d61","path":"Sources/Sentry/SentryHttpTransport.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98836f5531819312d8b361c85d8e8b18a4","path":"Sources/Sentry/Public/SentryHub.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98252ac7b4e2a6feb7cd60fdd1a4f09dc4","path":"Sources/Sentry/SentryHub.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98638a129b104a232f59a5551902b1f3f4","path":"Sources/Sentry/include/SentryHub+Private.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98118a67a7239494acc31abf04261df084","path":"Sources/Swift/Protocol/SentryId.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98070046bf0c41b80e5921e2353abd1714","path":"Sources/Sentry/include/SentryInAppLogic.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98c06ead7e0dbabb3e7c310df0e456432b","path":"Sources/Sentry/SentryInAppLogic.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98e38ea514f750e6ef90ab207cb16028c2","path":"Sources/Sentry/include/SentryInstallation.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e981b188529a60da3e88d8e87a3cef822ea","path":"Sources/Sentry/SentryInstallation.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98f1ecb074aa05d438550e8283fcf8840e","path":"Sources/Swift/Protocol/SentryIntegrationProtocol.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9880707aac6a0b662d864e53db8ec3e873","path":"Sources/Sentry/include/SentryInternalCDefines.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98b48ba94d494b08c87d61b93a678e3dd2","path":"Sources/Sentry/include/SentryInternalDefines.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9827d2c767a3050b20c39203308cb87f8d","path":"Sources/Sentry/include/SentryInternalNotificationNames.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9849c7220f6bfe0eeb615b9a7accf8b533","path":"Sources/Sentry/include/SentryInternalSerializable.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98def9cc150a565d2550979ec5b69998b1","path":"Sources/Sentry/include/SentryLaunchProfiling.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98911fdc6061a2135c748153f2e4a05aa3","path":"Sources/Sentry/Profiling/SentryLaunchProfiling.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e989df9dc056cea8fcf0f59af8f85cd8282","path":"Sources/Swift/Helper/Log/SentryLevel.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e983c8f81c7f1106715ce45bc09d968a4cb","path":"Sources/Sentry/include/SentryLevelHelper.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9827103d0e236a2917fac90b75c47f4fec","path":"Sources/Sentry/SentryLevelHelper.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98844dd3ba156013068dbf7f35651a5957","path":"Sources/Sentry/include/SentryLevelMapper.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e986b5d8de5042f9b6c6debab299d006332","path":"Sources/Sentry/SentryLevelMapper.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e988d673b72b0119aacb08d60c172d07c94","path":"Sources/Sentry/include/SentryLog.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9828d4fd172c6650cc894dc077f2713654","path":"Sources/Swift/Tools/SentryLog.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98c9238e73552f603c4ec002a1af34b21c","path":"Sources/Sentry/include/SentryLogC.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e980bbcb909a9d03a552f1277782e18761a","path":"Sources/Sentry/SentryLogC.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e986b40c6c48b55db5046d6db511942fa38","path":"Sources/Swift/Tools/SentryLogOutput.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.cpp.cpp","guid":"bfdfe7dc352907fc980b868725387e9849d90f9a6a3c8d0dad49a3899b83cec1","path":"Sources/Sentry/SentryMachLogging.cpp","sourceTree":"","type":"file"},{"fileType":"sourcecode.cpp.h","guid":"bfdfe7dc352907fc980b868725387e9800ab9a3ecaf9fb561465a8384a1a4232","path":"Sources/Sentry/include/SentryMachLogging.hpp","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e982d535deadc12989119b0615b9dd28f36","path":"Sources/Sentry/Public/SentryMeasurementUnit.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98d114189b8a76ae18f56bcfc4aaa7ea75","path":"Sources/Sentry/SentryMeasurementUnit.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e980ca01cfbb872cb4259cd1e35227961de","path":"Sources/Sentry/include/SentryMeasurementValue.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e987666c44d8e4bd63cee7a88e33191a7e6","path":"Sources/Sentry/SentryMeasurementValue.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9851202d61adfe91c437af0f1c67e03d04","path":"Sources/Sentry/Public/SentryMechanism.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98e451424b1cdbc8b40cd309ecd4bbbf52","path":"Sources/Sentry/SentryMechanism.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98bab24acd4702a28a3c4c9063e4ef0dce","path":"Sources/Sentry/Public/SentryMechanismMeta.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e982968cc739e708fdcb3738f171b8efd43","path":"Sources/Sentry/SentryMechanismMeta.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9830744347f699402adbc18f50caf4e2d7","path":"Sources/Sentry/Public/SentryMessage.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e981082286186bf8efb07f5f2ce82e71bfc","path":"Sources/Sentry/SentryMessage.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9899ce478e31a9826614e13502b4400b26","path":"Sources/Sentry/include/SentryMeta.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e980d00aea86836a212dca15301d0f6be80","path":"Sources/Sentry/SentryMeta.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98ead309d87cf3d8ca9c2bc918c7485105","path":"Sources/Sentry/include/SentryMetricKitIntegration.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e982f522d156c7a74cefb6fc138d7ed9eea","path":"Sources/Sentry/SentryMetricKitIntegration.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e987cd2cb4adcd0f896a6a9aa250d475f99","path":"Sources/Sentry/include/SentryMetricProfiler.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.cpp.objcpp","guid":"bfdfe7dc352907fc980b868725387e9869f27a6b12c3ecdb44b5cf7f02588341","path":"Sources/Sentry/SentryMetricProfiler.mm","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e986ce9c2df801c62045926d8864e84a2a1","path":"Sources/Swift/Metrics/SentryMetricsAPI.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e981e970cc41f9443b9aec9727b6001ea4c","path":"Sources/Swift/Metrics/SentryMetricsClient.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e982f7d0a3333cb2dcfcbe9c5afd9ec20f5","path":"Sources/Sentry/include/SentryMigrateSessionInit.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98c0abc314e5dda9b6db74ec0670b9817b","path":"Sources/Sentry/SentryMigrateSessionInit.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e984f295a6e7dc5aee192e374504ed7fe03","path":"Sources/Sentry/include/SentryMsgPackSerializer.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9886c992067744c43e9998edd72ad5e859","path":"Sources/Sentry/SentryMsgPackSerializer.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98571b885c8f8846fcc4fd4db493b7816d","path":"Sources/Swift/MetricKit/SentryMXCallStackTree.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e987aff5927f8aad996cb87fb760614dbfd","path":"Sources/Swift/MetricKit/SentryMXManager.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98e6c11cbc922ee1e497e67191309e6292","path":"Sources/Sentry/include/SentryNetworkTracker.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9845b3a6e7f24267ede88073dada9a10f1","path":"Sources/Sentry/SentryNetworkTracker.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98940d6066dd7b5b655ab91100255b0082","path":"Sources/Sentry/include/SentryNetworkTrackingIntegration.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98e7ed27e60fec75e818894a08259699a4","path":"Sources/Sentry/SentryNetworkTrackingIntegration.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e983581727c987fa4cca0954306818b12b7","path":"Sources/Sentry/include/SentryNoOpSpan.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9805654afa1f82958dd293c561ac9ab271","path":"Sources/Sentry/SentryNoOpSpan.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9833e522abc1d5b99c6068adbf830967c2","path":"Sources/Sentry/include/SentryNSDataSwizzling.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9833178f1b00328efc29f49ca21f6fa919","path":"Sources/Sentry/SentryNSDataSwizzling.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98a32f1dc5a94f948c465e56c4010cf7e2","path":"Sources/Sentry/include/SentryNSDataTracker.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e984d57188a0ae1e5a026a97e63d1e37dd4","path":"Sources/Sentry/SentryNSDataTracker.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e987e9ef0b68a6618bff633d8257bbe4807","path":"Sources/Sentry/include/SentryNSDataUtils.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9846770a46bcf2b781d57ce3c925c8d341","path":"Sources/Sentry/SentryNSDataUtils.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e981b65e1d892bcf2bcb3c5f20ec5972415","path":"Sources/Sentry/include/SentryNSDictionarySanitize.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e986c8f20ef0729cf34cac9cba109e20051","path":"Sources/Sentry/SentryNSDictionarySanitize.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e983337f11aeab3a289f5c8c19c3908b921","path":"Sources/Sentry/Public/SentryNSError.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e984e9914a4b4293c692a90ba05b97965b5","path":"Sources/Sentry/SentryNSError.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9880ca6abb4fff5d7995dc2123497283ad","path":"Sources/Sentry/include/SentryNSNotificationCenterWrapper.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e986f2b065fb1bca703459a4977185b8d05","path":"Sources/Sentry/SentryNSNotificationCenterWrapper.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98663c96b18dbadd2be6f08f5b8ea31c5e","path":"Sources/Sentry/include/SentryNSProcessInfoWrapper.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.cpp.objcpp","guid":"bfdfe7dc352907fc980b868725387e9894ae4b1a56d92b5ab6ed329af307db93","path":"Sources/Sentry/SentryNSProcessInfoWrapper.mm","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9878f37430fba46c9dd937dbfac8cf691c","path":"Sources/Sentry/include/SentryNSTimerFactory.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98692460fbec7a54719df44466b20bcd96","path":"Sources/Sentry/SentryNSTimerFactory.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98680d2dc7ef64f30c303c18b4117568fc","path":"Sources/Sentry/include/SentryNSURLRequest.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98e288c4e7a426b08b04b780e40e1f743f","path":"Sources/Sentry/SentryNSURLRequest.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98c1a99b0cf864330bb01057e2bffc60fc","path":"Sources/Sentry/include/SentryNSURLRequestBuilder.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98d4217e23e64d40642d3a033d561f8d35","path":"Sources/Sentry/SentryNSURLRequestBuilder.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e989354f19ee7a39a29a3b6520194111f66","path":"Sources/Sentry/include/SentryNSURLSessionTaskSearch.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e985cf2ec8edccb99946f64c83878c3c803","path":"Sources/Sentry/SentryNSURLSessionTaskSearch.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98537f34ec2613ecc57890ea95d1c7af35","path":"Sources/Sentry/include/SentryObjCRuntimeWrapper.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e987bcb2b2868ee46b3d212b5e9a35106d4","path":"Sources/Swift/Integrations/SessionReplay/SentryOnDemandReplay.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e989e50a47e4c378c019942525106400715","path":"Sources/Sentry/Public/SentryOptions.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98063d68f2c40f62f6810d0ed406bb382e","path":"Sources/Sentry/SentryOptions.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98eeec8be2c9ebaa5285c5472c3b36bf2e","path":"Sources/Sentry/include/HybridPublic/SentryOptions+HybridSDKs.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e984bba6bc40fc60e061c7795ed9d14710d","path":"Sources/Sentry/include/SentryOptions+Private.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e984efbed9b76890e16efc0580a6c7f5776","path":"Sources/Sentry/include/SentryPerformanceTracker.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e989a15e09aff4d797481eb78d448d68934","path":"Sources/Sentry/SentryPerformanceTracker.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e981abbc71e31b4628810dbd6cd1779a6ab","path":"Sources/Sentry/include/SentryPerformanceTrackingIntegration.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98d4b7a5dd39a2fec7b7ddc0ee48570ad5","path":"Sources/Sentry/SentryPerformanceTrackingIntegration.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e988b84a4cbbf82db3ee0783089fc6f3bba","path":"Sources/Swift/Integrations/SessionReplay/SentryPixelBuffer.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9855c159eb37097297bb381315fef4af83","path":"Sources/Sentry/include/SentryPredicateDescriptor.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98d4338a3e8437f3b1d1acce2c48ddc4fb","path":"Sources/Sentry/SentryPredicateDescriptor.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98cb932407441bd3d3ca9e06a6841edf04","path":"Sources/Sentry/include/SentryPrivate.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98a5d56cb9cee92ba68a856453c30f353d","path":"Sources/Sentry/include/SentryProfiledTracerConcurrency.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.cpp.objcpp","guid":"bfdfe7dc352907fc980b868725387e987291ecb593ccdba0e5fc6a98cbf5fdae","path":"Sources/Sentry/Profiling/SentryProfiledTracerConcurrency.mm","sourceTree":"","type":"file"},{"fileType":"sourcecode.cpp.objcpp","guid":"bfdfe7dc352907fc980b868725387e989733858144eb7e903d25bb39b2aa1d69","path":"Sources/Sentry/SentryProfiler.mm","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98023f426c062e3f41cac6b9ff2674eb7c","path":"Sources/Sentry/include/SentryProfiler+Private.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e981591d1e000f7e36a693757459cc651de","path":"Sources/Sentry/Profiling/SentryProfilerDefines.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9834c19d0343e8b716ae4009d27cd5343f","path":"Sources/Sentry/include/SentryProfilerSerialization.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.cpp.objcpp","guid":"bfdfe7dc352907fc980b868725387e98e9464d6a322ac39a174f4614fab171f4","path":"Sources/Sentry/Profiling/SentryProfilerSerialization.mm","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e986d6c0901f1b81ea1582095356892c849","path":"Sources/Sentry/Profiling/SentryProfilerSerialization+Test.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9849170326f9e7324f758fad5c8fda7c26","path":"Sources/Sentry/include/SentryProfilerState.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.cpp.objcpp","guid":"bfdfe7dc352907fc980b868725387e988624d29c0763de3bcc218c7c4acf5c0e","path":"Sources/Sentry/Profiling/SentryProfilerState.mm","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98c97c721f87e21f23b35f49ab3480ddd9","path":"Sources/Sentry/include/SentryProfilerState+ObjCpp.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9803b8fbdce4e2ed80e49b511afbfdf7ba","path":"Sources/Sentry/include/SentryProfilerTestHelpers.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9857b858be807d990227d8cc8a467799eb","path":"Sources/Sentry/Profiling/SentryProfilerTestHelpers.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98d57d7ff8850a7f68bc5127a2de78e1c6","path":"Sources/Sentry/include/SentryProfileTimeseries.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.cpp.objcpp","guid":"bfdfe7dc352907fc980b868725387e98cf09f39b101036d9039a5e9fdd5d4910","path":"Sources/Sentry/SentryProfileTimeseries.mm","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e980fa47d49e2bb65216878780f3a37d5f1","path":"Sources/Sentry/Public/SentryProfilingConditionals.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98dbbe1e35eb18685486a5c8b6577bbbe9","path":"Sources/Sentry/SentryPropagationContext.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9888a2658ee08672b053eb8cc3642a4e76","path":"Sources/Sentry/SentryPropagationContext.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e989bdf26ad09027d76fb370df8816c6eb9","path":"Sources/Sentry/include/SentryQueueableRequestManager.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98c2d294613ba90e126869f812c1ab6325","path":"Sources/Sentry/SentryQueueableRequestManager.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e983454e6f421a0345f33e633ea310f2406","path":"Sources/Sentry/include/SentryRandom.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e980f3118093a755d081ed6850275016cf3","path":"Sources/Sentry/SentryRandom.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98a60f227949bd23fe3b07ff7b9601fc22","path":"Sources/Sentry/include/SentryRateLimitParser.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98fe5ac5ab93a3da03e0e408b697c6a079","path":"Sources/Sentry/SentryRateLimitParser.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e989d3798a7794bb34165683bfa0463151d","path":"Sources/Sentry/include/SentryRateLimits.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e986a01eacf4fd9c5cbbfa18985456424be","path":"Sources/Sentry/include/SentryReachability.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98d3daca73881c4a4b4874c14922476273","path":"Sources/Sentry/SentryReachability.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98b22a5ba1237fd87037d77d3641902363","path":"Sources/Swift/Protocol/SentryRedactOptions.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9842a47bed079d580cd20989b9075294e3","path":"Sources/Swift/Integrations/SessionReplay/SentryReplayEvent.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e985726026a1a1c31e50325233986a3b382","path":"Sources/Swift/Integrations/SessionReplay/SentryReplayOptions.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9805701d4fccb49684aa1b55a60f18fb3c","path":"Sources/Swift/Integrations/SessionReplay/SentryReplayRecording.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e989cc077d015a9f88874f161283b85d1af","path":"Sources/Swift/Integrations/SessionReplay/SentryReplayType.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9857bc555ad325fec19f683147a099c7e1","path":"Sources/Swift/Integrations/SessionReplay/SentryReplayVideoMaker.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98de5de5142f58314b7d12a2e669c41266","path":"Sources/Sentry/Public/SentryRequest.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9871b012a3b5f1462b9d197c7012741e34","path":"Sources/Sentry/SentryRequest.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e985df69af6f60d3c7f31989a95e3f3a9e8","path":"Sources/Sentry/include/SentryRequestManager.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e987657341f336ac02acfcf382525f1509c","path":"Sources/Sentry/include/SentryRequestOperation.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98d263d257b6329d0a2dfee2e60a97fb5c","path":"Sources/Sentry/SentryRequestOperation.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e987942c8ac2f032c0940a6a38ab91bf734","path":"Sources/Sentry/include/SentryRetryAfterHeaderParser.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98ac9e44632d815a3c81252e6452f71035","path":"Sources/Sentry/SentryRetryAfterHeaderParser.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98f50457f417b0f79a0e1b0de834cf9aca","path":"Sources/Swift/Integrations/SessionReplay/RRWeb/SentryRRWebBreadcrumbEvent.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98cb2f125ee7e524b3c4b954ba30af5d66","path":"Sources/Swift/Integrations/SessionReplay/RRWeb/SentryRRWebCustomEvent.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e988d52ddcc81c8bca32b6adcfe81c6b681","path":"Sources/Swift/Integrations/SessionReplay/RRWeb/SentryRRWebEvent.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98078862f44140b3e99c42e7d1ab12699b","path":"Sources/Swift/Integrations/SessionReplay/RRWeb/SentryRRWebMetaEvent.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9846929a8c54eb68f7ae47aba553065cd4","path":"Sources/Swift/Integrations/SessionReplay/RRWeb/SentryRRWebSpanEvent.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98fbbaa747f79a3331a947c4a688bc013a","path":"Sources/Swift/Integrations/SessionReplay/RRWeb/SentryRRWebTouchEvent.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98cb215688c9705038d49294cb6576f234","path":"Sources/Swift/Integrations/SessionReplay/RRWeb/SentryRRWebVideoEvent.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98cf48b342ad07ad2abb993872033b47d0","path":"Sources/Sentry/include/SentrySample.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e983cab1af79491ca23a25ea7c74d671896","path":"Sources/Sentry/Profiling/SentrySample.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e984d00b5598fb7c97a8153da8d65037dfa","path":"Sources/Sentry/Public/SentrySampleDecision.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e986c69e66e55629c2c42ad5d27ff70b570","path":"Sources/Sentry/SentrySampleDecision.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e981142043015590766f312d36cdd419e39","path":"Sources/Sentry/include/SentrySampleDecision+Private.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e987eb65cfe85a754fabdf367fc17bec2d2","path":"Sources/Sentry/include/SentrySamplerDecision.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9894d865a928ae3975ad2746f118b4468b","path":"Sources/Sentry/SentrySamplerDecision.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98ea213eb10687e172d1d3aded3bd41ae7","path":"Sources/Sentry/include/SentrySampling.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98662bd827119a1617f1274adce996caf7","path":"Sources/Sentry/SentrySampling.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e987d47f3af45086a117a1cc24b2df9365a","path":"Sources/Sentry/Public/SentrySamplingContext.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e989ff077c3356757e30d11f375da1ea1eb","path":"Sources/Sentry/SentrySamplingContext.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.cpp.cpp","guid":"bfdfe7dc352907fc980b868725387e981a82de91e8f2e5e3932cf0c7756ee711","path":"Sources/Sentry/SentrySamplingProfiler.cpp","sourceTree":"","type":"file"},{"fileType":"sourcecode.cpp.h","guid":"bfdfe7dc352907fc980b868725387e985cd5b426c8a926a07fa8d251d5dab624","path":"Sources/Sentry/include/SentrySamplingProfiler.hpp","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e988185ba6a88b996493cdf38ca379baabb","path":"Sources/Sentry/Public/SentryScope.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98a51dc90ee182f3f234e31a6c3f115461","path":"Sources/Sentry/SentryScope.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98918a5c81a8e0b096accec29a8a6d8bb3","path":"Sources/Sentry/include/SentryScope+Private.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98e441058a2a5fac727c3f9be0e75a7e8e","path":"Sources/Sentry/include/SentryScopeObserver.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e98ff6e8cbde607cb070aaf521c95fb961a","path":"Sources/Sentry/SentryScopeSyncC.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9881d0f7b202c7e8a98e9936b29655c95d","path":"Sources/Sentry/include/SentryScopeSyncC.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e989a369f24a3d33c353bfec1e7f64b3ea6","path":"Sources/Sentry/include/HybridPublic/SentryScreenFrames.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98e46fe301c2548cc68a7e96ce8366b59a","path":"Sources/Sentry/SentryScreenFrames.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98d09812498914ddbf97a6798491555ab6","path":"Sources/Sentry/include/SentryScreenshot.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98e04f95ec1867f216e77231ddd1dda094","path":"Sources/Sentry/SentryScreenshot.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98b1ff561b93fc4c5e149438256f956387","path":"Sources/Sentry/include/SentryScreenshotIntegration.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e989e9f3ce88297e823291af52c5ad53475","path":"Sources/Sentry/SentryScreenshotIntegration.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9826acbc0b304b2a0ad2003d840178f0ff","path":"Sources/Sentry/Public/SentrySDK.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e980e9d69924caebf6884c96f9ce74bc334","path":"Sources/Sentry/SentrySDK.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98c7d38ced97e72f78e89610670b1b3fb3","path":"Sources/Sentry/include/SentrySDK+Private.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e984d5ae9898138c992b2021f68ec8babf6","path":"Sources/Sentry/include/SentrySdkInfo.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98faa847346efca28ffffb35376ea3a952","path":"Sources/Sentry/SentrySdkInfo.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98cf616d9f6cd818fe3348d29c0c916aa2","path":"Sources/Sentry/Public/SentrySerializable.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98e1dd28873beb5b2dcb1af5beb95d271b","path":"Sources/Sentry/include/SentrySerialization.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e980d6f259ec16188737b09287bd283b367","path":"Sources/Sentry/SentrySerialization.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98f0fafebc44b49d7ff8414673d3cef1c9","path":"Sources/Sentry/include/SentrySession.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e980b8b57442bb9cab2603b9a999081af38","path":"Sources/Sentry/SentrySession.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e982116f2e7bb2c8e2ac20df990e0f81236","path":"Sources/Sentry/include/SentrySession+Private.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e983c8c985666c5352b6af3c7433239a020","path":"Sources/Sentry/include/SentrySessionCrashedHandler.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98b12e7dd603ab333ae8345872f96182a8","path":"Sources/Sentry/SentrySessionCrashedHandler.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98928b60c2da7f7825875efa439d557e6b","path":"Sources/Swift/Protocol/SentrySessionListener.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9828b59ccaf8ff87aa8c8c271b9b5bf184","path":"Sources/Swift/Integrations/SessionReplay/SentrySessionReplay.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98aed94b002bc11a182b8826fa79b3c5e5","path":"Sources/Sentry/include/SentrySessionReplayIntegration.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98fa40304186e2c99ee5ccbfc8d92800fc","path":"Sources/Sentry/SentrySessionReplayIntegration.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e987cc66d7b02be2cb3d51c5934a525009a","path":"Sources/Sentry/include/SentrySessionReplayIntegration+Private.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98ada9c823043bb19207c45e2346ff5a6a","path":"Sources/Sentry/include/HybridPublic/SentrySessionReplayIntegration-Hybrid.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.c","guid":"bfdfe7dc352907fc980b868725387e9894dee7e19fea06bda4dbaaf5c1d9d35b","path":"Sources/Sentry/SentrySessionReplaySyncC.c","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9864e08ba6ae71bf1d1c7b269fd8a8fe0f","path":"Sources/Sentry/include/SentrySessionReplaySyncC.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98e28c0aa12b28173b3abf435b1fa19a34","path":"Sources/Sentry/include/SentrySessionTracker.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98c0c10fbaa1cb5e98abbbef20d36e7b41","path":"Sources/Sentry/SentrySessionTracker.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98c6bf18b7cbf57a94d6c0d96399d10e5c","path":"Sources/Sentry/include/SentrySpan.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98d09bbbc9887ced326bec2d6d7246dce1","path":"Sources/Sentry/SentrySpan.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98f21c40f0c349752b7f501e01c22fa48b","path":"Sources/Sentry/include/SentrySpan+Private.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98ecbbe5eadcd9001b4c2fc21e078f887a","path":"Sources/Sentry/Public/SentrySpanContext.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e980572088b35705ae125400e5eaf1ca5c0","path":"Sources/Sentry/SentrySpanContext.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9853c456af0a27c58300581e4f45771983","path":"Sources/Sentry/include/SentrySpanContext+Private.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98c593d7d98c929c6a2b0f7d4d0589a1d4","path":"Sources/Sentry/Public/SentrySpanId.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9826f4fe2fca7fe75078c0e9e4e65a2d02","path":"Sources/Sentry/SentrySpanId.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e980a3fbb743bd3ac9e28a18b2bd756f025","path":"Sources/Sentry/include/SentrySpanOperations.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9871bde8593bddf6bbbc9f5418fd2e33c4","path":"Sources/Sentry/Public/SentrySpanProtocol.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9878c897661c68cc4ff7868fa62493291e","path":"Sources/Sentry/Public/SentrySpanStatus.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98c18eedf9441788560deccc91341f4ef1","path":"Sources/Sentry/SentrySpanStatus.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98786a47c67a0dc6ec0050a48c5a5e4e3d","path":"Sources/Sentry/include/SentrySpotlightTransport.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98571366b1f9a87519f69133aa94ea1592","path":"Sources/Sentry/SentrySpotlightTransport.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e985987b898ae3639d21a5bfb374115d528","path":"Sources/Swift/Integrations/SessionReplay/SentrySRDefaultBreadcrumbConverter.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.cpp.h","guid":"bfdfe7dc352907fc980b868725387e98a1f775d7fcfd5f7d435fe7abffb91fb3","path":"Sources/Sentry/include/SentryStackBounds.hpp","sourceTree":"","type":"file"},{"fileType":"sourcecode.cpp.h","guid":"bfdfe7dc352907fc980b868725387e98ea3cc80c8121c0454193a096db3b0641","path":"Sources/Sentry/include/SentryStackFrame.hpp","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e988ebb06d6eb20ea09e0d5cce625106b6e","path":"Sources/Sentry/Public/SentryStacktrace.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9803eaef84f8cf151765e8f2dacff23bae","path":"Sources/Sentry/SentryStacktrace.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9863ffcb1f013b44034ba0bfda59953036","path":"Sources/Sentry/include/SentryStacktraceBuilder.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e985614a6bc35ea979167d2101aaf82ca72","path":"Sources/Sentry/SentryStacktraceBuilder.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98e70d13d7772d677d98ce5f06ab6a2bc7","path":"Sources/Sentry/include/SentryStatsdClient.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e988a1606defceb762419fa23b4bb125351","path":"Sources/Sentry/SentryStatsdClient.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98e05ba937b597322372e39fc0358edb5f","path":"Sources/Sentry/include/SentrySubClassFinder.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e989c1b2a8ac0bcddb420b0941ef640d0eb","path":"Sources/Sentry/SentrySubClassFinder.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e984d4ee0056f4fbde7672fb570961a8583","path":"Sources/Sentry/include/SentrySwift.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e980bdb04ca4987d86965932ea3417f118b","path":"Sources/Sentry/include/SentrySwiftAsyncIntegration.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98a09e471bf53c0e8d38c894fb2f23deda","path":"Sources/Sentry/SentrySwiftAsyncIntegration.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9864929f6d55b4bf658809924b262e5263","path":"Sources/Sentry/include/HybridPublic/SentrySwizzle.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98d97ee0c09e9e900424b1603d10be7375","path":"Sources/Sentry/SentrySwizzle.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9877c9f1b3633c0e198960f68aa0c0810f","path":"Sources/Sentry/include/SentrySwizzleWrapper.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98711558a1dd411bb478cd26d8f3f745f2","path":"Sources/Sentry/SentrySwizzleWrapper.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98993f9b39cd9919f8ec83374ff13b1328","path":"Sources/Sentry/include/SentrySysctl.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e985deb0edc70afd30a8360f3dc8e856686","path":"Sources/Sentry/SentrySysctl.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98ff36fbd967725834366ae2130fb2bed0","path":"Sources/Sentry/include/SentrySystemEventBreadcrumbs.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98a94a3a2ea869ff4381e8499b0d762c37","path":"Sources/Sentry/SentrySystemEventBreadcrumbs.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e988821b58f8dee309405f48b809d77c389","path":"Sources/Sentry/include/SentrySystemWrapper.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.cpp.objcpp","guid":"bfdfe7dc352907fc980b868725387e98e7b36496f0059bd09052fe0723a1482e","path":"Sources/Sentry/SentrySystemWrapper.mm","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9846f8b71aa0d77e70aad6baf2780ede40","path":"Sources/Sentry/Public/SentryThread.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98333f641f00d2f2094339ae17ba284755","path":"Sources/Sentry/SentryThread.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.cpp.cpp","guid":"bfdfe7dc352907fc980b868725387e98f983405d6088b8487df98cbf1cf08289","path":"Sources/Sentry/SentryThreadHandle.cpp","sourceTree":"","type":"file"},{"fileType":"sourcecode.cpp.h","guid":"bfdfe7dc352907fc980b868725387e9804a7db4ceccb6811912f7206f78e9447","path":"Sources/Sentry/include/SentryThreadHandle.hpp","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9862bc8fb6ad96b53f2b26b863c155ac1e","path":"Sources/Sentry/include/SentryThreadInspector.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9880761a78208e56b6a4707d7016a455cc","path":"Sources/Sentry/SentryThreadInspector.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.cpp.cpp","guid":"bfdfe7dc352907fc980b868725387e98bebf1492e8f631b28d50d71933f1372b","path":"Sources/Sentry/SentryThreadMetadataCache.cpp","sourceTree":"","type":"file"},{"fileType":"sourcecode.cpp.h","guid":"bfdfe7dc352907fc980b868725387e9826a9f2e728b361a2543a40d1abedebd0","path":"Sources/Sentry/include/SentryThreadMetadataCache.hpp","sourceTree":"","type":"file"},{"fileType":"sourcecode.cpp.h","guid":"bfdfe7dc352907fc980b868725387e98706ef18fd5b8c82cc56ff430d4229cb2","path":"Sources/Sentry/include/SentryThreadState.hpp","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e980755b34b65dc8dfadeb1feee1a3cf958","path":"Sources/Sentry/include/SentryThreadWrapper.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e980b6ae641edc0eb8755e4ed4a2488c062","path":"Sources/Sentry/SentryThreadWrapper.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98da71b3b7edd671b3d5c2cd01db469838","path":"Sources/Sentry/include/SentryTime.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.cpp.objcpp","guid":"bfdfe7dc352907fc980b868725387e989800de374e76adb4a26b0bcddeecc2fd","path":"Sources/Sentry/SentryTime.mm","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e980cb9271b7f81c1a2d6b0f37452893839","path":"Sources/Sentry/include/SentryTimeToDisplayTracker.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98c7ad08268e0d328f29de3b10cd63ddc2","path":"Sources/Sentry/SentryTimeToDisplayTracker.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e982a75db7baa0413a63ae20e85adb1d22b","path":"Sources/Swift/Integrations/SessionReplay/SentryTouchTracker.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98a2a1ef82a610bc27d79d0c54c67af7f0","path":"Sources/Sentry/Public/SentryTraceContext.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9887cc63e57392bb0beca2e79621425813","path":"Sources/Sentry/SentryTraceContext.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9892f3522efd5a3f19de8e39f523c688d6","path":"Sources/Sentry/Public/SentryTraceHeader.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98df898a62101ed695fd27677ba8483559","path":"Sources/Sentry/SentryTraceHeader.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98c6f2a07c2d050533d09cf4c390de1f8c","path":"Sources/Sentry/include/SentryTraceOrigins.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98b7d38aba29d59ffea50820df6a97c3d1","path":"Sources/Sentry/include/SentryTraceProfiler.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.cpp.objcpp","guid":"bfdfe7dc352907fc980b868725387e98d882e4234aaaf8e0069c816fdd59f579","path":"Sources/Sentry/Profiling/SentryTraceProfiler.mm","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e981398c4eb84cdf497790fb1fe607e2e63","path":"Sources/Sentry/include/SentryTracer.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e982bf9fe4d6f2326a7fccee18a75faa388","path":"Sources/Sentry/SentryTracer.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98872ffad4b49352b651b261a73dad0060","path":"Sources/Sentry/include/SentryTracer+Private.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9810cd37e42899add31fcc33f4bb739aac","path":"Sources/Sentry/include/SentryTracerConfiguration.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e986e709c30b562ffcb2b1eda4a0ad426d4","path":"Sources/Sentry/SentryTracerConfiguration.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9866a291b84196da3f36541b841f97bd0e","path":"Sources/Sentry/include/SentryTransaction.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98221eaf92859927540a5d4291e050542e","path":"Sources/Sentry/SentryTransaction.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98da808eab441d9e75217ff3d80a5e32a4","path":"Sources/Sentry/Public/SentryTransactionContext.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.cpp.objcpp","guid":"bfdfe7dc352907fc980b868725387e9841ff7a58c398688fb8514f02d4c41c49","path":"Sources/Sentry/SentryTransactionContext.mm","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98cb1d9988a5b4400c251fe18f3b1dd801","path":"Sources/Sentry/include/SentryTransactionContext+Private.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9836194029038246f4ec8bf1410be089cc","path":"Sources/Swift/Integrations/Performance/SentryTransactionNameSource.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98e2f9ec322d655c0ea0950183b52221d8","path":"Sources/Sentry/include/SentryTransport.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98d6a32bf0e617b57a20d2c33c46eb7914","path":"Sources/Sentry/include/SentryTransportAdapter.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e984be035c6e10d36e66be434b1c21a073b","path":"Sources/Sentry/SentryTransportAdapter.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98954fa85d06420d5ec52380486fd5cb33","path":"Sources/Sentry/include/SentryTransportFactory.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98b676ca7f4497f4d1fe4138d1f5e18cf7","path":"Sources/Sentry/SentryTransportFactory.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e986a376fd26c1eb7097e69832b6f02467c","path":"Sources/Sentry/include/SentryUIApplication.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98ad8cadda5d19b22d7d5e391f3f2e5773","path":"Sources/Sentry/SentryUIApplication.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9894a25cc56626f69559a458a15ac02efd","path":"Sources/Sentry/include/SentryUIDeviceWrapper.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e983ef575e6594a639f646ed7e15b622f05","path":"Sources/Sentry/SentryUIDeviceWrapper.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98d16e83a31b866dde2772c9ce54293036","path":"Sources/Sentry/include/SentryUIEventTracker.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98c7648303d42528f7384cd7e96635cb91","path":"Sources/Sentry/SentryUIEventTracker.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98727154a288bb63516c37f191b7f989fc","path":"Sources/Sentry/include/SentryUIEventTrackerMode.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98ec06dab8b33576c79bb7f8e7c442836d","path":"Sources/Sentry/include/SentryUIEventTrackerTransactionMode.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98e4854f9e24894360225810635de170d4","path":"Sources/Sentry/SentryUIEventTrackerTransactionMode.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98521d00d65862188e87f09af5fdd2b143","path":"Sources/Sentry/include/SentryUIEventTrackingIntegration.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98c9a689aa4dad67a6b1ca280f2f6ebea4","path":"Sources/Sentry/SentryUIEventTrackingIntegration.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e980e3a99429d2074eae3ed0f01f406b861","path":"Sources/Sentry/include/SentryUIViewControllerPerformanceTracker.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e9845042217e88518e3e855ac6610a6bd28","path":"Sources/Sentry/SentryUIViewControllerPerformanceTracker.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9889e3af4a95fcd5340aa42b761c61304c","path":"Sources/Sentry/include/SentryUIViewControllerSwizzling.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e983176fb557a25d7b21f81988edd0f03bb","path":"Sources/Sentry/SentryUIViewControllerSwizzling.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9893fe513507e3ed2aee8e215050e2e0d7","path":"Sources/Sentry/Public/SentryUser.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e981599944e2dac3b6e693aef6d13475bc3","path":"Sources/Sentry/SentryUser.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98306548f7ff648626ef748253421486b8","path":"Sources/Sentry/include/HybridPublic/SentryUser+Private.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e983d67248f1449eda03bef4a9ab05fe6a4","path":"Sources/Sentry/Public/SentryUserFeedback.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98aea6546754779f273d8124b565989345","path":"Sources/Sentry/SentryUserFeedback.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9821d0b241774df222e3b7377348f85f63","path":"Sources/Swift/Integrations/SessionReplay/SentryVideoInfo.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e980bb1557164f81c9e1bfbb7f8363a7867","path":"Sources/Sentry/include/SentryViewHierarchy.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98299ac7ba68df1b240a6b268ff9a86b76","path":"Sources/Sentry/SentryViewHierarchy.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e980afc7a0cee689b8418b92fe22b0c3615","path":"Sources/Sentry/include/SentryViewHierarchyIntegration.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98b53e903d92672c16fb54d8ff0231cef9","path":"Sources/Sentry/SentryViewHierarchyIntegration.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98817181aab5ecfaf90b5b85e17c49dec8","path":"Sources/Swift/Tools/SentryViewPhotographer.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9889f837ea12a895bd661390ab3127aa6d","path":"Sources/Swift/Tools/SentryViewScreenshotProvider.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98831b777b6c8ddefb76a396274e08cf65","path":"Sources/Sentry/include/SentryWatchdogTerminationLogic.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98018b75d110252e6219e0ee545362ac37","path":"Sources/Sentry/SentryWatchdogTerminationLogic.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e981b4d68ef1663be58c2c7d79a11afa4df","path":"Sources/Sentry/include/SentryWatchdogTerminationScopeObserver.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98364193a20b48ba275110ac3d6d5e9cda","path":"Sources/Sentry/SentryWatchdogTerminationScopeObserver.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98fd01427898ca74956b565fd99d02dc1c","path":"Sources/Sentry/include/SentryWatchdogTerminationTracker.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e987e91816c36c7362ce51491a1a85d80cd","path":"Sources/Sentry/SentryWatchdogTerminationTracker.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98780c1b9a14bc63cecf76b3990a59877b","path":"Sources/Sentry/include/SentryWatchdogTerminationTrackingIntegration.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98491b83da9ba737ee9f38229ad6e420d5","path":"Sources/Sentry/SentryWatchdogTerminationTrackingIntegration.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98ed78e714a1400e941f04e4a22e3d5b2e","path":"Sources/Sentry/Public/SentryWithoutUIKit.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e981fe228c1e3eb9e98e34f508e02afeb4d","path":"Sources/Swift/Metrics/SetMetric.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98fa7dc020d34d966016e6329a66dd8e9f","path":"Sources/Swift/Extensions/StringExtensions.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98d9c9b6d5fc746b0b6e694918a128dda3","path":"Sources/Swift/SwiftDescriptor.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98cb607a81a62d906f3b39c57798758ac5","path":"Sources/Swift/Tools/UIImageHelper.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e988bedd1aa102f8e0c2b086358fe8c4265","path":"Sources/Swift/Tools/UIRedactBuilder.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98199af03d7bbe972473ba296e49e9e28f","path":"Sources/Sentry/include/UIViewController+Sentry.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98c67c9dd02046243d9509ed2cd4798160","path":"Sources/Sentry/UIViewController+Sentry.m","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98a9bdf6e3af1025f342b74ed83538e99f","path":"Sources/Swift/Extensions/UIViewExtensions.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9847561447b26da1538d79830f9f25d534","path":"Sources/Swift/Tools/UrlSanitized.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98e0f7059daf81ae00dbaea4f94fd0940e","path":"Sources/Swift/Tools/URLSessionTaskHelper.swift","sourceTree":"","type":"file"},{"children":[{"fileType":"text.xml","guid":"bfdfe7dc352907fc980b868725387e98720140f27bb1633c64c1b1da36ff7150","path":"Sources/Resources/PrivacyInfo.xcprivacy","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e984e8e4b6b4cdf0c8e787fc62391dd92fc","name":"Resources","path":"","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98fde7c7fa201b545fa7346c2dbc65d01e","name":"HybridSDK","path":"","sourceTree":"","type":"group"},{"children":[{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e98ef1bcc008e87a9a25e802f910bd3b338","path":"ResourceBundle-Sentry-Sentry-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e98379c05280a321bf688329fe3aef3105b","path":"Sentry.modulemap","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98743f2bd1841f8f09decb589fed2636c8","path":"Sentry-dummy.m","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e983b019ef6e78b06a8a837bc6d70936ca6","path":"Sentry-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98a244d1195fc83689d6ce3ffc5e36f6e2","path":"Sentry-prefix.pch","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e983938971700c25e06de2a31eeb4ea8038","path":"Sentry-umbrella.h","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e98008eb440e134dedec2875572805c0b95","path":"Sentry.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e9802292b204c9bad4ac7f8961219de2b07","path":"Sentry.release.xcconfig","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e986fb9ef72a20518718c75331fd4883cdd","name":"Support Files","path":"../Target Support Files/Sentry","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e982359f49a15cf54f5a5642b64cc3eb2e3","name":"Sentry","path":"Sentry","sourceTree":"","type":"group"},{"children":[{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98d16ccbe7206fb0e2ba6426ac4ec38dd9","path":"SwiftyGif/NSImage+SwiftyGif.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9889573bb4021c2391fbff0a825ae4178b","path":"SwiftyGif/NSImageView+SwiftyGif.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e9835fe91271911430f451a39e60aa1986c","path":"SwiftyGif/ObjcAssociatedWeakObject.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98ee06371f21d2935a5ee20d4d5bb4e44f","path":"SwiftyGif/SwiftyGif.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98e7350beaaae8ffbf946713feea0a4648","path":"SwiftyGif/SwiftyGifManager.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98595dbe4b870633814d56f32dc4618746","path":"SwiftyGif/UIImage+SwiftyGif.swift","sourceTree":"","type":"file"},{"fileType":"sourcecode.swift","guid":"bfdfe7dc352907fc980b868725387e98d770d4ce570ae939fbded8f93e00ff8e","path":"SwiftyGif/UIImageView+SwiftyGif.swift","sourceTree":"","type":"file"},{"children":[{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e9875483e8f93a2b38f9b1fe471ba33c3a7","path":"SwiftyGif.modulemap","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98c6490049da5fff353f3196233e5aa86b","path":"SwiftyGif-dummy.m","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e988ad7a0fe1ae6306e9a821e4ed13fff44","path":"SwiftyGif-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e981915be642f44f92bbb0c663859b70dfe","path":"SwiftyGif-prefix.pch","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e980626803cd8030a41b813ca8f28fffea0","path":"SwiftyGif-umbrella.h","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e98ed5df08130fde981e5066c74824ca0ad","path":"SwiftyGif.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e98be3854286ad572fdc485c3f4251ca09f","path":"SwiftyGif.release.xcconfig","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e98810193ceb3c555979788ed2a5c372602","name":"Support Files","path":"../Target Support Files/SwiftyGif","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e980ae2061b06cbc4928ef7854c13f38740","name":"SwiftyGif","path":"SwiftyGif","sourceTree":"","type":"group"},{"children":[{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98632304d423ab2bf91c956eac7a76a9c7","path":"Toast-Framework/Toast.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9890a726412a53fe90ba2295a50dfd22a2","path":"Toast/UIView+Toast.h","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e989752157b2cd16354797296f760ae1aef","path":"Toast/UIView+Toast.m","sourceTree":"","type":"file"},{"children":[{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e989938462c628741b000a566d13b66d51d","path":"Toast.modulemap","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98e3d5685abb47d94d856240e992947bc4","path":"Toast-dummy.m","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e989038dc03edeabc512af644ec374031bf","path":"Toast-Info.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e985032ba3070912dc548c2a9e4e7a902a9","path":"Toast-prefix.pch","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e98c66dfb77b319009b7b9b119ff175df41","path":"Toast-umbrella.h","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e9863fe2c666a46f95fbbe4c0f7414d7d8b","path":"Toast.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e98ca2bbfca057495e362d7fe67afdfd6b9","path":"Toast.release.xcconfig","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e987533233d249f8595cd310d0355eed423","name":"Support Files","path":"../Target Support Files/Toast","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e984ea7b243f231f45db36c593b6182c199","name":"Toast","path":"Toast","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e988cfe4b62ae2e73f7f7be4602344a0f59","name":"Pods","path":"","sourceTree":"","type":"group"},{"guid":"bfdfe7dc352907fc980b868725387e9879aabb07c832697b70c102efdf5309db","name":"Products","path":"","sourceTree":"","type":"group"},{"children":[{"children":[{"fileType":"sourcecode.module-map","guid":"bfdfe7dc352907fc980b868725387e98bc9b33687cf974a825db433d5a4ce66f","path":"Pods-Runner.modulemap","sourceTree":"","type":"file"},{"fileType":"text","guid":"bfdfe7dc352907fc980b868725387e987edd064bed57826d8d017ae149a3d52e","path":"Pods-Runner-acknowledgements.markdown","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e987fb378bbcd6c5ed58c790a44f8de1ad8","path":"Pods-Runner-acknowledgements.plist","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.objc","guid":"bfdfe7dc352907fc980b868725387e98d314b0e0b0622c4b6805a741f2686226","path":"Pods-Runner-dummy.m","sourceTree":"","type":"file"},{"fileType":"text.script.sh","guid":"bfdfe7dc352907fc980b868725387e98c868b1c7c8b6f1d4f98ebdaeb296a675","path":"Pods-Runner-frameworks.sh","sourceTree":"","type":"file"},{"fileType":"text.plist.xml","guid":"bfdfe7dc352907fc980b868725387e986fd78883b66d5d9077a96975628773ce","path":"Pods-Runner-Info.plist","sourceTree":"","type":"file"},{"fileType":"text.script.sh","guid":"bfdfe7dc352907fc980b868725387e983a42af4f91479a2ff6a94702f9452725","path":"Pods-Runner-resources.sh","sourceTree":"","type":"file"},{"fileType":"sourcecode.c.h","guid":"bfdfe7dc352907fc980b868725387e9847c36cf1fe2165ccb62332bdeff26348","path":"Pods-Runner-umbrella.h","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e985f6ec3891a0be6525111807007bbcf76","path":"Pods-Runner.debug.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e988db807c74690161e03dc575ee7464945","path":"Pods-Runner.profile.xcconfig","sourceTree":"","type":"file"},{"fileType":"text.xcconfig","guid":"bfdfe7dc352907fc980b868725387e98778a51cd43710d278531fd4c4e5ed80f","path":"Pods-Runner.release.xcconfig","sourceTree":"","type":"file"}],"guid":"bfdfe7dc352907fc980b868725387e9863ac39dbb3c5e6697e1e483c96fdcf12","name":"Pods-Runner","path":"Target Support Files/Pods-Runner","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98d87df373aa945c7cffc56572b5b5c1d0","name":"Targets Support Files","path":"","sourceTree":"","type":"group"}],"guid":"bfdfe7dc352907fc980b868725387e98677e601b37074db53aff90e47c8f96d1","name":"Pods","path":"","sourceTree":"","type":"group"},"guid":"bfdfe7dc352907fc980b868725387e98","path":"/Users/morn/dev/samples/AppFlowy/frontend/appflowy_flutter/ios/Pods/Pods.xcodeproj","projectDirectory":"/Users/morn/dev/samples/AppFlowy/frontend/appflowy_flutter/ios/Pods","targets":["TARGET@v11_hash=8afac9aa7f2f7ae8a47d13626813e422","TARGET@v11_hash=dba02e4185326fadc962f9bcc306afff","TARGET@v11_hash=9ed764e6a36ac0463803ed04679a098b","TARGET@v11_hash=343abb3a1787caba848689df913b48cc","TARGET@v11_hash=18cc54ce823da0aaace1f19b54169b79","TARGET@v11_hash=455e18a547e744c268f0ab0be67b484f","TARGET@v11_hash=ce18edbb47580206399cf4783eec7ed5","TARGET@v11_hash=50777e38e58c4490a53094c0b174a83e","TARGET@v11_hash=be4bfd549192ab886b16634e611a5cfb","TARGET@v11_hash=bc8a844879af7a4b46ae41879f040474","TARGET@v11_hash=41f53d07703b6cdfcf27ef7c9560cf8c","TARGET@v11_hash=fe89e7ef4549c341d03d86fb3e6322bd","TARGET@v11_hash=8fb782e24ce265c815ff1b320853f917","TARGET@v11_hash=532913e38c7e06e5eea62089d1193ee4","TARGET@v11_hash=3c05d2ce5e305ec83a99f3301a5236aa","TARGET@v11_hash=a588ecdf5bfe2f1ad6c5d9a6bb9940f1","TARGET@v11_hash=c433bd69b99230b9785c1be714ce0175","TARGET@v11_hash=2438ab2bbd7e02a80b06e65e631e1ee9","TARGET@v11_hash=559c3084339e631943ea8fbb0ff14658","TARGET@v11_hash=78c419d7e36f388dac9ad87ec6534e43","TARGET@v11_hash=72545b20ee6d4e64d463a237167e469f","TARGET@v11_hash=7931e16ef4631bcfa5b05077cd140cef","TARGET@v11_hash=91393af516387dfbbafa2eb5029109fc","TARGET@v11_hash=c51f85455c2588dcd567c74a4396fcbf","TARGET@v11_hash=a1a63d5178cbcd2daae2e0cba9b032e5","TARGET@v11_hash=03dea4a492a969d9433ed28b4b2a0aec","TARGET@v11_hash=1dcf7cc21e4184e0f28a9789b4c382c9","TARGET@v11_hash=eaabb77f2569c0713fe5909f5362b3fa","TARGET@v11_hash=817de712cb6fac2be24baa7ec42aaf97","TARGET@v11_hash=fbd6377f91e5f0cc1620995c99b99ff0","TARGET@v11_hash=b5817aa8a8a5b233abd08d304efe013d","TARGET@v11_hash=86aab23948c9cd257baaff836f7414a1","TARGET@v11_hash=29ec1227ae80fa5e85545dce343417e5","TARGET@v11_hash=ab8b0fc009ec3b369e9ae605936ce603","TARGET@v11_hash=64039072b063670902e1ef354134e49d","TARGET@v11_hash=5449496bc380a949b05257eb8db9d316","TARGET@v11_hash=3cca9ca389e095b67ce3af588be9188d","TARGET@v11_hash=f78ac38fdf215d89c0281e470c44b101","TARGET@v11_hash=692a56120a7530dd608fbaa413d3d410","TARGET@v11_hash=bd3c446e66dacbac35fda866591052c9","TARGET@v11_hash=7d8a079c75bc93528df1276ff6c1a06e","TARGET@v11_hash=22014fcd8061c49ec1a7011011fa29d2","TARGET@v11_hash=0c4ac04efd08ba24acda74bf403c30fe","TARGET@v11_hash=6d6f324e26347bf163f3e2dcaa278075","TARGET@v11_hash=36e48dc34e49b20eaf26c3a4d1213a82","TARGET@v11_hash=583d53cd439ec89e8ee070a321e88f4e","TARGET@v11_hash=65477760b7bea77bb4f50c90f24afed5","TARGET@v11_hash=40f8368e8026f113aa896b4bd218efee","TARGET@v11_hash=78da11ebe0789216e22d2e6aaa220c0a"]} \ No newline at end of file diff --git a/frontend/appflowy_flutter/macos/build/ios/XCBuildData/PIFCache/workspace/WORKSPACE@v11_hash=(null)_subobjects=9b6915bad2214bcc5eb58b855fe7b55a-json b/frontend/appflowy_flutter/macos/build/ios/XCBuildData/PIFCache/workspace/WORKSPACE@v11_hash=(null)_subobjects=9b6915bad2214bcc5eb58b855fe7b55a-json new file mode 100644 index 0000000000..0d35e5fa9a --- /dev/null +++ b/frontend/appflowy_flutter/macos/build/ios/XCBuildData/PIFCache/workspace/WORKSPACE@v11_hash=(null)_subobjects=9b6915bad2214bcc5eb58b855fe7b55a-json @@ -0,0 +1 @@ +{"guid":"dc4b70c03e8043e50e38f2068887b1d4","name":"Pods","path":"/Users/morn/dev/samples/AppFlowy/frontend/appflowy_flutter/ios/Pods/Pods.xcodeproj/project.xcworkspace","projects":["PROJECT@v11_mod=a7fbf46937053896f73cc7c7ec6baefb_hash=bfdfe7dc352907fc980b868725387e98plugins=1OJSG6M1FOV3XYQCBH7Z29RZ0FPR9XDE1"]} \ No newline at end of file diff --git a/frontend/appflowy_flutter/packages/appflowy_backend/example/macos/Podfile b/frontend/appflowy_flutter/packages/appflowy_backend/example/macos/Podfile index 8c77835677..012a925033 100644 --- a/frontend/appflowy_flutter/packages/appflowy_backend/example/macos/Podfile +++ b/frontend/appflowy_flutter/packages/appflowy_backend/example/macos/Podfile @@ -1,4 +1,4 @@ -platform :osx, '10.13' +platform :osx, '10.14' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/frontend/appflowy_flutter/packages/appflowy_backend/example/macos/Runner.xcodeproj/project.pbxproj b/frontend/appflowy_flutter/packages/appflowy_backend/example/macos/Runner.xcodeproj/project.pbxproj index ac3debdf8e..b6c5559634 100644 --- a/frontend/appflowy_flutter/packages/appflowy_backend/example/macos/Runner.xcodeproj/project.pbxproj +++ b/frontend/appflowy_flutter/packages/appflowy_backend/example/macos/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 51; + objectVersion = 54; objects = { /* Begin PBXAggregateTarget section */ @@ -26,11 +26,7 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; - 33D1A10422148B71006C7A3E /* FlutterMacOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */; }; - 33D1A10522148B93006C7A3E /* FlutterMacOS.framework in Bundle Framework */ = {isa = PBXBuildFile; fileRef = 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; B7C2E82907836001B5A6F548 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 49D7864808B727FDFB82A4C2 /* Pods_Runner.framework */; }; - D73912F022F37F9E000D13A0 /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D73912EF22F37F9E000D13A0 /* App.framework */; }; - D73912F222F3801D000D13A0 /* App.framework in Bundle Framework */ = {isa = PBXBuildFile; fileRef = D73912EF22F37F9E000D13A0 /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -50,8 +46,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - D73912F222F3801D000D13A0 /* App.framework in Bundle Framework */, - 33D1A10522148B93006C7A3E /* FlutterMacOS.framework in Bundle Framework */, ); name = "Bundle Framework"; runOnlyForDeploymentPostprocessing = 0; @@ -71,7 +65,6 @@ 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; - 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FlutterMacOS.framework; path = Flutter/ephemeral/FlutterMacOS.framework; sourceTree = SOURCE_ROOT; }; 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; @@ -80,7 +73,6 @@ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; A82DF8E6F43DF0AD4D0653DC /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - D73912EF22F37F9E000D13A0 /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/ephemeral/App.framework; sourceTree = SOURCE_ROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -88,8 +80,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D73912F022F37F9E000D13A0 /* App.framework in Frameworks */, - 33D1A10422148B71006C7A3E /* FlutterMacOS.framework in Frameworks */, B7C2E82907836001B5A6F548 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -145,8 +135,6 @@ 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, - D73912EF22F37F9E000D13A0 /* App.framework */, - 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */, ); path = Flutter; sourceTree = ""; @@ -215,7 +203,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0920; - LastUpgradeCheck = 0930; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 33CC10EC2044A3C60003C045 = { @@ -268,6 +256,7 @@ /* Begin PBXShellScriptBuildPhase section */ 3399D490228B24CF009A79C7 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -281,7 +270,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename\n"; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; }; 33CC111E2044C6BF0003C045 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; @@ -414,7 +403,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -497,7 +486,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -544,7 +533,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.14; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/frontend/appflowy_flutter/packages/appflowy_backend/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/frontend/appflowy_flutter/packages/appflowy_backend/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 3d8205cf56..758981e665 100644 --- a/frontend/appflowy_flutter/packages/appflowy_backend/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/frontend/appflowy_flutter/packages/appflowy_backend/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ Bool { return true } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } } diff --git a/frontend/appflowy_flutter/packages/appflowy_backend/lib/appflowy_backend.dart b/frontend/appflowy_flutter/packages/appflowy_backend/lib/appflowy_backend.dart index 69e287f117..f69fd16927 100644 --- a/frontend/appflowy_flutter/packages/appflowy_backend/lib/appflowy_backend.dart +++ b/frontend/appflowy_flutter/packages/appflowy_backend/lib/appflowy_backend.dart @@ -4,11 +4,11 @@ import 'dart:ffi'; import 'dart:io'; import 'dart:isolate'; +import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/rust_stream.dart'; import 'package:ffi/ffi.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; -import 'package:logger/logger.dart'; import 'ffi.dart' as ffi; @@ -62,28 +62,15 @@ class RustLogStreamReceiver { late StreamController _streamController; late StreamSubscription _subscription; int get port => _ffiPort.sendPort.nativePort; - late Logger _logger; RustLogStreamReceiver._internal() { _ffiPort = RawReceivePort(); _streamController = StreamController(); _ffiPort.handler = _streamController.add; - _logger = Logger( - printer: PrettyPrinter( - methodCount: 0, // number of method calls to be displayed - errorMethodCount: 8, // number of method calls if stacktrace is provided - lineLength: 120, // width of the output - colors: false, // Colorful log messages - printEmojis: false, // Print an emoji for each log message - dateTimeFormat: - DateTimeFormat.none, // Should each log print contain a timestamp - ), - level: kDebugMode ? Level.trace : Level.info, - ); _subscription = _streamController.stream.listen((data) { String decodedString = utf8.decode(data); - _logger.i(decodedString); + Log.info(decodedString); }); } diff --git a/frontend/appflowy_flutter/packages/appflowy_backend/lib/dispatch/dispatch.dart b/frontend/appflowy_flutter/packages/appflowy_backend/lib/dispatch/dispatch.dart index 552c6a268f..12fdd60ccf 100644 --- a/frontend/appflowy_flutter/packages/appflowy_backend/lib/dispatch/dispatch.dart +++ b/frontend/appflowy_flutter/packages/appflowy_backend/lib/dispatch/dispatch.dart @@ -24,8 +24,6 @@ import 'package:isolates/isolates.dart'; import 'package:isolates/ports.dart'; import 'package:protobuf/protobuf.dart'; -import '../protobuf/flowy-config/entities.pb.dart'; -import '../protobuf/flowy-config/event_map.pb.dart'; import '../protobuf/flowy-date/entities.pb.dart'; import '../protobuf/flowy-date/event_map.pb.dart'; @@ -35,7 +33,6 @@ part 'dart_event/flowy-folder/dart_event.dart'; part 'dart_event/flowy-user/dart_event.dart'; part 'dart_event/flowy-database2/dart_event.dart'; part 'dart_event/flowy-document/dart_event.dart'; -part 'dart_event/flowy-config/dart_event.dart'; part 'dart_event/flowy-date/dart_event.dart'; part 'dart_event/flowy-search/dart_event.dart'; part 'dart_event/flowy-ai/dart_event.dart'; diff --git a/frontend/appflowy_flutter/packages/appflowy_backend/lib/log.dart b/frontend/appflowy_flutter/packages/appflowy_backend/lib/log.dart index 355a196621..ce0a4e2248 100644 --- a/frontend/appflowy_flutter/packages/appflowy_backend/lib/log.dart +++ b/frontend/appflowy_flutter/packages/appflowy_backend/lib/log.dart @@ -3,64 +3,43 @@ import 'dart:ffi'; import 'package:ffi/ffi.dart' as ffi; import 'package:flutter/foundation.dart'; -import 'package:logger/logger.dart'; +import 'package:talker/talker.dart'; import 'ffi.dart'; class Log { static final shared = Log(); - // ignore: unused_field - late Logger _logger; - bool _enabled = false; + late Talker _logger; + + bool enableFlutterLog = true; // used to disable log in tests @visibleForTesting bool disableLog = false; Log() { - _logger = Logger( - printer: PrettyPrinter( - methodCount: 2, // Number of method calls to be displayed - errorMethodCount: 8, // Number of method calls if stacktrace is provided - lineLength: 120, // Width of the output - colors: true, // Colorful log messages - printEmojis: true, // Print an emoji for each log message - ), - level: kDebugMode ? Level.trace : Level.info, + _logger = Talker( + filter: LogLevelTalkerFilter(), ); } - static void enableFlutterLog() { - shared._enabled = true; - } - // Generic internal logging function to reduce code duplication - static void _log(Level level, int rustLevel, dynamic msg, - [dynamic error, StackTrace? stackTrace]) { - if (shared._enabled) { - switch (level) { - case Level.info: - shared._logger.i(msg, stackTrace: stackTrace); - break; - case Level.debug: - shared._logger.d(msg, stackTrace: stackTrace); - break; - case Level.warning: - shared._logger.w(msg, stackTrace: stackTrace); - break; - case Level.error: - shared._logger.e(msg, stackTrace: stackTrace); - break; - case Level.trace: - shared._logger.t(msg, stackTrace: stackTrace); - break; - default: - shared._logger.log(level, msg, stackTrace: stackTrace); - } + static void _log( + LogLevel level, + int rustLevel, + dynamic msg, [ + dynamic error, + StackTrace? stackTrace, + ]) { + // only forward logs to flutter in debug mode, otherwise log to rust to + // persist logs in the file system + if (shared.enableFlutterLog && kDebugMode) { + shared._logger.log(msg, logLevel: level, stackTrace: stackTrace); + } else { + String formattedMessage = _formatMessageWithStackTrace(msg, stackTrace); + rust_log(rustLevel, toNativeUtf8(formattedMessage)); } - String formattedMessage = _formatMessageWithStackTrace(msg, stackTrace); - rust_log(rustLevel, toNativeUtf8(formattedMessage)); } static void info(dynamic msg, [dynamic error, StackTrace? stackTrace]) { @@ -68,7 +47,7 @@ class Log { return; } - _log(Level.info, 0, msg, error, stackTrace); + _log(LogLevel.info, 0, msg, error, stackTrace); } static void debug(dynamic msg, [dynamic error, StackTrace? stackTrace]) { @@ -76,7 +55,7 @@ class Log { return; } - _log(Level.debug, 1, msg, error, stackTrace); + _log(LogLevel.debug, 1, msg, error, stackTrace); } static void warn(dynamic msg, [dynamic error, StackTrace? stackTrace]) { @@ -84,7 +63,7 @@ class Log { return; } - _log(Level.warning, 3, msg, error, stackTrace); + _log(LogLevel.warning, 3, msg, error, stackTrace); } static void trace(dynamic msg, [dynamic error, StackTrace? stackTrace]) { @@ -92,7 +71,7 @@ class Log { return; } - _log(Level.trace, 2, msg, error, stackTrace); + _log(LogLevel.verbose, 2, msg, error, stackTrace); } static void error(dynamic msg, [dynamic error, StackTrace? stackTrace]) { @@ -100,7 +79,7 @@ class Log { return; } - _log(Level.error, 4, msg, error, stackTrace); + _log(LogLevel.error, 4, msg, error, stackTrace); } } @@ -119,3 +98,11 @@ String _formatMessageWithStackTrace(dynamic msg, StackTrace? stackTrace) { } return msg.toString(); } + +class LogLevelTalkerFilter implements TalkerFilter { + @override + bool filter(TalkerData data) { + // filter out the debug logs in release mode + return kDebugMode ? true : data.logLevel != LogLevel.debug; + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_backend/pubspec.yaml b/frontend/appflowy_flutter/packages/appflowy_backend/pubspec.yaml index 9ff267929a..18aea4838b 100644 --- a/frontend/appflowy_flutter/packages/appflowy_backend/pubspec.yaml +++ b/frontend/appflowy_flutter/packages/appflowy_backend/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: ffi: ^2.0.2 isolates: ^3.0.3+8 protobuf: ^3.1.0 - logger: ^2.4.0 + talker: ^4.7.1 plugin_platform_interface: ^2.1.3 appflowy_result: path: ../appflowy_result diff --git a/frontend/appflowy_flutter/packages/appflowy_popover/example/lib/example_button.dart b/frontend/appflowy_flutter/packages/appflowy_popover/example/lib/example_button.dart index ebd757aad3..12bfd87ac3 100644 --- a/frontend/appflowy_flutter/packages/appflowy_popover/example/lib/example_button.dart +++ b/frontend/appflowy_flutter/packages/appflowy_popover/example/lib/example_button.dart @@ -23,7 +23,7 @@ class _PopoverMenuState extends State { borderRadius: const BorderRadius.all(Radius.circular(8)), boxShadow: [ BoxShadow( - color: Colors.grey.withOpacity(0.5), + color: Colors.grey.withValues(alpha: 0.5), spreadRadius: 5, blurRadius: 7, offset: const Offset(0, 3), // changes position of shadow diff --git a/frontend/appflowy_flutter/packages/appflowy_popover/lib/appflowy_popover.dart b/frontend/appflowy_flutter/packages/appflowy_popover/lib/appflowy_popover.dart index 925b9cad02..0b4208e6fc 100644 --- a/frontend/appflowy_flutter/packages/appflowy_popover/lib/appflowy_popover.dart +++ b/frontend/appflowy_flutter/packages/appflowy_popover/lib/appflowy_popover.dart @@ -1,5 +1,5 @@ /// AppFlowyBoard library -library appflowy_popover; +library; export 'src/mutex.dart'; export 'src/popover.dart'; diff --git a/frontend/appflowy_flutter/packages/appflowy_result/lib/appflowy_result.dart b/frontend/appflowy_flutter/packages/appflowy_result/lib/appflowy_result.dart index 97b81cfe1a..d91c9e4954 100644 --- a/frontend/appflowy_flutter/packages/appflowy_result/lib/appflowy_result.dart +++ b/frontend/appflowy_flutter/packages/appflowy_result/lib/appflowy_result.dart @@ -1,4 +1,5 @@ -library appflowy_result; +/// AppFlowyPopover library +library; export 'src/async_result.dart'; export 'src/result.dart'; diff --git a/frontend/appflowy_flutter/packages/appflowy_result/pubspec.yaml b/frontend/appflowy_flutter/packages/appflowy_result/pubspec.yaml index fa2e35f329..5d8f0d88c2 100644 --- a/frontend/appflowy_flutter/packages/appflowy_result/pubspec.yaml +++ b/frontend/appflowy_flutter/packages/appflowy_result/pubspec.yaml @@ -1,7 +1,7 @@ name: appflowy_result description: "A new Flutter package project." version: 0.0.1 -homepage: +homepage: https://github.com/appflowy-io/appflowy environment: sdk: ">=3.3.0 <4.0.0" @@ -9,40 +9,3 @@ environment: dev_dependencies: flutter_lints: ^3.0.0 - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. -flutter: - - # To add assets to your package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # - # For details regarding assets in packages, see - # https://flutter.dev/assets-and-images/#from-packages - # - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware - - # To add custom fonts to your package, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts in packages, see - # https://flutter.dev/custom-fonts/#from-packages diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/.gitignore b/frontend/appflowy_flutter/packages/appflowy_ui/.gitignore new file mode 100644 index 0000000000..da0bb7ce97 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/.gitignore @@ -0,0 +1,31 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +build/ diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/.metadata b/frontend/appflowy_flutter/packages/appflowy_ui/.metadata new file mode 100644 index 0000000000..79932b61d5 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "d8a9f9a52e5af486f80d932e838ee93861ffd863" + channel: "[user-branch]" + +project_type: package diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/CHANGELOG.md b/frontend/appflowy_flutter/packages/appflowy_ui/CHANGELOG.md new file mode 100644 index 0000000000..41cc7d8192 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/LICENSE b/frontend/appflowy_flutter/packages/appflowy_ui/LICENSE new file mode 100644 index 0000000000..ba75c69f7f --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/README.md b/frontend/appflowy_flutter/packages/appflowy_ui/README.md new file mode 100644 index 0000000000..953d3545f1 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/README.md @@ -0,0 +1,39 @@ +# AppFlowy UI + +AppFlowy UI is a Flutter package that provides a collection of reusable UI components following the AppFlowy design system. These components are designed to be consistent, accessible, and easy to use. + +## Features + +- **Design System Components**: Buttons, text fields, and more UI components that follow the AppFlowy design system +- **Theming**: Consistent theming across all components with light and dark mode support + +## Installation + +Add the following to your `pubspec.yaml` file: + +```yaml +dependencies: + appflowy_ui: ^1.0.0 +``` + +## Supported components + +- [x] Button +- [x] TextField +- [ ] Avatar +- [ ] Checkbox +- [ ] Grid +- [ ] Link +- [ ] Loading & Progress Indicator +- [ ] Menu +- [ ] Message Box +- [ ] Navigation Bar +- [ ] Popover +- [ ] Scroll Bar +- [ ] Tab Bar +- [ ] Toggle +- [ ] Tooltip + +## Reference + +Figma: https://www.figma.com/design/aphWa2OgkqyIragpatdk7a/Design-System diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/analysis_options.yaml b/frontend/appflowy_flutter/packages/appflowy_ui/analysis_options.yaml new file mode 100644 index 0000000000..abba19b4fe --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/analysis_options.yaml @@ -0,0 +1,29 @@ +include: package:flutter_lints/flutter.yaml + +linter: + rules: + - require_trailing_commas + + - prefer_collection_literals + - prefer_final_fields + - prefer_final_in_for_each + - prefer_final_locals + + - sized_box_for_whitespace + - use_decorated_box + + - unnecessary_parenthesis + - unnecessary_await_in_return + - unnecessary_raw_strings + + - avoid_unnecessary_containers + - avoid_redundant_argument_values + - avoid_unused_constructor_parameters + + - always_declare_return_types + + - sort_constructors_first + - unawaited_futures + +errors: + invalid_annotation_target: ignore diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/.gitignore b/frontend/appflowy_flutter/packages/appflowy_ui/example/.gitignore new file mode 100644 index 0000000000..79c113f9b5 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/example/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/.metadata b/frontend/appflowy_flutter/packages/appflowy_ui/example/.metadata new file mode 100644 index 0000000000..777c932a64 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/example/.metadata @@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "d8a9f9a52e5af486f80d932e838ee93861ffd863" + channel: "[user-branch]" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 + base_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 + - platform: macos + create_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 + base_revision: d8a9f9a52e5af486f80d932e838ee93861ffd863 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/README.md b/frontend/appflowy_flutter/packages/appflowy_ui/example/README.md new file mode 100644 index 0000000000..2ccc9e658d --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/example/README.md @@ -0,0 +1,41 @@ +# AppFlowy UI Example + +This example demonstrates how to use the `appflowy_ui` package in a Flutter application. + +## Getting Started + +To run this example: + +1. Ensure you have Flutter installed and set up on your machine +2. Clone this repository +3. Navigate to the example directory: + ```bash + cd example + ``` +4. Get the dependencies: + ```bash + flutter pub get + ``` +5. Run the example: + ```bash + flutter run + ``` + +## Features Demonstrated + +- Basic app structure using AppFlowy UI components +- Material 3 design integration +- Responsive layout + +## Project Structure + +- `lib/main.dart`: The main application file +- `pubspec.yaml`: Project dependencies and configuration + +## Additional Resources + +For more information about the AppFlowy UI package, please refer to: + +- The main package documentation +- [AppFlowy Website](https://appflowy.io) +- [AppFlowy GitHub Repository](https://github.com/AppFlowy-IO/AppFlowy) diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/analysis_options.yaml b/frontend/appflowy_flutter/packages/appflowy_ui/example/analysis_options.yaml new file mode 100644 index 0000000000..0d2902135c --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/example/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/lib/main.dart b/frontend/appflowy_flutter/packages/appflowy_ui/example/lib/main.dart new file mode 100644 index 0000000000..0d23746ebd --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/example/lib/main.dart @@ -0,0 +1,117 @@ +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:flutter/material.dart'; + +import 'src/buttons/buttons_page.dart'; +import 'src/modal/modal_page.dart'; +import 'src/textfield/textfield_page.dart'; + +enum ThemeMode { + light, + dark, +} + +final themeMode = ValueNotifier(ThemeMode.light); + +void main() { + runApp( + const MyApp(), + ); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return ValueListenableBuilder( + valueListenable: themeMode, + builder: (context, themeMode, child) { + final themeBuilder = AppFlowyDefaultTheme(); + final themeData = + themeMode == ThemeMode.light ? ThemeData.light() : ThemeData.dark(); + + return AnimatedAppFlowyTheme( + data: themeMode == ThemeMode.light + ? themeBuilder.light() + : themeBuilder.dark(), + child: MaterialApp( + debugShowCheckedModeBanner: false, + title: 'AppFlowy UI Example', + theme: themeData.copyWith( + visualDensity: VisualDensity.standard, + ), + home: const MyHomePage( + title: 'AppFlowy UI', + ), + ), + ); + }, + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({ + super.key, + required this.title, + }); + + final String title; + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + final tabs = [ + Tab(text: 'Button'), + Tab(text: 'TextField'), + Tab(text: 'Modal'), + ]; + + @override + Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); + return DefaultTabController( + length: tabs.length, + child: Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).colorScheme.inversePrimary, + title: Text( + widget.title, + style: theme.textStyle.title.enhanced( + color: theme.textColorScheme.primary, + ), + ), + actions: [ + IconButton( + icon: Icon( + Theme.of(context).brightness == Brightness.light + ? Icons.dark_mode + : Icons.light_mode, + ), + onPressed: _toggleTheme, + tooltip: 'Toggle theme', + ), + ], + ), + body: TabBarView( + children: [ + ButtonsPage(), + TextFieldPage(), + ModalPage(), + ], + ), + bottomNavigationBar: TabBar( + tabs: tabs, + ), + floatingActionButton: null, + ), + ); + } + + void _toggleTheme() { + themeMode.value = + themeMode.value == ThemeMode.light ? ThemeMode.dark : ThemeMode.light; + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/lib/src/buttons/buttons_page.dart b/frontend/appflowy_flutter/packages/appflowy_ui/example/lib/src/buttons/buttons_page.dart new file mode 100644 index 0000000000..0d0c018222 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/example/lib/src/buttons/buttons_page.dart @@ -0,0 +1,287 @@ +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:flutter/material.dart'; + +class ButtonsPage extends StatelessWidget { + const ButtonsPage({super.key}); + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildSection( + 'Filled Text Buttons', + [ + AFFilledTextButton.primary( + text: 'Primary Button', + onTap: () {}, + ), + const SizedBox(width: 16), + AFFilledTextButton.destructive( + text: 'Destructive Button', + onTap: () {}, + ), + const SizedBox(width: 16), + AFFilledTextButton.disabled( + text: 'Disabled Button', + ), + ], + ), + const SizedBox(height: 32), + _buildSection( + 'Filled Icon Text Buttons', + [ + AFFilledButton.primary( + onTap: () {}, + builder: (context, isHovering, disabled) => Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.add, + size: 20, + color: AppFlowyTheme.of(context).textColorScheme.onFill, + ), + const SizedBox(width: 8), + Text( + 'Primary Button', + style: TextStyle( + color: AppFlowyTheme.of(context).textColorScheme.onFill, + ), + ), + ], + ), + ), + const SizedBox(width: 16), + AFFilledButton.destructive( + onTap: () {}, + builder: (context, isHovering, disabled) => Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.delete, + size: 20, + color: AppFlowyTheme.of(context).textColorScheme.onFill, + ), + const SizedBox(width: 8), + Text( + 'Destructive Button', + style: TextStyle( + color: AppFlowyTheme.of(context).textColorScheme.onFill, + ), + ), + ], + ), + ), + const SizedBox(width: 16), + AFFilledButton.disabled( + builder: (context, isHovering, disabled) => Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.block, + size: 20, + color: AppFlowyTheme.of(context).textColorScheme.tertiary, + ), + const SizedBox(width: 8), + Text( + 'Disabled Button', + style: TextStyle( + color: + AppFlowyTheme.of(context).textColorScheme.tertiary, + ), + ), + ], + ), + ), + ], + ), + const SizedBox(height: 32), + _buildSection( + 'Outlined Text Buttons', + [ + AFOutlinedTextButton.normal( + text: 'Normal Button', + onTap: () {}, + ), + const SizedBox(width: 16), + AFOutlinedTextButton.destructive( + text: 'Destructive Button', + onTap: () {}, + ), + const SizedBox(width: 16), + AFOutlinedTextButton.disabled( + text: 'Disabled Button', + ), + ], + ), + const SizedBox(height: 32), + _buildSection( + 'Outlined Icon Text Buttons', + [ + AFOutlinedButton.normal( + onTap: () {}, + builder: (context, isHovering, disabled) => Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.add, + size: 20, + color: AppFlowyTheme.of(context).textColorScheme.primary, + ), + const SizedBox(width: 8), + Text( + 'Normal Button', + style: TextStyle( + color: + AppFlowyTheme.of(context).textColorScheme.primary, + ), + ), + ], + ), + ), + const SizedBox(width: 16), + AFOutlinedButton.destructive( + onTap: () {}, + builder: (context, isHovering, disabled) => Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.delete, + size: 20, + color: AppFlowyTheme.of(context).textColorScheme.error, + ), + const SizedBox(width: 8), + Text( + 'Destructive Button', + style: TextStyle( + color: AppFlowyTheme.of(context).textColorScheme.error, + ), + ), + ], + ), + ), + const SizedBox(width: 16), + AFOutlinedButton.disabled( + builder: (context, isHovering, disabled) => Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.block, + size: 20, + color: AppFlowyTheme.of(context).textColorScheme.tertiary, + ), + const SizedBox(width: 8), + Text( + 'Disabled Button', + style: TextStyle( + color: + AppFlowyTheme.of(context).textColorScheme.tertiary, + ), + ), + ], + ), + ), + ], + ), + const SizedBox(height: 32), + _buildSection( + 'Ghost Buttons', + [ + AFGhostTextButton.primary( + text: 'Primary Button', + onTap: () {}, + ), + const SizedBox(width: 16), + AFGhostTextButton.disabled( + text: 'Disabled Button', + ), + ], + ), + const SizedBox(height: 32), + _buildSection( + 'Button with alignment', + [ + SizedBox( + width: 200, + child: AFFilledTextButton.primary( + text: 'Left Button', + onTap: () {}, + alignment: Alignment.centerLeft, + ), + ), + const SizedBox(width: 16), + SizedBox( + width: 200, + child: AFFilledTextButton.primary( + text: 'Center Button', + onTap: () {}, + alignment: Alignment.center, + ), + ), + const SizedBox(width: 16), + SizedBox( + width: 200, + child: AFFilledTextButton.primary( + text: 'Right Button', + onTap: () {}, + alignment: Alignment.centerRight, + ), + ), + ], + ), + const SizedBox(height: 32), + _buildSection( + 'Button Sizes', + [ + AFFilledTextButton.primary( + text: 'Small Button', + onTap: () {}, + size: AFButtonSize.s, + ), + const SizedBox(width: 16), + AFFilledTextButton.primary( + text: 'Medium Button', + onTap: () {}, + ), + const SizedBox(width: 16), + AFFilledTextButton.primary( + text: 'Large Button', + onTap: () {}, + size: AFButtonSize.l, + ), + const SizedBox(width: 16), + AFFilledTextButton.primary( + text: 'Extra Large Button', + onTap: () {}, + size: AFButtonSize.xl, + ), + ], + ), + ], + ), + ); + } + + Widget _buildSection(String title, List children) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 16), + Wrap( + spacing: 8, + runSpacing: 8, + children: children, + ), + ], + ); + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/lib/src/modal/modal_page.dart b/frontend/appflowy_flutter/packages/appflowy_ui/example/lib/src/modal/modal_page.dart new file mode 100644 index 0000000000..4a9480d1b9 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/example/lib/src/modal/modal_page.dart @@ -0,0 +1,153 @@ +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:flutter/material.dart'; + +class ModalPage extends StatefulWidget { + const ModalPage({super.key}); + + @override + State createState() => _ModalPageState(); +} + +class _ModalPageState extends State { + double width = AFModalDimension.M; + + @override + Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); + + return Center( + child: Container( + constraints: BoxConstraints(maxWidth: 600), + padding: EdgeInsets.symmetric(horizontal: theme.spacing.xl), + child: Column( + spacing: theme.spacing.l, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + spacing: theme.spacing.m, + mainAxisSize: MainAxisSize.min, + children: [ + AFGhostButton.normal( + onTap: () => setState(() => width = AFModalDimension.S), + builder: (context, isHovering, disabled) { + return Text( + 'S', + style: TextStyle( + color: width == AFModalDimension.S + ? theme.textColorScheme.theme + : theme.textColorScheme.primary, + ), + ); + }, + ), + AFGhostButton.normal( + onTap: () => setState(() => width = AFModalDimension.M), + builder: (context, isHovering, disabled) { + return Text( + 'M', + style: TextStyle( + color: width == AFModalDimension.M + ? theme.textColorScheme.theme + : theme.textColorScheme.primary, + ), + ); + }, + ), + AFGhostButton.normal( + onTap: () => setState(() => width = AFModalDimension.L), + builder: (context, isHovering, disabled) { + return Text( + 'L', + style: TextStyle( + color: width == AFModalDimension.L + ? theme.textColorScheme.theme + : theme.textColorScheme.primary, + ), + ); + }, + ), + ], + ), + AFFilledButton.primary( + builder: (context, isHovering, disabled) { + return Text( + 'Show Modal', + style: TextStyle( + color: AppFlowyTheme.of(context).textColorScheme.onFill, + ), + ); + }, + onTap: () { + showDialog( + context: context, + barrierColor: theme.surfaceColorScheme.overlay, + builder: (context) { + final theme = AppFlowyTheme.of(context); + + return Center( + child: AFModal( + constraints: BoxConstraints( + maxWidth: width, + maxHeight: AFModalDimension.dialogHeight, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + AFModalHeader( + leading: Text( + 'Header', + style: theme.textStyle.heading4.standard( + color: theme.textColorScheme.primary, + ), + ), + trailing: [ + AFGhostButton.normal( + onTap: () => Navigator.of(context).pop(), + builder: (context, isHovering, disabled) { + return const Icon(Icons.close); + }, + ) + ], + ), + Expanded( + child: AFModalBody( + child: Text( + 'A dialog briefly presents information or requests confirmation, allowing users to continue their workflow after interaction.'), + ), + ), + AFModalFooter( + trailing: [ + AFOutlinedButton.normal( + onTap: () => Navigator.of(context).pop(), + builder: (context, isHovering, disabled) { + return const Text('Cancel'); + }, + ), + AFFilledButton.primary( + onTap: () => Navigator.of(context).pop(), + builder: (context, isHovering, disabled) { + return Text( + 'Apply', + style: TextStyle( + color: AppFlowyTheme.of(context) + .textColorScheme + .onFill, + ), + ); + }, + ), + ], + ) + ], + )), + ); + }, + ); + }, + ), + ], + ), + ), + ); + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/lib/src/textfield/textfield_page.dart b/frontend/appflowy_flutter/packages/appflowy_ui/example/lib/src/textfield/textfield_page.dart new file mode 100644 index 0000000000..9e3436ecd4 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/example/lib/src/textfield/textfield_page.dart @@ -0,0 +1,90 @@ +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:flutter/material.dart'; + +class TextFieldPage extends StatelessWidget { + const TextFieldPage({super.key}); + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildSection( + 'TextField Sizes', + [ + AFTextField( + hintText: 'Please enter your name', + size: AFTextFieldSize.m, + ), + AFTextField( + hintText: 'Please enter your name', + ), + ], + ), + const SizedBox(height: 32), + _buildSection( + 'TextField with hint text', + [ + AFTextField( + hintText: 'Please enter your name', + ), + ], + ), + const SizedBox(height: 32), + _buildSection( + 'TextField with initial text', + [ + AFTextField( + initialText: 'https://appflowy.com', + ), + ], + ), + const SizedBox(height: 32), + _buildSection( + 'TextField with validator ', + [ + AFTextField( + validator: (controller) { + if (controller.text.isEmpty) { + return (true, 'This field is required'); + } + + final emailRegex = + RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$'); + if (!emailRegex.hasMatch(controller.text)) { + return (true, 'Please enter a valid email address'); + } + + return (false, ''); + }, + ), + ], + ), + ], + ), + ); + } + + Widget _buildSection(String title, List children) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(height: 16), + Wrap( + spacing: 8, + runSpacing: 8, + children: children, + ), + ], + ); + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/.gitignore b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/.gitignore new file mode 100644 index 0000000000..746adbb6b9 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Flutter/Flutter-Debug.xcconfig b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000000..c2efd0b608 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Flutter/Flutter-Release.xcconfig b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000000..c2efd0b608 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner.xcodeproj/project.pbxproj b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..345181d730 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,705 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* appflowy_ui_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "appflowy_ui_example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* appflowy_ui_example.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* appflowy_ui_example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.appflowyUiExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/appflowy_ui_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/appflowy_ui_example"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.appflowyUiExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/appflowy_ui_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/appflowy_ui_example"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.appflowyUiExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/appflowy_ui_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/appflowy_ui_example"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000000..04d5b736e6 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..1d526a16ed --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/AppDelegate.swift b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000000..b3c1761412 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..a2ec33f19f --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000000..82b6f9d9a3 Binary files /dev/null and b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000000..13b35eba55 Binary files /dev/null and b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000000..0a3f5fa40f Binary files /dev/null and b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000000..bdb57226d5 Binary files /dev/null and b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000000..f083318e09 Binary files /dev/null and b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000000..326c0e72c9 Binary files /dev/null and b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000000..2f1632cfdd Binary files /dev/null and b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Base.lproj/MainMenu.xib b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000000..80e867a4e0 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Configs/AppInfo.xcconfig b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000000..47821fa6d8 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = appflowy_ui_example + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.appflowyUiExample + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved. diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Configs/Debug.xcconfig b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000000..36b0fd9464 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Configs/Release.xcconfig b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000000..dff4f49561 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Configs/Warnings.xcconfig b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000000..42bcbf4780 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/DebugProfile.entitlements b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000000..dddb8a30c8 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Info.plist b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Info.plist new file mode 100644 index 0000000000..4789daa6a4 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/MainFlutterWindow.swift b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000000..3cc05eb234 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Release.entitlements b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Release.entitlements new file mode 100644 index 0000000000..852fa1a472 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/RunnerTests/RunnerTests.swift b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000000..61f3bd1fc5 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/example/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/pubspec.yaml b/frontend/appflowy_flutter/packages/appflowy_ui/example/pubspec.yaml new file mode 100644 index 0000000000..af361ecfab --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/example/pubspec.yaml @@ -0,0 +1,24 @@ +name: appflowy_ui_example +description: "Example app showcasing AppFlowy UI components and widgets" +publish_to: "none" + +version: 1.0.0+1 + +environment: + flutter: ">=3.27.4" + sdk: ">=3.3.0 <4.0.0" + +dependencies: + flutter: + sdk: flutter + appflowy_ui: + path: ../ + cupertino_icons: ^1.0.6 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + +flutter: + uses-material-design: true diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/example/test/widget_test.dart b/frontend/appflowy_flutter/packages/appflowy_ui/example/test/widget_test.dart new file mode 100644 index 0000000000..423052a342 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/example/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:appflowy_ui_example/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/appflowy_ui.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/appflowy_ui.dart new file mode 100644 index 0000000000..974907f940 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/appflowy_ui.dart @@ -0,0 +1,2 @@ +export 'src/component/component.dart'; +export 'src/theme/theme.dart'; diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/base_button/base.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/base_button/base.dart new file mode 100644 index 0000000000..39d5175af1 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/base_button/base.dart @@ -0,0 +1,54 @@ +import 'package:appflowy_ui/src/theme/appflowy_theme.dart'; +import 'package:flutter/widgets.dart'; + +enum AFButtonSize { + s, + m, + l, + xl; + + TextStyle buildTextStyle(BuildContext context) { + final theme = AppFlowyTheme.of(context); + + return switch (this) { + AFButtonSize.s => theme.textStyle.body.enhanced(), + AFButtonSize.m => theme.textStyle.body.enhanced(), + AFButtonSize.l => theme.textStyle.body.enhanced(), + AFButtonSize.xl => theme.textStyle.title.enhanced(), + }; + } + + EdgeInsetsGeometry buildPadding(BuildContext context) { + final theme = AppFlowyTheme.of(context); + + return switch (this) { + AFButtonSize.s => EdgeInsets.symmetric( + horizontal: theme.spacing.l, + vertical: theme.spacing.xs, + ), + AFButtonSize.m => EdgeInsets.symmetric( + horizontal: theme.spacing.xl, + vertical: theme.spacing.s, + ), + AFButtonSize.l => EdgeInsets.symmetric( + horizontal: theme.spacing.xl, + vertical: 10, // why? + ), + AFButtonSize.xl => EdgeInsets.symmetric( + horizontal: theme.spacing.xl, + vertical: 14, // why? + ), + }; + } + + double buildBorderRadius(BuildContext context) { + final theme = AppFlowyTheme.of(context); + + return switch (this) { + AFButtonSize.s => theme.borderRadius.m, + AFButtonSize.m => theme.borderRadius.m, + AFButtonSize.l => 10, // why? + AFButtonSize.xl => theme.borderRadius.xl, + }; + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/base_button/base_button.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/base_button/base_button.dart new file mode 100644 index 0000000000..9bb36507e8 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/base_button/base_button.dart @@ -0,0 +1,152 @@ +import 'package:appflowy_ui/src/theme/appflowy_theme.dart'; +import 'package:flutter/material.dart'; + +typedef AFBaseButtonColorBuilder = Color Function( + BuildContext context, + bool isHovering, + bool disabled, +); + +typedef AFBaseButtonBorderColorBuilder = Color Function( + BuildContext context, + bool isHovering, + bool disabled, + bool isFocused, +); + +class AFBaseButton extends StatefulWidget { + const AFBaseButton({ + super.key, + required this.onTap, + required this.builder, + required this.padding, + required this.borderRadius, + this.borderColor, + this.backgroundColor, + this.ringColor, + this.disabled = false, + }); + + final VoidCallback? onTap; + + final AFBaseButtonBorderColorBuilder? borderColor; + final AFBaseButtonBorderColorBuilder? ringColor; + final AFBaseButtonColorBuilder? backgroundColor; + + final EdgeInsetsGeometry padding; + final double borderRadius; + final bool disabled; + + final Widget Function( + BuildContext context, + bool isHovering, + bool disabled, + ) builder; + + @override + State createState() => _AFBaseButtonState(); +} + +class _AFBaseButtonState extends State { + final FocusNode focusNode = FocusNode(); + + bool isHovering = false; + bool isFocused = false; + + @override + void dispose() { + focusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final Color borderColor = _buildBorderColor(context); + final Color backgroundColor = _buildBackgroundColor(context); + final Color ringColor = _buildRingColor(context); + + return Actions( + actions: { + ActivateIntent: CallbackAction( + onInvoke: (_) { + if (!widget.disabled) { + widget.onTap?.call(); + } + return; + }, + ), + }, + child: Focus( + focusNode: focusNode, + onFocusChange: (isFocused) { + setState(() => this.isFocused = isFocused); + }, + child: MouseRegion( + cursor: widget.disabled + ? SystemMouseCursors.basic + : SystemMouseCursors.click, + onEnter: (_) => setState(() => isHovering = true), + onExit: (_) => setState(() => isHovering = false), + child: GestureDetector( + onTap: widget.disabled ? null : widget.onTap, + child: DecoratedBox( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(widget.borderRadius), + border: isFocused + ? Border.all( + color: ringColor, + width: 2, + strokeAlign: BorderSide.strokeAlignOutside, + ) + : null, + ), + child: DecoratedBox( + decoration: BoxDecoration( + color: backgroundColor, + border: Border.all(color: borderColor), + borderRadius: BorderRadius.circular(widget.borderRadius), + ), + child: Padding( + padding: widget.padding, + child: widget.builder( + context, + isHovering, + widget.disabled, + ), + ), + ), + ), + ), + ), + ), + ); + } + + Color _buildBorderColor(BuildContext context) { + final theme = AppFlowyTheme.of(context); + return widget.borderColor + ?.call(context, isHovering, widget.disabled, isFocused) ?? + theme.borderColorScheme.greyTertiary; + } + + Color _buildBackgroundColor(BuildContext context) { + final theme = AppFlowyTheme.of(context); + return widget.backgroundColor?.call(context, isHovering, widget.disabled) ?? + theme.fillColorScheme.transparent; + } + + Color _buildRingColor(BuildContext context) { + final theme = AppFlowyTheme.of(context); + + if (widget.ringColor != null) { + return widget.ringColor! + .call(context, isHovering, widget.disabled, isFocused); + } + + if (isFocused) { + return theme.borderColorScheme.themeThick.withAlpha(128); + } + + return theme.borderColorScheme.transparent; + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/base_button/base_text_button.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/base_button/base_text_button.dart new file mode 100644 index 0000000000..035307d10b --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/base_button/base_text_button.dart @@ -0,0 +1,55 @@ +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:flutter/material.dart'; + +class AFBaseTextButton extends StatelessWidget { + const AFBaseTextButton({ + super.key, + required this.text, + required this.onTap, + this.disabled = false, + this.size = AFButtonSize.m, + this.padding, + this.borderRadius, + this.textColor, + this.backgroundColor, + this.alignment, + this.textStyle, + }); + + /// The text of the button. + final String text; + + /// Whether the button is disabled. + final bool disabled; + + /// The callback when the button is tapped. + final VoidCallback onTap; + + /// The size of the button. + final AFButtonSize size; + + /// The padding of the button. + final EdgeInsetsGeometry? padding; + + /// The border radius of the button. + final double? borderRadius; + + /// The text color of the button. + final AFBaseButtonColorBuilder? textColor; + + /// The background color of the button. + final AFBaseButtonColorBuilder? backgroundColor; + + /// The alignment of the button. + /// + /// If it's null, the button size will be the size of the text with padding. + final Alignment? alignment; + + /// The text style of the button. + final TextStyle? textStyle; + + @override + Widget build(BuildContext context) { + throw UnimplementedError(); + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/button.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/button.dart new file mode 100644 index 0000000000..31a3a20b5f --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/button.dart @@ -0,0 +1,16 @@ +// Base button +export 'base_button/base.dart'; +export 'base_button/base_button.dart'; +export 'base_button/base_text_button.dart'; +// Filled buttons +export 'filled_button/filled_button.dart'; +export 'filled_button/filled_icon_text_button.dart'; +export 'filled_button/filled_text_button.dart'; +// Ghost buttons +export 'ghost_button/ghost_button.dart'; +export 'ghost_button/ghost_icon_text_button.dart'; +export 'ghost_button/ghost_text_button.dart'; +// Outlined buttons +export 'outlined_button/outlined_button.dart'; +export 'outlined_button/outlined_icon_text_button.dart'; +export 'outlined_button/outlined_text_button.dart'; diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/filled_button/filled_button.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/filled_button/filled_button.dart new file mode 100644 index 0000000000..e871626b59 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/filled_button/filled_button.dart @@ -0,0 +1,125 @@ +import 'package:appflowy_ui/src/component/component.dart'; +import 'package:appflowy_ui/src/theme/appflowy_theme.dart'; +import 'package:flutter/material.dart'; + +typedef AFFilledButtonWidgetBuilder = Widget Function( + BuildContext context, + bool isHovering, + bool disabled, +); + +class AFFilledButton extends StatelessWidget { + const AFFilledButton._({ + super.key, + required this.builder, + required this.onTap, + required this.backgroundColor, + this.size = AFButtonSize.m, + this.padding, + this.borderRadius, + this.disabled = false, + }); + + /// Primary text button. + factory AFFilledButton.primary({ + Key? key, + required AFFilledButtonWidgetBuilder builder, + required VoidCallback onTap, + AFButtonSize size = AFButtonSize.m, + EdgeInsetsGeometry? padding, + double? borderRadius, + bool disabled = false, + }) { + return AFFilledButton._( + key: key, + builder: builder, + onTap: onTap, + size: size, + padding: padding, + borderRadius: borderRadius, + disabled: disabled, + backgroundColor: (context, isHovering, disabled) { + if (disabled) { + return AppFlowyTheme.of(context).fillColorScheme.primaryAlpha5; + } + if (isHovering) { + return AppFlowyTheme.of(context).fillColorScheme.themeThickHover; + } + return AppFlowyTheme.of(context).fillColorScheme.themeThick; + }, + ); + } + + /// Destructive text button. + factory AFFilledButton.destructive({ + Key? key, + required AFFilledButtonWidgetBuilder builder, + required VoidCallback onTap, + AFButtonSize size = AFButtonSize.m, + EdgeInsetsGeometry? padding, + double? borderRadius, + bool disabled = false, + }) { + return AFFilledButton._( + key: key, + builder: builder, + onTap: onTap, + size: size, + padding: padding, + borderRadius: borderRadius, + disabled: disabled, + backgroundColor: (context, isHovering, disabled) { + if (disabled) { + return AppFlowyTheme.of(context).fillColorScheme.primaryAlpha5; + } + if (isHovering) { + return AppFlowyTheme.of(context).fillColorScheme.errorThickHover; + } + return AppFlowyTheme.of(context).fillColorScheme.errorThick; + }, + ); + } + + /// Disabled text button. + factory AFFilledButton.disabled({ + Key? key, + required AFFilledButtonWidgetBuilder builder, + AFButtonSize size = AFButtonSize.m, + EdgeInsetsGeometry? padding, + double? borderRadius, + }) { + return AFFilledButton._( + key: key, + builder: builder, + onTap: () {}, + size: size, + disabled: true, + padding: padding, + borderRadius: borderRadius, + backgroundColor: (context, isHovering, disabled) => + AppFlowyTheme.of(context).fillColorScheme.primaryAlpha5, + ); + } + + final VoidCallback onTap; + final bool disabled; + final AFButtonSize size; + final EdgeInsetsGeometry? padding; + final double? borderRadius; + + final AFBaseButtonColorBuilder? backgroundColor; + final AFFilledButtonWidgetBuilder builder; + + @override + Widget build(BuildContext context) { + return AFBaseButton( + disabled: disabled, + backgroundColor: backgroundColor, + borderColor: (_, __, ___, ____) => Colors.transparent, + padding: padding ?? size.buildPadding(context), + borderRadius: borderRadius ?? size.buildBorderRadius(context), + onTap: onTap, + builder: builder, + ); + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/filled_button/filled_icon_text_button.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/filled_button/filled_icon_text_button.dart new file mode 100644 index 0000000000..04c49d0b01 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/filled_button/filled_icon_text_button.dart @@ -0,0 +1,199 @@ +import 'package:appflowy_ui/src/component/component.dart'; +import 'package:appflowy_ui/src/theme/appflowy_theme.dart'; +import 'package:flutter/material.dart'; + +typedef AFFilledIconBuilder = Widget Function( + BuildContext context, + bool isHovering, + bool disabled, +); + +class AFFilledIconTextButton extends StatelessWidget { + const AFFilledIconTextButton._({ + super.key, + required this.text, + required this.onTap, + required this.iconBuilder, + this.textColor, + this.backgroundColor, + this.size = AFButtonSize.m, + this.padding, + this.borderRadius, + }); + + /// Primary filled text button. + factory AFFilledIconTextButton.primary({ + Key? key, + required String text, + required VoidCallback onTap, + required AFFilledIconBuilder iconBuilder, + AFButtonSize size = AFButtonSize.m, + EdgeInsetsGeometry? padding, + double? borderRadius, + }) { + return AFFilledIconTextButton._( + key: key, + text: text, + onTap: onTap, + iconBuilder: iconBuilder, + size: size, + padding: padding, + borderRadius: borderRadius, + backgroundColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + if (disabled) { + return theme.fillColorScheme.tertiary; + } + if (isHovering) { + return theme.fillColorScheme.themeThickHover; + } + return theme.fillColorScheme.themeThick; + }, + textColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + return theme.textColorScheme.onFill; + }, + ); + } + + /// Destructive filled text button. + factory AFFilledIconTextButton.destructive({ + Key? key, + required String text, + required VoidCallback onTap, + required AFFilledIconBuilder iconBuilder, + AFButtonSize size = AFButtonSize.m, + EdgeInsetsGeometry? padding, + double? borderRadius, + }) { + return AFFilledIconTextButton._( + key: key, + text: text, + iconBuilder: iconBuilder, + onTap: onTap, + size: size, + padding: padding, + borderRadius: borderRadius, + backgroundColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + if (disabled) { + return theme.fillColorScheme.tertiary; + } + if (isHovering) { + return theme.fillColorScheme.errorThickHover; + } + return theme.fillColorScheme.errorThick; + }, + textColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + return theme.textColorScheme.onFill; + }, + ); + } + + /// Disabled filled text button. + factory AFFilledIconTextButton.disabled({ + Key? key, + required String text, + required AFFilledIconBuilder iconBuilder, + AFButtonSize size = AFButtonSize.m, + EdgeInsetsGeometry? padding, + double? borderRadius, + }) { + return AFFilledIconTextButton._( + key: key, + text: text, + iconBuilder: iconBuilder, + onTap: () {}, + size: size, + padding: padding, + borderRadius: borderRadius, + backgroundColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + return theme.fillColorScheme.tertiary; + }, + textColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + return theme.textColorScheme.onFill; + }, + ); + } + + /// Ghost filled text button with transparent background that shows color on hover. + factory AFFilledIconTextButton.ghost({ + Key? key, + required String text, + required VoidCallback onTap, + required AFFilledIconBuilder iconBuilder, + AFButtonSize size = AFButtonSize.m, + EdgeInsetsGeometry? padding, + double? borderRadius, + }) { + return AFFilledIconTextButton._( + key: key, + text: text, + iconBuilder: iconBuilder, + onTap: onTap, + size: size, + padding: padding, + borderRadius: borderRadius, + backgroundColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + if (disabled) { + return Colors.transparent; + } + if (isHovering) { + return theme.fillColorScheme.themeThickHover; + } + return Colors.transparent; + }, + textColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + if (disabled) { + return theme.textColorScheme.tertiary; + } + return theme.textColorScheme.primary; + }, + ); + } + + final String text; + final VoidCallback onTap; + final AFButtonSize size; + final EdgeInsetsGeometry? padding; + final double? borderRadius; + + final AFFilledIconBuilder iconBuilder; + + final AFBaseButtonColorBuilder? textColor; + final AFBaseButtonColorBuilder? backgroundColor; + + @override + Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); + + return AFBaseButton( + backgroundColor: backgroundColor, + padding: padding ?? size.buildPadding(context), + borderRadius: borderRadius ?? size.buildBorderRadius(context), + onTap: onTap, + builder: (context, isHovering, disabled) { + final textColor = this.textColor?.call(context, isHovering, disabled) ?? + theme.textColorScheme.onFill; + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + iconBuilder(context, isHovering, disabled), + SizedBox(width: theme.spacing.s), + Text( + text, + style: size.buildTextStyle(context).copyWith( + color: textColor, + ), + ), + ], + ); + }, + ); + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/filled_button/filled_text_button.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/filled_button/filled_text_button.dart new file mode 100644 index 0000000000..d1b1d868d0 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/filled_button/filled_text_button.dart @@ -0,0 +1,149 @@ +import 'package:appflowy_ui/src/component/component.dart'; +import 'package:appflowy_ui/src/theme/appflowy_theme.dart'; +import 'package:flutter/material.dart'; + +class AFFilledTextButton extends AFBaseTextButton { + const AFFilledTextButton({ + super.key, + required super.text, + required super.onTap, + required super.backgroundColor, + required super.textColor, + super.size = AFButtonSize.m, + super.padding, + super.borderRadius, + super.disabled = false, + super.alignment, + super.textStyle, + }); + + /// Primary text button. + factory AFFilledTextButton.primary({ + Key? key, + required String text, + required VoidCallback onTap, + AFButtonSize size = AFButtonSize.m, + EdgeInsetsGeometry? padding, + double? borderRadius, + bool disabled = false, + Alignment? alignment, + TextStyle? textStyle, + }) { + return AFFilledTextButton( + key: key, + text: text, + onTap: onTap, + size: size, + padding: padding, + borderRadius: borderRadius, + disabled: disabled, + alignment: alignment, + textStyle: textStyle, + textColor: (context, isHovering, disabled) => + AppFlowyTheme.of(context).textColorScheme.onFill, + backgroundColor: (context, isHovering, disabled) { + if (disabled) { + return AppFlowyTheme.of(context).fillColorScheme.primaryAlpha5; + } + if (isHovering) { + return AppFlowyTheme.of(context).fillColorScheme.themeThickHover; + } + return AppFlowyTheme.of(context).fillColorScheme.themeThick; + }, + ); + } + + /// Destructive text button. + factory AFFilledTextButton.destructive({ + Key? key, + required String text, + required VoidCallback onTap, + AFButtonSize size = AFButtonSize.m, + EdgeInsetsGeometry? padding, + double? borderRadius, + bool disabled = false, + Alignment? alignment, + TextStyle? textStyle, + }) { + return AFFilledTextButton( + key: key, + text: text, + onTap: onTap, + size: size, + padding: padding, + borderRadius: borderRadius, + disabled: disabled, + alignment: alignment, + textStyle: textStyle, + textColor: (context, isHovering, disabled) => + AppFlowyTheme.of(context).textColorScheme.onFill, + backgroundColor: (context, isHovering, disabled) { + if (disabled) { + return AppFlowyTheme.of(context).fillColorScheme.primaryAlpha5; + } + if (isHovering) { + return AppFlowyTheme.of(context).fillColorScheme.errorThickHover; + } + return AppFlowyTheme.of(context).fillColorScheme.errorThick; + }, + ); + } + + /// Disabled text button. + factory AFFilledTextButton.disabled({ + Key? key, + required String text, + AFButtonSize size = AFButtonSize.m, + EdgeInsetsGeometry? padding, + double? borderRadius, + Alignment? alignment, + TextStyle? textStyle, + }) { + return AFFilledTextButton( + key: key, + text: text, + onTap: () {}, + size: size, + padding: padding, + borderRadius: borderRadius, + disabled: true, + alignment: alignment, + textStyle: textStyle, + textColor: (context, isHovering, disabled) => + AppFlowyTheme.of(context).textColorScheme.tertiary, + backgroundColor: (context, isHovering, disabled) => + AppFlowyTheme.of(context).fillColorScheme.primaryAlpha5, + ); + } + + @override + Widget build(BuildContext context) { + return AFBaseButton( + disabled: disabled, + backgroundColor: backgroundColor, + borderColor: (_, __, ___, ____) => Colors.transparent, + padding: padding ?? size.buildPadding(context), + borderRadius: borderRadius ?? size.buildBorderRadius(context), + onTap: onTap, + builder: (context, isHovering, disabled) { + final textColor = this.textColor?.call(context, isHovering, disabled) ?? + AppFlowyTheme.of(context).textColorScheme.onFill; + Widget child = Text( + text, + style: textStyle ?? + size.buildTextStyle(context).copyWith(color: textColor), + ); + + final alignment = this.alignment; + if (alignment != null) { + child = Align( + alignment: alignment, + child: child, + ); + } + + return child; + }, + ); + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/ghost_button/ghost_button.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/ghost_button/ghost_button.dart new file mode 100644 index 0000000000..6300c6f5a8 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/ghost_button/ghost_button.dart @@ -0,0 +1,96 @@ +import 'package:appflowy_ui/src/component/component.dart'; +import 'package:appflowy_ui/src/theme/appflowy_theme.dart'; +import 'package:flutter/material.dart'; + +typedef AFGhostButtonWidgetBuilder = Widget Function( + BuildContext context, + bool isHovering, + bool disabled, +); + +class AFGhostButton extends StatelessWidget { + const AFGhostButton._({ + super.key, + required this.onTap, + required this.backgroundColor, + required this.builder, + this.size = AFButtonSize.m, + this.padding, + this.borderRadius, + this.disabled = false, + }); + + /// Normal ghost button. + factory AFGhostButton.normal({ + Key? key, + required VoidCallback onTap, + required AFGhostButtonWidgetBuilder builder, + AFButtonSize size = AFButtonSize.m, + EdgeInsetsGeometry? padding, + double? borderRadius, + bool disabled = false, + }) { + return AFGhostButton._( + key: key, + builder: builder, + onTap: onTap, + size: size, + padding: padding, + borderRadius: borderRadius, + disabled: disabled, + backgroundColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + if (disabled) { + return theme.fillColorScheme.transparent; + } + if (isHovering) { + return theme.fillColorScheme.primaryAlpha5; + } + return theme.fillColorScheme.transparent; + }, + ); + } + + /// Disabled ghost button. + factory AFGhostButton.disabled({ + Key? key, + required AFGhostButtonWidgetBuilder builder, + AFButtonSize size = AFButtonSize.m, + EdgeInsetsGeometry? padding, + double? borderRadius, + }) { + return AFGhostButton._( + key: key, + builder: builder, + onTap: () {}, + size: size, + padding: padding, + borderRadius: borderRadius, + disabled: true, + backgroundColor: (context, isHovering, disabled) => + AppFlowyTheme.of(context).fillColorScheme.transparent, + ); + } + + final VoidCallback onTap; + final bool disabled; + final AFButtonSize size; + final EdgeInsetsGeometry? padding; + final double? borderRadius; + + final AFBaseButtonColorBuilder? backgroundColor; + final AFGhostButtonWidgetBuilder builder; + + @override + Widget build(BuildContext context) { + return AFBaseButton( + disabled: disabled, + backgroundColor: backgroundColor, + borderColor: (_, __, ___, ____) => Colors.transparent, + padding: padding ?? size.buildPadding(context), + borderRadius: borderRadius ?? size.buildBorderRadius(context), + onTap: onTap, + builder: builder, + ); + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/ghost_button/ghost_icon_text_button.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/ghost_button/ghost_icon_text_button.dart new file mode 100644 index 0000000000..af65599ea3 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/ghost_button/ghost_icon_text_button.dart @@ -0,0 +1,141 @@ +import 'package:appflowy_ui/src/component/component.dart'; +import 'package:appflowy_ui/src/theme/appflowy_theme.dart'; +import 'package:flutter/material.dart'; + +typedef AFGhostIconBuilder = Widget Function( + BuildContext context, + bool isHovering, + bool disabled, +); + +class AFGhostIconTextButton extends StatelessWidget { + const AFGhostIconTextButton({ + super.key, + required this.text, + required this.onTap, + required this.iconBuilder, + this.textColor, + this.backgroundColor, + this.size = AFButtonSize.m, + this.padding, + this.borderRadius, + this.disabled = false, + }); + + /// Primary ghost text button. + factory AFGhostIconTextButton.primary({ + Key? key, + required String text, + required VoidCallback onTap, + required AFGhostIconBuilder iconBuilder, + AFButtonSize size = AFButtonSize.m, + EdgeInsetsGeometry? padding, + double? borderRadius, + bool disabled = false, + }) { + return AFGhostIconTextButton( + key: key, + text: text, + onTap: onTap, + iconBuilder: iconBuilder, + size: size, + padding: padding, + borderRadius: borderRadius, + disabled: disabled, + backgroundColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + if (disabled) { + return Colors.transparent; + } + if (isHovering) { + return theme.fillColorScheme.primaryAlpha5; + } + return Colors.transparent; + }, + textColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + if (disabled) { + return theme.textColorScheme.tertiary; + } + return theme.textColorScheme.primary; + }, + ); + } + + /// Disabled ghost text button. + factory AFGhostIconTextButton.disabled({ + Key? key, + required String text, + required AFGhostIconBuilder iconBuilder, + AFButtonSize size = AFButtonSize.m, + EdgeInsetsGeometry? padding, + double? borderRadius, + }) { + return AFGhostIconTextButton( + key: key, + text: text, + iconBuilder: iconBuilder, + onTap: () {}, + size: size, + padding: padding, + borderRadius: borderRadius, + disabled: true, + backgroundColor: (context, isHovering, disabled) { + return Colors.transparent; + }, + textColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + return theme.textColorScheme.tertiary; + }, + ); + } + + final String text; + final bool disabled; + final VoidCallback onTap; + final AFButtonSize size; + final EdgeInsetsGeometry? padding; + final double? borderRadius; + + final AFGhostIconBuilder iconBuilder; + + final AFBaseButtonColorBuilder? textColor; + final AFBaseButtonColorBuilder? backgroundColor; + + @override + Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); + + return AFBaseButton( + disabled: disabled, + backgroundColor: backgroundColor, + borderColor: (context, isHovering, disabled, isFocused) { + return Colors.transparent; + }, + padding: padding ?? size.buildPadding(context), + borderRadius: borderRadius ?? size.buildBorderRadius(context), + onTap: onTap, + builder: (context, isHovering, disabled) { + final textColor = this.textColor?.call(context, isHovering, disabled) ?? + theme.textColorScheme.primary; + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + iconBuilder( + context, + isHovering, + disabled, + ), + SizedBox(width: theme.spacing.m), + Text( + text, + style: size.buildTextStyle(context).copyWith( + color: textColor, + ), + ), + ], + ); + }, + ); + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/ghost_button/ghost_text_button.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/ghost_button/ghost_text_button.dart new file mode 100644 index 0000000000..d154d67dbd --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/ghost_button/ghost_text_button.dart @@ -0,0 +1,116 @@ +import 'package:appflowy_ui/src/component/component.dart'; +import 'package:appflowy_ui/src/theme/appflowy_theme.dart'; +import 'package:flutter/material.dart'; + +class AFGhostTextButton extends AFBaseTextButton { + const AFGhostTextButton({ + super.key, + required super.text, + required super.onTap, + super.textColor, + super.backgroundColor, + super.size = AFButtonSize.m, + super.padding, + super.borderRadius, + super.disabled = false, + super.alignment, + }); + + /// Normal ghost text button. + factory AFGhostTextButton.primary({ + Key? key, + required String text, + required VoidCallback onTap, + AFButtonSize size = AFButtonSize.m, + EdgeInsetsGeometry? padding, + double? borderRadius, + bool disabled = false, + Alignment? alignment, + }) { + return AFGhostTextButton( + key: key, + text: text, + onTap: onTap, + size: size, + padding: padding, + borderRadius: borderRadius, + disabled: disabled, + alignment: alignment, + backgroundColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + if (isHovering) { + return theme.fillColorScheme.primaryAlpha5; + } + return theme.fillColorScheme.transparent; + }, + textColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + if (disabled) { + return theme.textColorScheme.tertiary; + } + if (isHovering) { + return theme.textColorScheme.primary; + } + return theme.textColorScheme.primary; + }, + ); + } + + /// Disabled ghost text button. + factory AFGhostTextButton.disabled({ + Key? key, + required String text, + AFButtonSize size = AFButtonSize.m, + EdgeInsetsGeometry? padding, + double? borderRadius, + Alignment? alignment, + }) { + return AFGhostTextButton( + key: key, + text: text, + onTap: () {}, + size: size, + padding: padding, + borderRadius: borderRadius, + disabled: true, + alignment: alignment, + textColor: (context, isHovering, disabled) => + AppFlowyTheme.of(context).textColorScheme.tertiary, + backgroundColor: (context, isHovering, disabled) => + AppFlowyTheme.of(context).fillColorScheme.transparent, + ); + } + + @override + Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); + + return AFBaseButton( + disabled: disabled, + backgroundColor: backgroundColor, + borderColor: (_, __, ___, ____) => Colors.transparent, + padding: padding ?? size.buildPadding(context), + borderRadius: borderRadius ?? size.buildBorderRadius(context), + onTap: onTap, + builder: (context, isHovering, disabled) { + final textColor = this.textColor?.call(context, isHovering, disabled) ?? + theme.textColorScheme.primary; + + Widget child = Text( + text, + style: size.buildTextStyle(context).copyWith(color: textColor), + ); + + final alignment = this.alignment; + if (alignment != null) { + child = Align( + alignment: alignment, + child: child, + ); + } + + return child; + }, + ); + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/outlined_button/outlined_button.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/outlined_button/outlined_button.dart new file mode 100644 index 0000000000..205d9931d6 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/outlined_button/outlined_button.dart @@ -0,0 +1,168 @@ +import 'package:appflowy_ui/src/component/component.dart'; +import 'package:appflowy_ui/src/theme/appflowy_theme.dart'; +import 'package:flutter/material.dart'; + +typedef AFOutlinedButtonWidgetBuilder = Widget Function( + BuildContext context, + bool isHovering, + bool disabled, +); + +class AFOutlinedButton extends StatelessWidget { + const AFOutlinedButton._({ + super.key, + required this.onTap, + required this.builder, + this.borderColor, + this.backgroundColor, + this.size = AFButtonSize.m, + this.padding, + this.borderRadius, + this.disabled = false, + }); + + /// Normal outlined button. + factory AFOutlinedButton.normal({ + Key? key, + required AFOutlinedButtonWidgetBuilder builder, + required VoidCallback onTap, + AFButtonSize size = AFButtonSize.m, + EdgeInsetsGeometry? padding, + double? borderRadius, + bool disabled = false, + }) { + return AFOutlinedButton._( + key: key, + onTap: onTap, + size: size, + padding: padding, + borderRadius: borderRadius, + disabled: disabled, + borderColor: (context, isHovering, disabled, isFocused) { + final theme = AppFlowyTheme.of(context); + if (disabled) { + return theme.borderColorScheme.greyTertiary; + } + if (isHovering) { + return theme.borderColorScheme.greyTertiaryHover; + } + return theme.borderColorScheme.greyTertiary; + }, + backgroundColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + if (disabled) { + return theme.fillColorScheme.transparent; + } + if (isHovering) { + return theme.fillColorScheme.primaryAlpha5; + } + return theme.fillColorScheme.transparent; + }, + builder: builder, + ); + } + + /// Destructive outlined button. + factory AFOutlinedButton.destructive({ + Key? key, + required AFOutlinedButtonWidgetBuilder builder, + required VoidCallback onTap, + AFButtonSize size = AFButtonSize.m, + EdgeInsetsGeometry? padding, + double? borderRadius, + bool disabled = false, + }) { + return AFOutlinedButton._( + key: key, + onTap: onTap, + size: size, + padding: padding, + borderRadius: borderRadius, + disabled: disabled, + borderColor: (context, isHovering, disabled, isFocused) { + final theme = AppFlowyTheme.of(context); + if (disabled) { + return theme.fillColorScheme.errorThick; + } + if (isHovering) { + return theme.fillColorScheme.errorThickHover; + } + return theme.fillColorScheme.errorThick; + }, + backgroundColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + if (disabled) { + return theme.fillColorScheme.errorThick; + } + if (isHovering) { + return theme.fillColorScheme.errorSelect; + } + return theme.fillColorScheme.transparent; + }, + builder: builder, + ); + } + + /// Disabled outlined text button. + factory AFOutlinedButton.disabled({ + Key? key, + required AFOutlinedButtonWidgetBuilder builder, + AFButtonSize size = AFButtonSize.m, + EdgeInsetsGeometry? padding, + double? borderRadius, + }) { + return AFOutlinedButton._( + key: key, + onTap: () {}, + size: size, + padding: padding, + borderRadius: borderRadius, + disabled: true, + borderColor: (context, isHovering, disabled, isFocused) { + final theme = AppFlowyTheme.of(context); + if (disabled) { + return theme.borderColorScheme.greyTertiary; + } + if (isHovering) { + return theme.borderColorScheme.greyTertiaryHover; + } + return theme.borderColorScheme.greyTertiary; + }, + backgroundColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + if (disabled) { + return theme.fillColorScheme.transparent; + } + if (isHovering) { + return theme.fillColorScheme.primaryAlpha5; + } + return theme.fillColorScheme.transparent; + }, + builder: builder, + ); + } + + final VoidCallback onTap; + final bool disabled; + final AFButtonSize size; + final EdgeInsetsGeometry? padding; + final double? borderRadius; + + final AFBaseButtonBorderColorBuilder? borderColor; + final AFBaseButtonColorBuilder? backgroundColor; + + final AFOutlinedButtonWidgetBuilder builder; + + @override + Widget build(BuildContext context) { + return AFBaseButton( + disabled: disabled, + backgroundColor: backgroundColor, + borderColor: borderColor, + padding: padding ?? size.buildPadding(context), + borderRadius: borderRadius ?? size.buildBorderRadius(context), + onTap: onTap, + builder: builder, + ); + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/outlined_button/outlined_icon_text_button.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/outlined_button/outlined_icon_text_button.dart new file mode 100644 index 0000000000..350594cd46 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/outlined_button/outlined_icon_text_button.dart @@ -0,0 +1,226 @@ +import 'package:appflowy_ui/src/component/component.dart'; +import 'package:appflowy_ui/src/theme/appflowy_theme.dart'; +import 'package:flutter/material.dart'; + +typedef AFOutlinedIconBuilder = Widget Function( + BuildContext context, + bool isHovering, + bool disabled, +); + +class AFOutlinedIconTextButton extends StatelessWidget { + const AFOutlinedIconTextButton._({ + super.key, + required this.text, + required this.onTap, + required this.iconBuilder, + this.borderColor, + this.textColor, + this.backgroundColor, + this.size = AFButtonSize.m, + this.padding, + this.borderRadius, + this.disabled = false, + this.alignment = MainAxisAlignment.center, + }); + + /// Normal outlined text button. + factory AFOutlinedIconTextButton.normal({ + Key? key, + required String text, + required VoidCallback onTap, + required AFOutlinedIconBuilder iconBuilder, + AFButtonSize size = AFButtonSize.m, + EdgeInsetsGeometry? padding, + double? borderRadius, + bool disabled = false, + MainAxisAlignment alignment = MainAxisAlignment.center, + }) { + return AFOutlinedIconTextButton._( + key: key, + text: text, + onTap: onTap, + iconBuilder: iconBuilder, + size: size, + padding: padding, + borderRadius: borderRadius, + disabled: disabled, + alignment: alignment, + borderColor: (context, isHovering, disabled, isFocused) { + final theme = AppFlowyTheme.of(context); + if (disabled) { + return theme.borderColorScheme.greyTertiary; + } + if (isHovering) { + return theme.borderColorScheme.greyTertiaryHover; + } + return theme.borderColorScheme.greyTertiary; + }, + backgroundColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + if (disabled) { + return theme.fillColorScheme.transparent; + } + if (isHovering) { + return theme.fillColorScheme.primaryAlpha5; + } + return theme.fillColorScheme.transparent; + }, + textColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + if (disabled) { + return theme.textColorScheme.tertiary; + } + if (isHovering) { + return theme.textColorScheme.primary; + } + return theme.textColorScheme.primary; + }, + ); + } + + /// Destructive outlined text button. + factory AFOutlinedIconTextButton.destructive({ + Key? key, + required String text, + required VoidCallback onTap, + required AFOutlinedIconBuilder iconBuilder, + AFButtonSize size = AFButtonSize.m, + EdgeInsetsGeometry? padding, + double? borderRadius, + bool disabled = false, + MainAxisAlignment alignment = MainAxisAlignment.center, + }) { + return AFOutlinedIconTextButton._( + key: key, + text: text, + iconBuilder: iconBuilder, + onTap: onTap, + size: size, + padding: padding, + borderRadius: borderRadius, + disabled: disabled, + alignment: alignment, + borderColor: (context, isHovering, disabled, isFocused) { + final theme = AppFlowyTheme.of(context); + if (disabled) { + return theme.fillColorScheme.errorThick; + } + if (isHovering) { + return theme.fillColorScheme.errorThickHover; + } + return theme.fillColorScheme.errorThick; + }, + backgroundColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + if (disabled) { + return theme.fillColorScheme.errorThick; + } + if (isHovering) { + return theme.fillColorScheme.errorThickHover; + } + return theme.fillColorScheme.errorThick; + }, + textColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + return disabled + ? theme.textColorScheme.error + : theme.textColorScheme.error; + }, + ); + } + + /// Disabled outlined text button. + factory AFOutlinedIconTextButton.disabled({ + Key? key, + required String text, + required AFOutlinedIconBuilder iconBuilder, + AFButtonSize size = AFButtonSize.m, + EdgeInsetsGeometry? padding, + double? borderRadius, + MainAxisAlignment alignment = MainAxisAlignment.center, + }) { + return AFOutlinedIconTextButton._( + key: key, + text: text, + iconBuilder: iconBuilder, + onTap: () {}, + size: size, + padding: padding, + borderRadius: borderRadius, + disabled: true, + alignment: alignment, + textColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + return disabled + ? theme.textColorScheme.tertiary + : theme.textColorScheme.primary; + }, + borderColor: (context, isHovering, disabled, isFocused) { + final theme = AppFlowyTheme.of(context); + if (disabled) { + return theme.borderColorScheme.greyTertiary; + } + if (isHovering) { + return theme.borderColorScheme.greyTertiaryHover; + } + return theme.borderColorScheme.greyTertiary; + }, + backgroundColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + if (disabled) { + return theme.fillColorScheme.transparent; + } + if (isHovering) { + return theme.fillColorScheme.primaryAlpha5; + } + return theme.fillColorScheme.transparent; + }, + ); + } + + final String text; + final bool disabled; + final VoidCallback onTap; + final AFButtonSize size; + final EdgeInsetsGeometry? padding; + final double? borderRadius; + final MainAxisAlignment alignment; + + final AFOutlinedIconBuilder iconBuilder; + + final AFBaseButtonColorBuilder? textColor; + final AFBaseButtonBorderColorBuilder? borderColor; + final AFBaseButtonColorBuilder? backgroundColor; + + @override + Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); + + return AFBaseButton( + backgroundColor: backgroundColor, + borderColor: borderColor, + padding: padding ?? size.buildPadding(context), + borderRadius: borderRadius ?? size.buildBorderRadius(context), + onTap: onTap, + disabled: disabled, + builder: (context, isHovering, disabled) { + final textColor = this.textColor?.call(context, isHovering, disabled) ?? + theme.textColorScheme.primary; + return Row( + mainAxisAlignment: alignment, + children: [ + iconBuilder(context, isHovering, disabled), + SizedBox(width: theme.spacing.s), + Text( + text, + style: size.buildTextStyle(context).copyWith( + color: textColor, + ), + ), + ], + ); + }, + ); + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/outlined_button/outlined_text_button.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/outlined_button/outlined_text_button.dart new file mode 100644 index 0000000000..d809d981b0 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/button/outlined_button/outlined_text_button.dart @@ -0,0 +1,212 @@ +import 'package:appflowy_ui/src/component/component.dart'; +import 'package:appflowy_ui/src/theme/appflowy_theme.dart'; +import 'package:flutter/material.dart'; + +class AFOutlinedTextButton extends AFBaseTextButton { + const AFOutlinedTextButton._({ + super.key, + required super.text, + required super.onTap, + this.borderColor, + super.textStyle, + super.textColor, + super.backgroundColor, + super.size = AFButtonSize.m, + super.padding, + super.borderRadius, + super.disabled = false, + super.alignment, + }); + + /// Normal outlined text button. + factory AFOutlinedTextButton.normal({ + Key? key, + required String text, + required VoidCallback onTap, + AFButtonSize size = AFButtonSize.m, + EdgeInsetsGeometry? padding, + double? borderRadius, + bool disabled = false, + Alignment? alignment, + TextStyle? textStyle, + }) { + return AFOutlinedTextButton._( + key: key, + text: text, + onTap: onTap, + size: size, + padding: padding, + borderRadius: borderRadius, + disabled: disabled, + alignment: alignment, + textStyle: textStyle, + borderColor: (context, isHovering, disabled, isFocused) { + final theme = AppFlowyTheme.of(context); + if (disabled) { + return theme.borderColorScheme.greyTertiary; + } + if (isHovering) { + return theme.borderColorScheme.greyTertiaryHover; + } + return theme.borderColorScheme.greyTertiary; + }, + backgroundColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + if (disabled) { + return theme.fillColorScheme.transparent; + } + if (isHovering) { + return theme.fillColorScheme.primaryAlpha5; + } + return theme.fillColorScheme.transparent; + }, + textColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + if (disabled) { + return theme.textColorScheme.tertiary; + } + if (isHovering) { + return theme.textColorScheme.primary; + } + return theme.textColorScheme.primary; + }, + ); + } + + /// Destructive outlined text button. + factory AFOutlinedTextButton.destructive({ + Key? key, + required String text, + required VoidCallback onTap, + AFButtonSize size = AFButtonSize.m, + EdgeInsetsGeometry? padding, + double? borderRadius, + bool disabled = false, + Alignment? alignment, + TextStyle? textStyle, + }) { + return AFOutlinedTextButton._( + key: key, + text: text, + onTap: onTap, + size: size, + padding: padding, + borderRadius: borderRadius, + disabled: disabled, + alignment: alignment, + textStyle: textStyle, + borderColor: (context, isHovering, disabled, isFocused) { + final theme = AppFlowyTheme.of(context); + if (disabled) { + return theme.fillColorScheme.errorThick; + } + if (isHovering) { + return theme.fillColorScheme.errorThickHover; + } + return theme.fillColorScheme.errorThick; + }, + backgroundColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + if (disabled) { + return theme.fillColorScheme.errorThick; + } + if (isHovering) { + return theme.fillColorScheme.errorSelect; + } + return theme.fillColorScheme.transparent; + }, + textColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + return disabled + ? theme.textColorScheme.error + : theme.textColorScheme.error; + }, + ); + } + + /// Disabled outlined text button. + factory AFOutlinedTextButton.disabled({ + Key? key, + required String text, + AFButtonSize size = AFButtonSize.m, + EdgeInsetsGeometry? padding, + double? borderRadius, + Alignment? alignment, + TextStyle? textStyle, + }) { + return AFOutlinedTextButton._( + key: key, + text: text, + onTap: () {}, + size: size, + padding: padding, + borderRadius: borderRadius, + disabled: true, + alignment: alignment, + textStyle: textStyle, + textColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + return disabled + ? theme.textColorScheme.tertiary + : theme.textColorScheme.primary; + }, + borderColor: (context, isHovering, disabled, isFocused) { + final theme = AppFlowyTheme.of(context); + if (disabled) { + return theme.borderColorScheme.greyTertiary; + } + if (isHovering) { + return theme.borderColorScheme.greyTertiaryHover; + } + return theme.borderColorScheme.greyTertiary; + }, + backgroundColor: (context, isHovering, disabled) { + final theme = AppFlowyTheme.of(context); + if (disabled) { + return theme.fillColorScheme.transparent; + } + if (isHovering) { + return theme.fillColorScheme.primaryAlpha5; + } + return theme.fillColorScheme.transparent; + }, + ); + } + + final AFBaseButtonBorderColorBuilder? borderColor; + + @override + Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); + + return AFBaseButton( + disabled: disabled, + backgroundColor: backgroundColor, + borderColor: borderColor, + padding: padding ?? size.buildPadding(context), + borderRadius: borderRadius ?? size.buildBorderRadius(context), + onTap: onTap, + builder: (context, isHovering, disabled) { + final textColor = this.textColor?.call(context, isHovering, disabled) ?? + theme.textColorScheme.primary; + + Widget child = Text( + text, + style: textStyle ?? + size.buildTextStyle(context).copyWith(color: textColor), + ); + + final alignment = this.alignment; + + if (alignment != null) { + child = Align( + alignment: alignment, + child: child, + ); + } + + return child; + }, + ); + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/component.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/component.dart new file mode 100644 index 0000000000..584d50c07b --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/component.dart @@ -0,0 +1,3 @@ +export 'button/button.dart'; +export 'modal/modal.dart'; +export 'textfield/textfield.dart'; diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/modal/dimension.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/modal/dimension.dart new file mode 100644 index 0000000000..72a7dbb5cf --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/modal/dimension.dart @@ -0,0 +1,9 @@ +class AFModalDimension { + const AFModalDimension._(); + + static const double S = 400.0; + static const double M = 560.0; + static const double L = 720.0; + + static const double dialogHeight = 200.0; +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/modal/modal.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/modal/modal.dart new file mode 100644 index 0000000000..4b40aebcbd --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/modal/modal.dart @@ -0,0 +1,125 @@ +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:flutter/material.dart'; + +export 'dimension.dart'; + +class AFModal extends StatelessWidget { + const AFModal({ + super.key, + this.constraints = const BoxConstraints(), + required this.child, + }); + + final BoxConstraints constraints; + final Widget child; + + @override + Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); + + return Center( + child: Padding( + padding: EdgeInsets.all(theme.spacing.xl), + child: ConstrainedBox( + constraints: constraints, + child: DecoratedBox( + decoration: BoxDecoration( + boxShadow: theme.shadow.medium, + borderRadius: BorderRadius.circular(theme.borderRadius.xl), + color: theme.surfaceColorScheme.primary, + ), + child: Material( + color: Colors.transparent, + child: child, + ), + ), + ), + ), + ); + } +} + +class AFModalHeader extends StatelessWidget { + const AFModalHeader({ + super.key, + required this.leading, + this.trailing = const [], + }); + + final Widget leading; + final List trailing; + + @override + Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); + + return Padding( + padding: EdgeInsets.only( + top: theme.spacing.xl, + left: theme.spacing.xxl, + right: theme.spacing.xxl, + ), + child: Row( + spacing: theme.spacing.s, + children: [ + Expanded(child: leading), + ...trailing, + ], + ), + ); + } +} + +class AFModalFooter extends StatelessWidget { + const AFModalFooter({ + super.key, + this.leading = const [], + this.trailing = const [], + }); + + final List leading; + final List trailing; + + @override + Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); + + return Padding( + padding: EdgeInsets.only( + bottom: theme.spacing.xl, + left: theme.spacing.xxl, + right: theme.spacing.xxl, + ), + child: Row( + spacing: theme.spacing.l, + children: [ + ...leading, + Spacer(), + ...trailing, + ], + ), + ); + } +} + +class AFModalBody extends StatelessWidget { + const AFModalBody({ + super.key, + required this.child, + }); + + final Widget child; + + @override + Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); + + return Padding( + padding: EdgeInsets.symmetric( + vertical: theme.spacing.l, + horizontal: theme.spacing.xxl, + ), + child: child, + ); + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/textfield/textfield.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/textfield/textfield.dart new file mode 100644 index 0000000000..3f5ad4cfed --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/component/textfield/textfield.dart @@ -0,0 +1,254 @@ +import 'package:appflowy_ui/src/theme/theme.dart'; +import 'package:flutter/material.dart'; + +typedef AFTextFieldValidator = (bool result, String errorText) Function( + TextEditingController controller, +); + +abstract class AFTextFieldState extends State { + // Error handler + void syncError({required String errorText}) {} + void clearError() {} + + /// Obscure the text. + void syncObscured(bool isObscured) {} +} + +class AFTextField extends StatefulWidget { + const AFTextField({ + super.key, + this.hintText, + this.initialText, + this.keyboardType, + this.size = AFTextFieldSize.l, + this.validator, + this.controller, + this.onChanged, + this.onSubmitted, + this.autoFocus, + this.obscureText = false, + this.suffixIconBuilder, + this.suffixIconConstraints, + }); + + /// The hint text to display when the text field is empty. + final String? hintText; + + /// The initial text to display in the text field. + final String? initialText; + + /// The type of keyboard to display. + final TextInputType? keyboardType; + + /// The size variant of the text field. + final AFTextFieldSize size; + + /// The validator to use for the text field. + final AFTextFieldValidator? validator; + + /// The controller to use for the text field. + /// + /// If it's not provided, the text field will use a new controller. + final TextEditingController? controller; + + /// The callback to call when the text field changes. + final void Function(String)? onChanged; + + /// The callback to call when the text field is submitted. + final void Function(String)? onSubmitted; + + /// Enable auto focus. + final bool? autoFocus; + + /// Obscure the text. + final bool obscureText; + + /// The trailing widget to display. + final Widget Function(BuildContext context, bool isObscured)? + suffixIconBuilder; + + /// The size of the suffix icon. + final BoxConstraints? suffixIconConstraints; + + @override + State createState() => _AFTextFieldState(); +} + +class _AFTextFieldState extends AFTextFieldState { + late final TextEditingController effectiveController; + + bool hasError = false; + String errorText = ''; + + bool isObscured = false; + + @override + void initState() { + super.initState(); + + effectiveController = widget.controller ?? TextEditingController(); + + final initialText = widget.initialText; + if (initialText != null) { + effectiveController.text = initialText; + } + + effectiveController.addListener(_validate); + + isObscured = widget.obscureText; + } + + @override + void dispose() { + effectiveController.removeListener(_validate); + if (widget.controller == null) { + effectiveController.dispose(); + } + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final theme = AppFlowyTheme.of(context); + final borderRadius = widget.size.borderRadius(theme); + final contentPadding = widget.size.contentPadding(theme); + + final errorBorderColor = theme.borderColorScheme.errorThick; + final defaultBorderColor = theme.borderColorScheme.greyTertiary; + + Widget child = TextField( + controller: effectiveController, + keyboardType: widget.keyboardType, + style: theme.textStyle.body.standard( + color: theme.textColorScheme.primary, + ), + obscureText: isObscured, + onChanged: widget.onChanged, + onSubmitted: widget.onSubmitted, + autofocus: widget.autoFocus ?? false, + decoration: InputDecoration( + hintText: widget.hintText, + hintStyle: theme.textStyle.body.standard( + color: theme.textColorScheme.tertiary, + ), + isDense: true, + constraints: BoxConstraints(), + contentPadding: contentPadding, + border: OutlineInputBorder( + borderSide: BorderSide( + color: hasError ? errorBorderColor : defaultBorderColor, + ), + borderRadius: borderRadius, + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: hasError ? errorBorderColor : defaultBorderColor, + ), + borderRadius: borderRadius, + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: hasError + ? errorBorderColor + : theme.borderColorScheme.themeThick, + ), + borderRadius: borderRadius, + ), + errorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: errorBorderColor, + ), + borderRadius: borderRadius, + ), + focusedErrorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: errorBorderColor, + ), + borderRadius: borderRadius, + ), + hoverColor: theme.borderColorScheme.greyTertiaryHover, + suffixIcon: widget.suffixIconBuilder?.call(context, isObscured), + suffixIconConstraints: widget.suffixIconConstraints, + ), + ); + + if (hasError && errorText.isNotEmpty) { + child = Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + child, + SizedBox(height: theme.spacing.xs), + Text( + errorText, + style: theme.textStyle.caption.standard( + color: theme.textColorScheme.error, + ), + ), + ], + ); + } + + return child; + } + + void _validate() { + final validator = widget.validator; + if (validator != null) { + final result = validator(effectiveController); + setState(() { + hasError = result.$1; + errorText = result.$2; + }); + } + } + + @override + void syncError({ + required String errorText, + }) { + setState(() { + hasError = true; + this.errorText = errorText; + }); + } + + @override + void clearError() { + setState(() { + hasError = false; + errorText = ''; + }); + } + + @override + void syncObscured(bool isObscured) { + setState(() { + this.isObscured = isObscured; + }); + } +} + +enum AFTextFieldSize { + m, + l; + + EdgeInsetsGeometry contentPadding(AppFlowyThemeData theme) { + return EdgeInsets.symmetric( + vertical: switch (this) { + AFTextFieldSize.m => theme.spacing.s, + AFTextFieldSize.l => 10.0, + }, + horizontal: theme.spacing.m, + ); + } + + BorderRadius borderRadius(AppFlowyThemeData theme) { + return BorderRadius.circular( + switch (this) { + AFTextFieldSize.m => theme.borderRadius.m, + AFTextFieldSize.l => 10.0, + }, + ); + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/appflowy_theme.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/appflowy_theme.dart new file mode 100644 index 0000000000..b8dc5a1149 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/appflowy_theme.dart @@ -0,0 +1,152 @@ +import 'package:appflowy_ui/src/theme/theme.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +class AppFlowyTheme extends StatelessWidget { + const AppFlowyTheme({ + super.key, + required this.data, + required this.child, + }); + + final AppFlowyThemeData data; + final Widget child; + + static AppFlowyThemeData of(BuildContext context, {bool listen = true}) { + final provider = maybeOf(context, listen: listen); + if (provider == null) { + throw FlutterError( + ''' + AppFlowyTheme.of() called with a context that does not contain a AppFlowyTheme.\n + No AppFlowyTheme ancestor could be found starting from the context that was passed to AppFlowyTheme.of(). + This can happen because you do not have a AppFlowyTheme widget (which introduces a AppFlowyTheme), + or it can happen if the context you use comes from a widget above this widget.\n + The context used was: $context''', + ); + } + return provider; + } + + static AppFlowyThemeData? maybeOf( + BuildContext context, { + bool listen = true, + }) { + if (listen) { + return context + .dependOnInheritedWidgetOfExactType() + ?.themeData; + } + final provider = context + .getElementForInheritedWidgetOfExactType() + ?.widget; + + return (provider as AppFlowyInheritedTheme?)?.themeData; + } + + @override + Widget build(BuildContext context) { + return AppFlowyInheritedTheme( + themeData: data, + child: child, + ); + } +} + +class AppFlowyInheritedTheme extends InheritedTheme { + const AppFlowyInheritedTheme({ + super.key, + required this.themeData, + required super.child, + }); + + final AppFlowyThemeData themeData; + + @override + Widget wrap(BuildContext context, Widget child) { + return AppFlowyTheme(data: themeData, child: child); + } + + @override + bool updateShouldNotify(AppFlowyInheritedTheme oldWidget) => + themeData != oldWidget.themeData; +} + +/// An interpolation between two [AppFlowyThemeData]s. +/// +/// This class specializes the interpolation of [Tween] to +/// call the [AppFlowyThemeData.lerp] method. +/// +/// See [Tween] for a discussion on how to use interpolation objects. +class AppFlowyThemeDataTween extends Tween { + /// Creates a [AppFlowyThemeData] tween. + /// + /// The [begin] and [end] properties must be non-null before the tween is + /// first used, but the arguments can be null if the values are going to be + /// filled in later. + AppFlowyThemeDataTween({super.begin, super.end}); + + @override + AppFlowyThemeData lerp(double t) => AppFlowyThemeData.lerp(begin!, end!, t); +} + +class AnimatedAppFlowyTheme extends ImplicitlyAnimatedWidget { + /// Creates an animated theme. + /// + /// By default, the theme transition uses a linear curve. + const AnimatedAppFlowyTheme({ + super.key, + required this.data, + super.curve, + super.duration = kThemeAnimationDuration, + super.onEnd, + required this.child, + }); + + /// Specifies the color and typography values for descendant widgets. + final AppFlowyThemeData data; + + /// The widget below this widget in the tree. + /// + /// {@macro flutter.widgets.ProxyWidget.child} + final Widget child; + + @override + AnimatedWidgetBaseState createState() => + _AnimatedThemeState(); +} + +class _AnimatedThemeState + extends AnimatedWidgetBaseState { + AppFlowyThemeDataTween? data; + + @override + void forEachTween(TweenVisitor visitor) { + data = visitor( + data, + widget.data, + (dynamic value) => + AppFlowyThemeDataTween(begin: value as AppFlowyThemeData), + )! as AppFlowyThemeDataTween; + } + + @override + Widget build(BuildContext context) { + return AppFlowyTheme( + data: widget.data, + child: widget.child, + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder description) { + super.debugFillProperties(description); + description.add( + DiagnosticsProperty( + 'data', + data, + showName: false, + defaultValue: null, + ), + ); + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/data/appflowy_default/primitive.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/data/appflowy_default/primitive.dart new file mode 100644 index 0000000000..2bd6d619d8 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/data/appflowy_default/primitive.dart @@ -0,0 +1,658 @@ +// ignore_for_file: constant_identifier_names, non_constant_identifier_names +// +// AUTO-GENERATED - DO NOT EDIT DIRECTLY +// +// This file is auto-generated by the generate_theme.dart script +// Generation time: 2025-04-19T13:45:56.076897 +// +// To modify these colors, edit the source JSON files and run the script: +// +// dart run script/generate_theme.dart +// +import 'package:flutter/material.dart'; + +class AppFlowyPrimitiveTokens { + AppFlowyPrimitiveTokens._(); + + /// #f8faff + static Color get neutral100 => Color(0xFFF8FAFF); + + /// #e4e8f5 + static Color get neutral200 => Color(0xFFE4E8F5); + + /// #ced3e6 + static Color get neutral300 => Color(0xFFCED3E6); + + /// #b5bbd3 + static Color get neutral400 => Color(0xFFB5BBD3); + + /// #989eb7 + static Color get neutral500 => Color(0xFF989EB7); + + /// #6f748c + static Color get neutral600 => Color(0xFF6F748C); + + /// #54596e + static Color get neutral700 => Color(0xFF54596E); + + /// #3c3f4e + static Color get neutral800 => Color(0xFF3C3F4E); + + /// #272930 + static Color get neutral900 => Color(0xFF272930); + + /// #21232a + static Color get neutral1000 => Color(0xFF21232A); + + /// #000000 + static Color get neutralBlack => Color(0xFF000000); + + /// #00000099 + static Color get neutralAlphaBlack60 => Color(0x99000000); + + /// #ffffff + static Color get neutralWhite => Color(0xFFFFFFFF); + + /// #ffffff00 + static Color get neutralAlphaWhite0 => Color(0x00FFFFFF); + + /// #ffffff33 + static Color get neutralAlphaWhite20 => Color(0x33FFFFFF); + + /// #ffffff4d + static Color get neutralAlphaWhite30 => Color(0x4DFFFFFF); + + /// #f9fafd0d + static Color get neutralAlphaGrey10005 => Color(0x0DF9FAFD); + + /// #f9fafd1a + static Color get neutralAlphaGrey10010 => Color(0x1AF9FAFD); + + /// #1f23290d + static Color get neutralAlphaGrey100005 => Color(0x0D1F2329); + + /// #1f23291a + static Color get neutralAlphaGrey100010 => Color(0x1A1F2329); + + /// #1f2329b2 + static Color get neutralAlphaGrey100070 => Color(0xB21F2329); + + /// #1f2329cc + static Color get neutralAlphaGrey100080 => Color(0xCC1F2329); + + /// #e3f6ff + static Color get blue100 => Color(0xFFE3F6FF); + + /// #a9e2ff + static Color get blue200 => Color(0xFFA9E2FF); + + /// #80d2ff + static Color get blue300 => Color(0xFF80D2FF); + + /// #4ec1ff + static Color get blue400 => Color(0xFF4EC1FF); + + /// #00b5ff + static Color get blue500 => Color(0xFF00B5FF); + + /// #0092d6 + static Color get blue600 => Color(0xFF0092D6); + + /// #0078c0 + static Color get blue700 => Color(0xFF0078C0); + + /// #0065a9 + static Color get blue800 => Color(0xFF0065A9); + + /// #00508f + static Color get blue900 => Color(0xFF00508F); + + /// #003c77 + static Color get blue1000 => Color(0xFF003C77); + + /// #00b5ff26 + static Color get blueAlphaBlue50015 => Color(0x2600B5FF); + + /// #ecf9f5 + static Color get green100 => Color(0xFFECF9F5); + + /// #c3e5d8 + static Color get green200 => Color(0xFFC3E5D8); + + /// #9ad1bc + static Color get green300 => Color(0xFF9AD1BC); + + /// #71bd9f + static Color get green400 => Color(0xFF71BD9F); + + /// #48a982 + static Color get green500 => Color(0xFF48A982); + + /// #248569 + static Color get green600 => Color(0xFF248569); + + /// #29725d + static Color get green700 => Color(0xFF29725D); + + /// #2e6050 + static Color get green800 => Color(0xFF2E6050); + + /// #305548 + static Color get green900 => Color(0xFF305548); + + /// #305244 + static Color get green1000 => Color(0xFF305244); + + /// #f1e0ff + static Color get purple100 => Color(0xFFF1E0FF); + + /// #e1b3ff + static Color get purple200 => Color(0xFFE1B3FF); + + /// #d185ff + static Color get purple300 => Color(0xFFD185FF); + + /// #bc58ff + static Color get purple400 => Color(0xFFBC58FF); + + /// #9327ff + static Color get purple500 => Color(0xFF9327FF); + + /// #7a1dcc + static Color get purple600 => Color(0xFF7A1DCC); + + /// #6617b3 + static Color get purple700 => Color(0xFF6617B3); + + /// #55138f + static Color get purple800 => Color(0xFF55138F); + + /// #470c72 + static Color get purple900 => Color(0xFF470C72); + + /// #380758 + static Color get purple1000 => Color(0xFF380758); + + /// #ffe5ef + static Color get magenta100 => Color(0xFFFFE5EF); + + /// #ffb8d1 + static Color get magenta200 => Color(0xFFFFB8D1); + + /// #ff8ab2 + static Color get magenta300 => Color(0xFFFF8AB2); + + /// #ff5c93 + static Color get magenta400 => Color(0xFFFF5C93); + + /// #fb006d + static Color get magenta500 => Color(0xFFFB006D); + + /// #d2005f + static Color get magenta600 => Color(0xFFD2005F); + + /// #d2005f + static Color get magenta700 => Color(0xFFD2005F); + + /// #850040 + static Color get magenta800 => Color(0xFF850040); + + /// #610031 + static Color get magenta900 => Color(0xFF610031); + + /// #400022 + static Color get magenta1000 => Color(0xFF400022); + + /// #ffd2dd + static Color get red100 => Color(0xFFFFD2DD); + + /// #ffa5b4 + static Color get red200 => Color(0xFFFFA5B4); + + /// #ff7d87 + static Color get red300 => Color(0xFFFF7D87); + + /// #ff5050 + static Color get red400 => Color(0xFFFF5050); + + /// #f33641 + static Color get red500 => Color(0xFFF33641); + + /// #e71d32 + static Color get red600 => Color(0xFFE71D32); + + /// #ad1625 + static Color get red700 => Color(0xFFAD1625); + + /// #8c101c + static Color get red800 => Color(0xFF8C101C); + + /// #6e0a1e + static Color get red900 => Color(0xFF6E0A1E); + + /// #4c0a17 + static Color get red1000 => Color(0xFF4C0A17); + + /// #f336411a + static Color get redAlphaRed50010 => Color(0x1AF33641); + + /// #fff3d5 + static Color get orange100 => Color(0xFFFFF3D5); + + /// #ffe4ab + static Color get orange200 => Color(0xFFFFE4AB); + + /// #ffd181 + static Color get orange300 => Color(0xFFFFD181); + + /// #ffbe62 + static Color get orange400 => Color(0xFFFFBE62); + + /// #ffa02e + static Color get orange500 => Color(0xFFFFA02E); + + /// #db7e21 + static Color get orange600 => Color(0xFFDB7E21); + + /// #b75f17 + static Color get orange700 => Color(0xFFB75F17); + + /// #93450e + static Color get orange800 => Color(0xFF93450E); + + /// #7a3108 + static Color get orange900 => Color(0xFF7A3108); + + /// #602706 + static Color get orange1000 => Color(0xFF602706); + + /// #fff9b2 + static Color get yellow100 => Color(0xFFFFF9B2); + + /// #ffec66 + static Color get yellow200 => Color(0xFFFFEC66); + + /// #ffdf1a + static Color get yellow300 => Color(0xFFFFDF1A); + + /// #ffcc00 + static Color get yellow400 => Color(0xFFFFCC00); + + /// #ffce00 + static Color get yellow500 => Color(0xFFFFCE00); + + /// #e6b800 + static Color get yellow600 => Color(0xFFE6B800); + + /// #cc9f00 + static Color get yellow700 => Color(0xFFCC9F00); + + /// #b38a00 + static Color get yellow800 => Color(0xFFB38A00); + + /// #9a7500 + static Color get yellow900 => Color(0xFF9A7500); + + /// #7f6200 + static Color get yellow1000 => Color(0xFF7F6200); + + /// #fcf2f2 + static Color get subtleColorRose100 => Color(0xFFFCF2F2); + + /// #fae3e3 + static Color get subtleColorRose200 => Color(0xFFFAE3E3); + + /// #fad9d9 + static Color get subtleColorRose300 => Color(0xFFFAD9D9); + + /// #edadad + static Color get subtleColorRose400 => Color(0xFFEDADAD); + + /// #cc4e4e + static Color get subtleColorRose500 => Color(0xFFCC4E4E); + + /// #702828 + static Color get subtleColorRose600 => Color(0xFF702828); + + /// #fcf4f0 + static Color get subtleColorPapaya100 => Color(0xFFFCF4F0); + + /// #fae8de + static Color get subtleColorPapaya200 => Color(0xFFFAE8DE); + + /// #fadfd2 + static Color get subtleColorPapaya300 => Color(0xFFFADFD2); + + /// #f0bda3 + static Color get subtleColorPapaya400 => Color(0xFFF0BDA3); + + /// #d67240 + static Color get subtleColorPapaya500 => Color(0xFFD67240); + + /// #6b3215 + static Color get subtleColorPapaya600 => Color(0xFF6B3215); + + /// #fff7ed + static Color get subtleColorTangerine100 => Color(0xFFFFF7ED); + + /// #fcedd9 + static Color get subtleColorTangerine200 => Color(0xFFFCEDD9); + + /// #fae5ca + static Color get subtleColorTangerine300 => Color(0xFFFAE5CA); + + /// #f2cb99 + static Color get subtleColorTangerine400 => Color(0xFFF2CB99); + + /// #db8f2c + static Color get subtleColorTangerine500 => Color(0xFFDB8F2C); + + /// #613b0a + static Color get subtleColorTangerine600 => Color(0xFF613B0A); + + /// #fff9ec + static Color get subtleColorMango100 => Color(0xFFFFF9EC); + + /// #fcf1d7 + static Color get subtleColorMango200 => Color(0xFFFCF1D7); + + /// #fae9c3 + static Color get subtleColorMango300 => Color(0xFFFAE9C3); + + /// #f5d68e + static Color get subtleColorMango400 => Color(0xFFF5D68E); + + /// #e0a416 + static Color get subtleColorMango500 => Color(0xFFE0A416); + + /// #5c4102 + static Color get subtleColorMango600 => Color(0xFF5C4102); + + /// #fffbe8 + static Color get subtleColorLemon100 => Color(0xFFFFFBE8); + + /// #fcf5cf + static Color get subtleColorLemon200 => Color(0xFFFCF5CF); + + /// #faefb9 + static Color get subtleColorLemon300 => Color(0xFFFAEFB9); + + /// #f5e282 + static Color get subtleColorLemon400 => Color(0xFFF5E282); + + /// #e0bb00 + static Color get subtleColorLemon500 => Color(0xFFE0BB00); + + /// #574800 + static Color get subtleColorLemon600 => Color(0xFF574800); + + /// #f9fae6 + static Color get subtleColorOlive100 => Color(0xFFF9FAE6); + + /// #f6f7d0 + static Color get subtleColorOlive200 => Color(0xFFF6F7D0); + + /// #f0f2b3 + static Color get subtleColorOlive300 => Color(0xFFF0F2B3); + + /// #dbde83 + static Color get subtleColorOlive400 => Color(0xFFDBDE83); + + /// #adb204 + static Color get subtleColorOlive500 => Color(0xFFADB204); + + /// #4a4c03 + static Color get subtleColorOlive600 => Color(0xFF4A4C03); + + /// #f6f9e6 + static Color get subtleColorLime100 => Color(0xFFF6F9E6); + + /// #eef5ce + static Color get subtleColorLime200 => Color(0xFFEEF5CE); + + /// #e7f0bb + static Color get subtleColorLime300 => Color(0xFFE7F0BB); + + /// #cfdb91 + static Color get subtleColorLime400 => Color(0xFFCFDB91); + + /// #92a822 + static Color get subtleColorLime500 => Color(0xFF92A822); + + /// #414d05 + static Color get subtleColorLime600 => Color(0xFF414D05); + + /// #f4faeb + static Color get subtleColorGrass100 => Color(0xFFF4FAEB); + + /// #e9f5d7 + static Color get subtleColorGrass200 => Color(0xFFE9F5D7); + + /// #def0c5 + static Color get subtleColorGrass300 => Color(0xFFDEF0C5); + + /// #bfd998 + static Color get subtleColorGrass400 => Color(0xFFBFD998); + + /// #75a828 + static Color get subtleColorGrass500 => Color(0xFF75A828); + + /// #334d0c + static Color get subtleColorGrass600 => Color(0xFF334D0C); + + /// #f1faf0 + static Color get subtleColorForest100 => Color(0xFFF1FAF0); + + /// #e2f5df + static Color get subtleColorForest200 => Color(0xFFE2F5DF); + + /// #d7f0d3 + static Color get subtleColorForest300 => Color(0xFFD7F0D3); + + /// #a8d6a1 + static Color get subtleColorForest400 => Color(0xFFA8D6A1); + + /// #49a33b + static Color get subtleColorForest500 => Color(0xFF49A33B); + + /// #1e4f16 + static Color get subtleColorForest600 => Color(0xFF1E4F16); + + /// #f0faf6 + static Color get subtleColorJade100 => Color(0xFFF0FAF6); + + /// #dff5eb + static Color get subtleColorJade200 => Color(0xFFDFF5EB); + + /// #cef0e1 + static Color get subtleColorJade300 => Color(0xFFCEF0E1); + + /// #90d1b5 + static Color get subtleColorJade400 => Color(0xFF90D1B5); + + /// #1c9963 + static Color get subtleColorJade500 => Color(0xFF1C9963); + + /// #075231 + static Color get subtleColorJade600 => Color(0xFF075231); + + /// #f0f9fa + static Color get subtleColorAqua100 => Color(0xFFF0F9FA); + + /// #dff3f5 + static Color get subtleColorAqua200 => Color(0xFFDFF3F5); + + /// #ccecf0 + static Color get subtleColorAqua300 => Color(0xFFCCECF0); + + /// #83ccd4 + static Color get subtleColorAqua400 => Color(0xFF83CCD4); + + /// #008e9e + static Color get subtleColorAqua500 => Color(0xFF008E9E); + + /// #004e57 + static Color get subtleColorAqua600 => Color(0xFF004E57); + + /// #f0f6fa + static Color get subtleColorAzure100 => Color(0xFFF0F6FA); + + /// #e1eef7 + static Color get subtleColorAzure200 => Color(0xFFE1EEF7); + + /// #d3e6f5 + static Color get subtleColorAzure300 => Color(0xFFD3E6F5); + + /// #88c0eb + static Color get subtleColorAzure400 => Color(0xFF88C0EB); + + /// #0877cc + static Color get subtleColorAzure500 => Color(0xFF0877CC); + + /// #154469 + static Color get subtleColorAzure600 => Color(0xFF154469); + + /// #f0f3fa + static Color get subtleColorDenim100 => Color(0xFFF0F3FA); + + /// #e3ebfa + static Color get subtleColorDenim200 => Color(0xFFE3EBFA); + + /// #d7e2f7 + static Color get subtleColorDenim300 => Color(0xFFD7E2F7); + + /// #9ab6ed + static Color get subtleColorDenim400 => Color(0xFF9AB6ED); + + /// #3267d1 + static Color get subtleColorDenim500 => Color(0xFF3267D1); + + /// #223c70 + static Color get subtleColorDenim600 => Color(0xFF223C70); + + /// #f2f2fc + static Color get subtleColorMauve100 => Color(0xFFF2F2FC); + + /// #e6e6fa + static Color get subtleColorMauve200 => Color(0xFFE6E6FA); + + /// #dcdcf7 + static Color get subtleColorMauve300 => Color(0xFFDCDCF7); + + /// #aeaef5 + static Color get subtleColorMauve400 => Color(0xFFAEAEF5); + + /// #5555e0 + static Color get subtleColorMauve500 => Color(0xFF5555E0); + + /// #36366b + static Color get subtleColorMauve600 => Color(0xFF36366B); + + /// #f6f3fc + static Color get subtleColorLavender100 => Color(0xFFF6F3FC); + + /// #ebe3fa + static Color get subtleColorLavender200 => Color(0xFFEBE3FA); + + /// #e4daf7 + static Color get subtleColorLavender300 => Color(0xFFE4DAF7); + + /// #c1aaf0 + static Color get subtleColorLavender400 => Color(0xFFC1AAF0); + + /// #8153db + static Color get subtleColorLavender500 => Color(0xFF8153DB); + + /// #462f75 + static Color get subtleColorLavender600 => Color(0xFF462F75); + + /// #f7f0fa + static Color get subtleColorLilac100 => Color(0xFFF7F0FA); + + /// #f0e1f7 + static Color get subtleColorLilac200 => Color(0xFFF0E1F7); + + /// #edd7f7 + static Color get subtleColorLilac300 => Color(0xFFEDD7F7); + + /// #d3a9e8 + static Color get subtleColorLilac400 => Color(0xFFD3A9E8); + + /// #9e4cc7 + static Color get subtleColorLilac500 => Color(0xFF9E4CC7); + + /// #562d6b + static Color get subtleColorLilac600 => Color(0xFF562D6B); + + /// #faf0fa + static Color get subtleColorMallow100 => Color(0xFFFAF0FA); + + /// #f5e1f4 + static Color get subtleColorMallow200 => Color(0xFFF5E1F4); + + /// #f5d7f4 + static Color get subtleColorMallow300 => Color(0xFFF5D7F4); + + /// #dea4dc + static Color get subtleColorMallow400 => Color(0xFFDEA4DC); + + /// #b240af + static Color get subtleColorMallow500 => Color(0xFFB240AF); + + /// #632861 + static Color get subtleColorMallow600 => Color(0xFF632861); + + /// #f9eff3 + static Color get subtleColorCamellia100 => Color(0xFFF9EFF3); + + /// #f7e1eb + static Color get subtleColorCamellia200 => Color(0xFFF7E1EB); + + /// #f7d7e5 + static Color get subtleColorCamellia300 => Color(0xFFF7D7E5); + + /// #e5a3c0 + static Color get subtleColorCamellia400 => Color(0xFFE5A3C0); + + /// #c24279 + static Color get subtleColorCamellia500 => Color(0xFFC24279); + + /// #6e2343 + static Color get subtleColorCamellia600 => Color(0xFF6E2343); + + /// #f5f5f5 + static Color get subtleColorSmoke100 => Color(0xFFF5F5F5); + + /// #e8e8e8 + static Color get subtleColorSmoke200 => Color(0xFFE8E8E8); + + /// #dedede + static Color get subtleColorSmoke300 => Color(0xFFDEDEDE); + + /// #b8b8b8 + static Color get subtleColorSmoke400 => Color(0xFFB8B8B8); + + /// #6e6e6e + static Color get subtleColorSmoke500 => Color(0xFF6E6E6E); + + /// #404040 + static Color get subtleColorSmoke600 => Color(0xFF404040); + + /// #f2f4f7 + static Color get subtleColorIron100 => Color(0xFFF2F4F7); + + /// #e6e9f0 + static Color get subtleColorIron200 => Color(0xFFE6E9F0); + + /// #dadee5 + static Color get subtleColorIron300 => Color(0xFFDADEE5); + + /// #b0b5bf + static Color get subtleColorIron400 => Color(0xFFB0B5BF); + + /// #666f80 + static Color get subtleColorIron500 => Color(0xFF666F80); + + /// #394152 + static Color get subtleColorIron600 => Color(0xFF394152); +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/data/appflowy_default/semantic.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/data/appflowy_default/semantic.dart new file mode 100644 index 0000000000..3c97c06df3 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/data/appflowy_default/semantic.dart @@ -0,0 +1,332 @@ +// ignore_for_file: constant_identifier_names, non_constant_identifier_names +// +// AUTO-GENERATED - DO NOT EDIT DIRECTLY +// +// This file is auto-generated by the generate_theme.dart script +// Generation time: 2025-04-19T13:45:56.089922 +// +// To modify these colors, edit the source JSON files and run the script: +// +// dart run script/generate_theme.dart +// +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:flutter/material.dart'; + +import '../shared.dart'; +import 'primitive.dart'; + +class AppFlowyDefaultTheme implements AppFlowyThemeBuilder { + @override + AppFlowyThemeData light({ + String? fontFamily, + }) { + final textStyle = AppFlowyBaseTextStyle.customFontFamily(fontFamily ?? ''); + final borderRadius = AppFlowySharedTokens.buildBorderRadius(); + final spacing = AppFlowySharedTokens.buildSpacing(); + final shadow = AppFlowySharedTokens.buildShadow(Brightness.light); + + final textColorScheme = AppFlowyTextColorScheme( + primary: AppFlowyPrimitiveTokens.neutral1000, + secondary: AppFlowyPrimitiveTokens.neutral600, + tertiary: AppFlowyPrimitiveTokens.neutral400, + quaternary: AppFlowyPrimitiveTokens.neutral200, + inverse: AppFlowyPrimitiveTokens.neutralWhite, + onFill: AppFlowyPrimitiveTokens.neutralWhite, + theme: AppFlowyPrimitiveTokens.blue500, + themeHover: AppFlowyPrimitiveTokens.blue600, + action: AppFlowyPrimitiveTokens.blue500, + actionHover: AppFlowyPrimitiveTokens.blue600, + info: AppFlowyPrimitiveTokens.blue500, + infoHover: AppFlowyPrimitiveTokens.blue600, + success: AppFlowyPrimitiveTokens.green600, + successHover: AppFlowyPrimitiveTokens.green700, + warning: AppFlowyPrimitiveTokens.orange600, + warningHover: AppFlowyPrimitiveTokens.orange700, + error: AppFlowyPrimitiveTokens.red600, + errorHover: AppFlowyPrimitiveTokens.red700, + purple: AppFlowyPrimitiveTokens.purple500, + purpleHover: AppFlowyPrimitiveTokens.purple600, + ); + + final iconColorScheme = AppFlowyIconColorScheme( + primary: AppFlowyPrimitiveTokens.neutral1000, + secondary: AppFlowyPrimitiveTokens.neutral600, + tertiary: AppFlowyPrimitiveTokens.neutral400, + quaternary: AppFlowyPrimitiveTokens.neutral200, + white: AppFlowyPrimitiveTokens.neutralWhite, + purpleThick: AppFlowyPrimitiveTokens.purple500, + purpleThickHover: AppFlowyPrimitiveTokens.purple600, + ); + + final borderColorScheme = AppFlowyBorderColorScheme( + primary: AppFlowyPrimitiveTokens.neutral200, + greyPrimary: AppFlowyPrimitiveTokens.neutral1000, + greyPrimaryHover: AppFlowyPrimitiveTokens.neutral900, + greySecondary: AppFlowyPrimitiveTokens.neutral800, + greySecondaryHover: AppFlowyPrimitiveTokens.neutral700, + greyTertiary: AppFlowyPrimitiveTokens.neutral300, + greyTertiaryHover: AppFlowyPrimitiveTokens.neutral400, + greyQuaternary: AppFlowyPrimitiveTokens.neutral100, + greyQuaternaryHover: AppFlowyPrimitiveTokens.neutral200, + transparent: AppFlowyPrimitiveTokens.neutralAlphaWhite0, + themeThick: AppFlowyPrimitiveTokens.blue500, + themeThickHover: AppFlowyPrimitiveTokens.blue600, + infoThick: AppFlowyPrimitiveTokens.blue500, + infoThickHover: AppFlowyPrimitiveTokens.blue600, + successThick: AppFlowyPrimitiveTokens.green600, + successThickHover: AppFlowyPrimitiveTokens.green700, + warningThick: AppFlowyPrimitiveTokens.orange600, + warningThickHover: AppFlowyPrimitiveTokens.orange700, + errorThick: AppFlowyPrimitiveTokens.red600, + errorThickHover: AppFlowyPrimitiveTokens.red700, + purpleThick: AppFlowyPrimitiveTokens.purple500, + purpleThickHover: AppFlowyPrimitiveTokens.purple600, + ); + + final fillColorScheme = AppFlowyFillColorScheme( + primary: AppFlowyPrimitiveTokens.neutral1000, + primaryHover: AppFlowyPrimitiveTokens.neutral900, + secondary: AppFlowyPrimitiveTokens.neutral600, + secondaryHover: AppFlowyPrimitiveTokens.neutral500, + tertiary: AppFlowyPrimitiveTokens.neutral300, + tertiaryHover: AppFlowyPrimitiveTokens.neutral400, + quaternary: AppFlowyPrimitiveTokens.neutral100, + quaternaryHover: AppFlowyPrimitiveTokens.neutral200, + transparent: AppFlowyPrimitiveTokens.neutralAlphaWhite0, + primaryAlpha5: AppFlowyPrimitiveTokens.neutralAlphaGrey100005, + primaryAlpha5Hover: AppFlowyPrimitiveTokens.neutralAlphaGrey100010, + primaryAlpha80: AppFlowyPrimitiveTokens.neutralAlphaGrey100080, + primaryAlpha80Hover: AppFlowyPrimitiveTokens.neutralAlphaGrey100070, + white: AppFlowyPrimitiveTokens.neutralWhite, + whiteAlpha: AppFlowyPrimitiveTokens.neutralAlphaWhite20, + whiteAlphaHover: AppFlowyPrimitiveTokens.neutralAlphaWhite30, + black: AppFlowyPrimitiveTokens.neutralBlack, + themeLight: AppFlowyPrimitiveTokens.blue100, + themeLightHover: AppFlowyPrimitiveTokens.blue200, + themeThick: AppFlowyPrimitiveTokens.blue500, + themeThickHover: AppFlowyPrimitiveTokens.blue600, + themeSelect: AppFlowyPrimitiveTokens.blueAlphaBlue50015, + infoLight: AppFlowyPrimitiveTokens.blue100, + infoLightHover: AppFlowyPrimitiveTokens.blue200, + infoThick: AppFlowyPrimitiveTokens.blue500, + infoThickHover: AppFlowyPrimitiveTokens.blue600, + successLight: AppFlowyPrimitiveTokens.green100, + successLightHover: AppFlowyPrimitiveTokens.green200, + successThick: AppFlowyPrimitiveTokens.green600, + successThickHover: AppFlowyPrimitiveTokens.green700, + warningLight: AppFlowyPrimitiveTokens.orange100, + warningLightHover: AppFlowyPrimitiveTokens.orange200, + warningThick: AppFlowyPrimitiveTokens.orange600, + warningThickHover: AppFlowyPrimitiveTokens.orange700, + errorLight: AppFlowyPrimitiveTokens.red100, + errorLightHover: AppFlowyPrimitiveTokens.red200, + errorThick: AppFlowyPrimitiveTokens.red600, + errorThickHover: AppFlowyPrimitiveTokens.red700, + errorSelect: AppFlowyPrimitiveTokens.redAlphaRed50010, + purpleLight: AppFlowyPrimitiveTokens.purple100, + purpleLightHover: AppFlowyPrimitiveTokens.purple200, + purpleThickHover: AppFlowyPrimitiveTokens.purple600, + purpleThick: AppFlowyPrimitiveTokens.purple500, + ); + + final surfaceColorScheme = AppFlowySurfaceColorScheme( + primary: AppFlowyPrimitiveTokens.neutralWhite, + overlay: AppFlowyPrimitiveTokens.neutralAlphaBlack60, + ); + + final backgroundColorScheme = AppFlowyBackgroundColorScheme( + primary: AppFlowyPrimitiveTokens.neutralWhite, + secondary: AppFlowyPrimitiveTokens.neutral100, + tertiary: AppFlowyPrimitiveTokens.neutral200, + quaternary: AppFlowyPrimitiveTokens.neutral300, + ); + + final brandColorScheme = AppFlowyBrandColorScheme( + skyline: Color(0xFF00B5FF), + aqua: Color(0xFF00C8FF), + violet: Color(0xFF9327FF), + amethyst: Color(0xFF8427E0), + berry: Color(0xFFE3006D), + coral: Color(0xFFFB006D), + golden: Color(0xFFF7931E), + amber: Color(0xFFFFBD00), + lemon: Color(0xFFFFCE00), + ); + + final otherColorsColorScheme = AppFlowyOtherColorsColorScheme( + textHighlight: AppFlowyPrimitiveTokens.blue200, + ); + + return AppFlowyThemeData( + textStyle: textStyle, + textColorScheme: textColorScheme, + borderColorScheme: borderColorScheme, + fillColorScheme: fillColorScheme, + surfaceColorScheme: surfaceColorScheme, + backgroundColorScheme: backgroundColorScheme, + iconColorScheme: iconColorScheme, + brandColorScheme: brandColorScheme, + otherColorsColorScheme: otherColorsColorScheme, + borderRadius: borderRadius, + spacing: spacing, + shadow: shadow, + ); + } + + @override + AppFlowyThemeData dark({ + String? fontFamily, + }) { + final textStyle = AppFlowyBaseTextStyle.customFontFamily(fontFamily ?? ''); + final borderRadius = AppFlowySharedTokens.buildBorderRadius(); + final spacing = AppFlowySharedTokens.buildSpacing(); + final shadow = AppFlowySharedTokens.buildShadow(Brightness.dark); + + final textColorScheme = AppFlowyTextColorScheme( + primary: AppFlowyPrimitiveTokens.neutral200, + secondary: AppFlowyPrimitiveTokens.neutral400, + tertiary: AppFlowyPrimitiveTokens.neutral600, + quaternary: AppFlowyPrimitiveTokens.neutral1000, + inverse: AppFlowyPrimitiveTokens.neutral1000, + onFill: AppFlowyPrimitiveTokens.neutralWhite, + theme: AppFlowyPrimitiveTokens.blue500, + themeHover: AppFlowyPrimitiveTokens.blue600, + action: AppFlowyPrimitiveTokens.blue500, + actionHover: AppFlowyPrimitiveTokens.blue600, + info: AppFlowyPrimitiveTokens.blue500, + infoHover: AppFlowyPrimitiveTokens.blue600, + success: AppFlowyPrimitiveTokens.green600, + successHover: AppFlowyPrimitiveTokens.green700, + warning: AppFlowyPrimitiveTokens.orange600, + warningHover: AppFlowyPrimitiveTokens.orange700, + error: AppFlowyPrimitiveTokens.red500, + errorHover: AppFlowyPrimitiveTokens.red400, + purple: AppFlowyPrimitiveTokens.purple500, + purpleHover: AppFlowyPrimitiveTokens.purple600, + ); + + final iconColorScheme = AppFlowyIconColorScheme( + primary: AppFlowyPrimitiveTokens.neutral200, + secondary: AppFlowyPrimitiveTokens.neutral400, + tertiary: AppFlowyPrimitiveTokens.neutral600, + quaternary: AppFlowyPrimitiveTokens.neutral1000, + white: AppFlowyPrimitiveTokens.neutralWhite, + purpleThick: Color(0xFFFFFFFF), + purpleThickHover: Color(0xFFFFFFFF), + ); + + final borderColorScheme = AppFlowyBorderColorScheme( + primary: AppFlowyPrimitiveTokens.neutral800, + greyPrimary: AppFlowyPrimitiveTokens.neutral100, + greyPrimaryHover: AppFlowyPrimitiveTokens.neutral200, + greySecondary: AppFlowyPrimitiveTokens.neutral300, + greySecondaryHover: AppFlowyPrimitiveTokens.neutral400, + greyTertiary: AppFlowyPrimitiveTokens.neutral800, + greyTertiaryHover: AppFlowyPrimitiveTokens.neutral700, + greyQuaternary: AppFlowyPrimitiveTokens.neutral1000, + greyQuaternaryHover: AppFlowyPrimitiveTokens.neutral900, + transparent: AppFlowyPrimitiveTokens.neutralAlphaWhite0, + themeThick: AppFlowyPrimitiveTokens.blue500, + themeThickHover: AppFlowyPrimitiveTokens.blue600, + infoThick: AppFlowyPrimitiveTokens.blue500, + infoThickHover: AppFlowyPrimitiveTokens.blue600, + successThick: AppFlowyPrimitiveTokens.green600, + successThickHover: AppFlowyPrimitiveTokens.green700, + warningThick: AppFlowyPrimitiveTokens.orange600, + warningThickHover: AppFlowyPrimitiveTokens.orange700, + errorThick: AppFlowyPrimitiveTokens.red500, + errorThickHover: AppFlowyPrimitiveTokens.red400, + purpleThick: AppFlowyPrimitiveTokens.purple500, + purpleThickHover: AppFlowyPrimitiveTokens.purple600, + ); + + final fillColorScheme = AppFlowyFillColorScheme( + primary: AppFlowyPrimitiveTokens.neutral100, + primaryHover: AppFlowyPrimitiveTokens.neutral200, + secondary: AppFlowyPrimitiveTokens.neutral300, + secondaryHover: AppFlowyPrimitiveTokens.neutral400, + tertiary: AppFlowyPrimitiveTokens.neutral600, + tertiaryHover: AppFlowyPrimitiveTokens.neutral500, + quaternary: AppFlowyPrimitiveTokens.neutral1000, + quaternaryHover: AppFlowyPrimitiveTokens.neutral900, + transparent: AppFlowyPrimitiveTokens.neutralAlphaWhite0, + primaryAlpha5: AppFlowyPrimitiveTokens.neutralAlphaGrey10005, + primaryAlpha5Hover: AppFlowyPrimitiveTokens.neutralAlphaGrey10010, + primaryAlpha80: AppFlowyPrimitiveTokens.neutralAlphaGrey100080, + primaryAlpha80Hover: AppFlowyPrimitiveTokens.neutralAlphaGrey100070, + white: AppFlowyPrimitiveTokens.neutralWhite, + whiteAlpha: AppFlowyPrimitiveTokens.neutralAlphaWhite20, + whiteAlphaHover: AppFlowyPrimitiveTokens.neutralAlphaWhite30, + black: AppFlowyPrimitiveTokens.neutralBlack, + themeLight: AppFlowyPrimitiveTokens.blue100, + themeLightHover: AppFlowyPrimitiveTokens.blue200, + themeThick: AppFlowyPrimitiveTokens.blue500, + themeThickHover: AppFlowyPrimitiveTokens.blue400, + themeSelect: AppFlowyPrimitiveTokens.blueAlphaBlue50015, + infoLight: AppFlowyPrimitiveTokens.blue100, + infoLightHover: AppFlowyPrimitiveTokens.blue200, + infoThick: AppFlowyPrimitiveTokens.blue500, + infoThickHover: AppFlowyPrimitiveTokens.blue600, + successLight: AppFlowyPrimitiveTokens.green100, + successLightHover: AppFlowyPrimitiveTokens.green200, + successThick: AppFlowyPrimitiveTokens.green600, + successThickHover: AppFlowyPrimitiveTokens.green700, + warningLight: AppFlowyPrimitiveTokens.orange100, + warningLightHover: AppFlowyPrimitiveTokens.orange200, + warningThick: AppFlowyPrimitiveTokens.orange600, + warningThickHover: AppFlowyPrimitiveTokens.orange700, + errorLight: AppFlowyPrimitiveTokens.red100, + errorLightHover: AppFlowyPrimitiveTokens.red200, + errorThick: AppFlowyPrimitiveTokens.red600, + errorThickHover: AppFlowyPrimitiveTokens.red500, + errorSelect: AppFlowyPrimitiveTokens.redAlphaRed50010, + purpleLight: AppFlowyPrimitiveTokens.purple100, + purpleLightHover: AppFlowyPrimitiveTokens.purple200, + purpleThickHover: AppFlowyPrimitiveTokens.purple600, + purpleThick: AppFlowyPrimitiveTokens.purple500, + ); + + final surfaceColorScheme = AppFlowySurfaceColorScheme( + primary: AppFlowyPrimitiveTokens.neutral900, + overlay: AppFlowyPrimitiveTokens.neutralAlphaBlack60, + ); + + final backgroundColorScheme = AppFlowyBackgroundColorScheme( + primary: AppFlowyPrimitiveTokens.neutral1000, + secondary: AppFlowyPrimitiveTokens.neutral900, + tertiary: AppFlowyPrimitiveTokens.neutral800, + quaternary: AppFlowyPrimitiveTokens.neutral700, + ); + + final brandColorScheme = AppFlowyBrandColorScheme( + skyline: Color(0xFF00B5FF), + aqua: Color(0xFF00C8FF), + violet: Color(0xFF9327FF), + amethyst: Color(0xFF8427E0), + berry: Color(0xFFE3006D), + coral: Color(0xFFFB006D), + golden: Color(0xFFF7931E), + amber: Color(0xFFFFBD00), + lemon: Color(0xFFFFCE00), + ); + + final otherColorsColorScheme = AppFlowyOtherColorsColorScheme( + textHighlight: AppFlowyPrimitiveTokens.blue200, + ); + + return AppFlowyThemeData( + textStyle: textStyle, + textColorScheme: textColorScheme, + borderColorScheme: borderColorScheme, + fillColorScheme: fillColorScheme, + surfaceColorScheme: surfaceColorScheme, + backgroundColorScheme: backgroundColorScheme, + iconColorScheme: iconColorScheme, + brandColorScheme: brandColorScheme, + otherColorsColorScheme: otherColorsColorScheme, + borderRadius: borderRadius, + spacing: spacing, + shadow: shadow, + ); + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/data/built_in_themes.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/data/built_in_themes.dart new file mode 100644 index 0000000000..2b29371433 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/data/built_in_themes.dart @@ -0,0 +1 @@ +export 'appflowy_default/semantic.dart'; diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/data/custom/custom_theme.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/data/custom/custom_theme.dart new file mode 100644 index 0000000000..ca058310b9 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/data/custom/custom_theme.dart @@ -0,0 +1,25 @@ +import 'package:appflowy_ui/appflowy_ui.dart'; + +class CustomTheme implements AppFlowyThemeBuilder { + const CustomTheme({ + required this.lightThemeJson, + required this.darkThemeJson, + }); + + final Map lightThemeJson; + final Map darkThemeJson; + + @override + AppFlowyThemeData light({ + String? fontFamily, + }) { + throw UnimplementedError(); + } + + @override + AppFlowyThemeData dark({ + String? fontFamily, + }) { + throw UnimplementedError(); + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/data/shared.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/data/shared.dart new file mode 100644 index 0000000000..c9c3c3adb0 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/data/shared.dart @@ -0,0 +1,87 @@ +import 'package:appflowy_ui/src/theme/definition/border_radius/border_radius.dart'; +import 'package:appflowy_ui/src/theme/definition/shadow/shadow.dart'; +import 'package:appflowy_ui/src/theme/definition/spacing/spacing.dart'; +import 'package:flutter/material.dart'; + +class AppFlowySpacingConstant { + static const double spacing100 = 4; + static const double spacing200 = 6; + static const double spacing300 = 8; + static const double spacing400 = 12; + static const double spacing500 = 16; + static const double spacing600 = 20; +} + +class AppFlowyBorderRadiusConstant { + static const double radius100 = 4; + static const double radius200 = 6; + static const double radius300 = 8; + static const double radius400 = 12; + static const double radius500 = 16; + static const double radius600 = 20; +} + +class AppFlowySharedTokens { + const AppFlowySharedTokens(); + + static AppFlowyBorderRadius buildBorderRadius() { + return AppFlowyBorderRadius( + xs: AppFlowyBorderRadiusConstant.radius100, + s: AppFlowyBorderRadiusConstant.radius200, + m: AppFlowyBorderRadiusConstant.radius300, + l: AppFlowyBorderRadiusConstant.radius400, + xl: AppFlowyBorderRadiusConstant.radius500, + xxl: AppFlowyBorderRadiusConstant.radius600, + ); + } + + static AppFlowySpacing buildSpacing() { + return AppFlowySpacing( + xs: AppFlowySpacingConstant.spacing100, + s: AppFlowySpacingConstant.spacing200, + m: AppFlowySpacingConstant.spacing300, + l: AppFlowySpacingConstant.spacing400, + xl: AppFlowySpacingConstant.spacing500, + xxl: AppFlowySpacingConstant.spacing600, + ); + } + + static AppFlowyShadow buildShadow( + Brightness brightness, + ) { + return switch (brightness) { + Brightness.light => AppFlowyShadow( + small: [ + BoxShadow( + offset: Offset(0, 2), + blurRadius: 16, + color: Color(0x1F000000), + ), + ], + medium: [ + BoxShadow( + offset: Offset(0, 4), + blurRadius: 32, + color: Color(0x1F000000), + ), + ], + ), + Brightness.dark => AppFlowyShadow( + small: [ + BoxShadow( + offset: Offset(0, 2), + blurRadius: 16, + color: Color(0x7A000000), + ), + ], + medium: [ + BoxShadow( + offset: Offset(0, 4), + blurRadius: 32, + color: Color(0x7A000000), + ), + ], + ), + }; + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/border_radius/border_radius.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/border_radius/border_radius.dart new file mode 100644 index 0000000000..fb07a5fe64 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/border_radius/border_radius.dart @@ -0,0 +1,17 @@ +class AppFlowyBorderRadius { + const AppFlowyBorderRadius({ + required this.xs, + required this.s, + required this.m, + required this.l, + required this.xl, + required this.xxl, + }); + + final double xs; + final double s; + final double m; + final double l; + final double xl; + final double xxl; +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/color_scheme/background_color_scheme.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/color_scheme/background_color_scheme.dart new file mode 100644 index 0000000000..c7324c34fe --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/color_scheme/background_color_scheme.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; + +class AppFlowyBackgroundColorScheme { + const AppFlowyBackgroundColorScheme({ + required this.primary, + required this.secondary, + required this.tertiary, + required this.quaternary, + }); + + final Color primary; + final Color secondary; + final Color tertiary; + final Color quaternary; + + AppFlowyBackgroundColorScheme lerp( + AppFlowyBackgroundColorScheme other, + double t, + ) { + return AppFlowyBackgroundColorScheme( + primary: Color.lerp(primary, other.primary, t)!, + secondary: Color.lerp(secondary, other.secondary, t)!, + tertiary: Color.lerp(tertiary, other.tertiary, t)!, + quaternary: Color.lerp(quaternary, other.quaternary, t)!, + ); + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/color_scheme/border_color_scheme.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/color_scheme/border_color_scheme.dart new file mode 100644 index 0000000000..ca65ed1fb4 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/color_scheme/border_color_scheme.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; + +class AppFlowyBorderColorScheme { + AppFlowyBorderColorScheme({ + required this.primary, + required this.greyPrimary, + required this.greyPrimaryHover, + required this.greySecondary, + required this.greySecondaryHover, + required this.greyTertiary, + required this.greyTertiaryHover, + required this.greyQuaternary, + required this.greyQuaternaryHover, + required this.transparent, + required this.themeThick, + required this.themeThickHover, + required this.infoThick, + required this.infoThickHover, + required this.successThick, + required this.successThickHover, + required this.warningThick, + required this.warningThickHover, + required this.errorThick, + required this.errorThickHover, + required this.purpleThick, + required this.purpleThickHover, + }); + + final Color primary; + final Color greyPrimary; + final Color greyPrimaryHover; + final Color greySecondary; + final Color greySecondaryHover; + final Color greyTertiary; + final Color greyTertiaryHover; + final Color greyQuaternary; + final Color greyQuaternaryHover; + final Color transparent; + final Color themeThick; + final Color themeThickHover; + final Color infoThick; + final Color infoThickHover; + final Color successThick; + final Color successThickHover; + final Color warningThick; + final Color warningThickHover; + final Color errorThick; + final Color errorThickHover; + final Color purpleThick; + final Color purpleThickHover; + + AppFlowyBorderColorScheme lerp( + AppFlowyBorderColorScheme other, + double t, + ) { + return AppFlowyBorderColorScheme( + primary: Color.lerp(primary, other.primary, t)!, + greyPrimary: Color.lerp(greyPrimary, other.greyPrimary, t)!, + greyPrimaryHover: + Color.lerp(greyPrimaryHover, other.greyPrimaryHover, t)!, + greySecondary: Color.lerp(greySecondary, other.greySecondary, t)!, + greySecondaryHover: + Color.lerp(greySecondaryHover, other.greySecondaryHover, t)!, + greyTertiary: Color.lerp(greyTertiary, other.greyTertiary, t)!, + greyTertiaryHover: + Color.lerp(greyTertiaryHover, other.greyTertiaryHover, t)!, + greyQuaternary: Color.lerp(greyQuaternary, other.greyQuaternary, t)!, + greyQuaternaryHover: + Color.lerp(greyQuaternaryHover, other.greyQuaternaryHover, t)!, + transparent: Color.lerp(transparent, other.transparent, t)!, + themeThick: Color.lerp(themeThick, other.themeThick, t)!, + themeThickHover: Color.lerp(themeThickHover, other.themeThickHover, t)!, + infoThick: Color.lerp(infoThick, other.infoThick, t)!, + infoThickHover: Color.lerp(infoThickHover, other.infoThickHover, t)!, + successThick: Color.lerp(successThick, other.successThick, t)!, + successThickHover: + Color.lerp(successThickHover, other.successThickHover, t)!, + warningThick: Color.lerp(warningThick, other.warningThick, t)!, + warningThickHover: + Color.lerp(warningThickHover, other.warningThickHover, t)!, + errorThick: Color.lerp(errorThick, other.errorThick, t)!, + errorThickHover: Color.lerp(errorThickHover, other.errorThickHover, t)!, + purpleThick: Color.lerp(purpleThick, other.purpleThick, t)!, + purpleThickHover: + Color.lerp(purpleThickHover, other.purpleThickHover, t)!, + ); + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/color_scheme/brand_color_scheme.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/color_scheme/brand_color_scheme.dart new file mode 100644 index 0000000000..4140f6924a --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/color_scheme/brand_color_scheme.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; + +class AppFlowyBrandColorScheme { + const AppFlowyBrandColorScheme({ + required this.skyline, + required this.aqua, + required this.violet, + required this.amethyst, + required this.berry, + required this.coral, + required this.golden, + required this.amber, + required this.lemon, + }); + + final Color skyline; + final Color aqua; + final Color violet; + final Color amethyst; + final Color berry; + final Color coral; + final Color golden; + final Color amber; + final Color lemon; + + AppFlowyBrandColorScheme lerp( + AppFlowyBrandColorScheme other, + double t, + ) { + return AppFlowyBrandColorScheme( + skyline: Color.lerp(skyline, other.skyline, t)!, + aqua: Color.lerp(aqua, other.aqua, t)!, + violet: Color.lerp(violet, other.violet, t)!, + amethyst: Color.lerp(amethyst, other.amethyst, t)!, + berry: Color.lerp(berry, other.berry, t)!, + coral: Color.lerp(coral, other.coral, t)!, + golden: Color.lerp(golden, other.golden, t)!, + amber: Color.lerp(amber, other.amber, t)!, + lemon: Color.lerp(lemon, other.lemon, t)!, + ); + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/color_scheme/color_scheme.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/color_scheme/color_scheme.dart new file mode 100644 index 0000000000..01952e1461 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/color_scheme/color_scheme.dart @@ -0,0 +1,8 @@ +export 'background_color_scheme.dart'; +export 'border_color_scheme.dart'; +export 'brand_color_scheme.dart'; +export 'fill_color_scheme.dart'; +export 'icon_color_scheme.dart'; +export 'other_color_scheme.dart'; +export 'surface_color_scheme.dart'; +export 'text_color_scheme.dart'; diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/color_scheme/fill_color_scheme.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/color_scheme/fill_color_scheme.dart new file mode 100644 index 0000000000..3faac64dfc --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/color_scheme/fill_color_scheme.dart @@ -0,0 +1,152 @@ +import 'package:flutter/material.dart'; + +class AppFlowyFillColorScheme { + const AppFlowyFillColorScheme({ + required this.primary, + required this.primaryHover, + required this.secondary, + required this.secondaryHover, + required this.tertiary, + required this.tertiaryHover, + required this.quaternary, + required this.quaternaryHover, + required this.transparent, + required this.primaryAlpha5, + required this.primaryAlpha5Hover, + required this.primaryAlpha80, + required this.primaryAlpha80Hover, + required this.white, + required this.whiteAlpha, + required this.whiteAlphaHover, + required this.black, + required this.themeLight, + required this.themeLightHover, + required this.themeThick, + required this.themeThickHover, + required this.themeSelect, + required this.infoLight, + required this.infoLightHover, + required this.infoThick, + required this.infoThickHover, + required this.successLight, + required this.successLightHover, + required this.successThick, + required this.successThickHover, + required this.warningLight, + required this.warningLightHover, + required this.warningThick, + required this.warningThickHover, + required this.errorLight, + required this.errorLightHover, + required this.errorThick, + required this.errorThickHover, + required this.errorSelect, + required this.purpleLight, + required this.purpleLightHover, + required this.purpleThick, + required this.purpleThickHover, + }); + + final Color primary; + final Color primaryHover; + final Color secondary; + final Color secondaryHover; + final Color tertiary; + final Color tertiaryHover; + final Color quaternary; + final Color quaternaryHover; + final Color transparent; + final Color primaryAlpha5; + final Color primaryAlpha5Hover; + final Color primaryAlpha80; + final Color primaryAlpha80Hover; + final Color white; + final Color whiteAlpha; + final Color whiteAlphaHover; + final Color black; + final Color themeLight; + final Color themeLightHover; + final Color themeThick; + final Color themeThickHover; + final Color themeSelect; + final Color infoLight; + final Color infoLightHover; + final Color infoThick; + final Color infoThickHover; + final Color successLight; + final Color successLightHover; + final Color successThick; + final Color successThickHover; + final Color warningLight; + final Color warningLightHover; + final Color warningThick; + final Color warningThickHover; + final Color errorLight; + final Color errorLightHover; + final Color errorThick; + final Color errorThickHover; + final Color errorSelect; + final Color purpleLight; + final Color purpleLightHover; + final Color purpleThick; + final Color purpleThickHover; + + AppFlowyFillColorScheme lerp( + AppFlowyFillColorScheme other, + double t, + ) { + return AppFlowyFillColorScheme( + primary: Color.lerp(primary, other.primary, t)!, + primaryHover: Color.lerp(primaryHover, other.primaryHover, t)!, + secondary: Color.lerp(secondary, other.secondary, t)!, + secondaryHover: Color.lerp(secondaryHover, other.secondaryHover, t)!, + tertiary: Color.lerp(tertiary, other.tertiary, t)!, + tertiaryHover: Color.lerp(tertiaryHover, other.tertiaryHover, t)!, + quaternary: Color.lerp(quaternary, other.quaternary, t)!, + quaternaryHover: Color.lerp(quaternaryHover, other.quaternaryHover, t)!, + transparent: Color.lerp(transparent, other.transparent, t)!, + primaryAlpha5: Color.lerp(primaryAlpha5, other.primaryAlpha5, t)!, + primaryAlpha5Hover: + Color.lerp(primaryAlpha5Hover, other.primaryAlpha5Hover, t)!, + primaryAlpha80: Color.lerp(primaryAlpha80, other.primaryAlpha80, t)!, + primaryAlpha80Hover: + Color.lerp(primaryAlpha80Hover, other.primaryAlpha80Hover, t)!, + white: Color.lerp(white, other.white, t)!, + whiteAlpha: Color.lerp(whiteAlpha, other.whiteAlpha, t)!, + whiteAlphaHover: Color.lerp(whiteAlphaHover, other.whiteAlphaHover, t)!, + black: Color.lerp(black, other.black, t)!, + themeLight: Color.lerp(themeLight, other.themeLight, t)!, + themeLightHover: Color.lerp(themeLightHover, other.themeLightHover, t)!, + themeThick: Color.lerp(themeThick, other.themeThick, t)!, + themeThickHover: Color.lerp(themeThickHover, other.themeThickHover, t)!, + themeSelect: Color.lerp(themeSelect, other.themeSelect, t)!, + infoLight: Color.lerp(infoLight, other.infoLight, t)!, + infoLightHover: Color.lerp(infoLightHover, other.infoLightHover, t)!, + infoThick: Color.lerp(infoThick, other.infoThick, t)!, + infoThickHover: Color.lerp(infoThickHover, other.infoThickHover, t)!, + successLight: Color.lerp(successLight, other.successLight, t)!, + successLightHover: + Color.lerp(successLightHover, other.successLightHover, t)!, + successThick: Color.lerp(successThick, other.successThick, t)!, + successThickHover: + Color.lerp(successThickHover, other.successThickHover, t)!, + warningLight: Color.lerp(warningLight, other.warningLight, t)!, + warningLightHover: + Color.lerp(warningLightHover, other.warningLightHover, t)!, + warningThick: Color.lerp(warningThick, other.warningThick, t)!, + warningThickHover: + Color.lerp(warningThickHover, other.warningThickHover, t)!, + errorLight: Color.lerp(errorLight, other.errorLight, t)!, + errorLightHover: Color.lerp(errorLightHover, other.errorLightHover, t)!, + errorThick: Color.lerp(errorThick, other.errorThick, t)!, + errorThickHover: Color.lerp(errorThickHover, other.errorThickHover, t)!, + errorSelect: Color.lerp(errorSelect, other.errorSelect, t)!, + purpleLight: Color.lerp(purpleLight, other.purpleLight, t)!, + purpleLightHover: + Color.lerp(purpleLightHover, other.purpleLightHover, t)!, + purpleThick: Color.lerp(purpleThick, other.purpleThick, t)!, + purpleThickHover: + Color.lerp(purpleThickHover, other.purpleThickHover, t)!, + ); + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/color_scheme/icon_color_scheme.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/color_scheme/icon_color_scheme.dart new file mode 100644 index 0000000000..efe59b8b99 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/color_scheme/icon_color_scheme.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; + +class AppFlowyIconColorScheme { + const AppFlowyIconColorScheme({ + required this.primary, + required this.secondary, + required this.tertiary, + required this.quaternary, + required this.white, + required this.purpleThick, + required this.purpleThickHover, + }); + + final Color primary; + final Color secondary; + final Color tertiary; + final Color quaternary; + final Color white; + final Color purpleThick; + final Color purpleThickHover; + + AppFlowyIconColorScheme lerp( + AppFlowyIconColorScheme other, + double t, + ) { + return AppFlowyIconColorScheme( + primary: Color.lerp(primary, other.primary, t)!, + secondary: Color.lerp(secondary, other.secondary, t)!, + tertiary: Color.lerp(tertiary, other.tertiary, t)!, + quaternary: Color.lerp(quaternary, other.quaternary, t)!, + white: Color.lerp(white, other.white, t)!, + purpleThick: Color.lerp(purpleThick, other.purpleThick, t)!, + purpleThickHover: + Color.lerp(purpleThickHover, other.purpleThickHover, t)!, + ); + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/color_scheme/other_color_scheme.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/color_scheme/other_color_scheme.dart new file mode 100644 index 0000000000..9bb21e54e6 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/color_scheme/other_color_scheme.dart @@ -0,0 +1,18 @@ +import 'dart:ui'; + +class AppFlowyOtherColorsColorScheme { + const AppFlowyOtherColorsColorScheme({ + required this.textHighlight, + }); + + final Color textHighlight; + + AppFlowyOtherColorsColorScheme lerp( + AppFlowyOtherColorsColorScheme other, + double t, + ) { + return AppFlowyOtherColorsColorScheme( + textHighlight: Color.lerp(textHighlight, other.textHighlight, t)!, + ); + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/color_scheme/surface_color_scheme.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/color_scheme/surface_color_scheme.dart new file mode 100644 index 0000000000..67be450a04 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/color_scheme/surface_color_scheme.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; + +class AppFlowySurfaceColorScheme { + const AppFlowySurfaceColorScheme({ + required this.primary, + required this.overlay, + }); + + final Color primary; + final Color overlay; + + AppFlowySurfaceColorScheme lerp( + AppFlowySurfaceColorScheme other, + double t, + ) { + return AppFlowySurfaceColorScheme( + primary: Color.lerp(primary, other.primary, t)!, + overlay: Color.lerp(overlay, other.overlay, t)!, + ); + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/color_scheme/text_color_scheme.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/color_scheme/text_color_scheme.dart new file mode 100644 index 0000000000..17e1f057ce --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/color_scheme/text_color_scheme.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; + +class AppFlowyTextColorScheme { + const AppFlowyTextColorScheme({ + required this.primary, + required this.secondary, + required this.tertiary, + required this.quaternary, + required this.inverse, + required this.onFill, + required this.theme, + required this.themeHover, + required this.action, + required this.actionHover, + required this.info, + required this.infoHover, + required this.success, + required this.successHover, + required this.warning, + required this.warningHover, + required this.error, + required this.errorHover, + required this.purple, + required this.purpleHover, + }); + + final Color primary; + final Color secondary; + final Color tertiary; + final Color quaternary; + final Color inverse; + final Color onFill; + final Color theme; + final Color themeHover; + final Color action; + final Color actionHover; + final Color info; + final Color infoHover; + final Color success; + final Color successHover; + final Color warning; + final Color warningHover; + final Color error; + final Color errorHover; + final Color purple; + final Color purpleHover; + + AppFlowyTextColorScheme lerp( + AppFlowyTextColorScheme other, + double t, + ) { + return AppFlowyTextColorScheme( + primary: Color.lerp(primary, other.primary, t)!, + secondary: Color.lerp(secondary, other.secondary, t)!, + tertiary: Color.lerp(tertiary, other.tertiary, t)!, + quaternary: Color.lerp(quaternary, other.quaternary, t)!, + inverse: Color.lerp(inverse, other.inverse, t)!, + onFill: Color.lerp(onFill, other.onFill, t)!, + theme: Color.lerp(theme, other.theme, t)!, + themeHover: Color.lerp(themeHover, other.themeHover, t)!, + action: Color.lerp(action, other.action, t)!, + actionHover: Color.lerp(actionHover, other.actionHover, t)!, + info: Color.lerp(info, other.info, t)!, + infoHover: Color.lerp(infoHover, other.infoHover, t)!, + success: Color.lerp(success, other.success, t)!, + successHover: Color.lerp(successHover, other.successHover, t)!, + warning: Color.lerp(warning, other.warning, t)!, + warningHover: Color.lerp(warningHover, other.warningHover, t)!, + error: Color.lerp(error, other.error, t)!, + errorHover: Color.lerp(errorHover, other.errorHover, t)!, + purple: Color.lerp(purple, other.purple, t)!, + purpleHover: Color.lerp(purpleHover, other.purpleHover, t)!, + ); + } +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/shadow/shadow.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/shadow/shadow.dart new file mode 100644 index 0000000000..457b86265e --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/shadow/shadow.dart @@ -0,0 +1,11 @@ +import 'package:flutter/widgets.dart'; + +class AppFlowyShadow { + AppFlowyShadow({ + required this.small, + required this.medium, + }); + + final List small; + final List medium; +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/spacing/spacing.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/spacing/spacing.dart new file mode 100644 index 0000000000..ea90784db3 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/spacing/spacing.dart @@ -0,0 +1,17 @@ +class AppFlowySpacing { + const AppFlowySpacing({ + required this.xs, + required this.s, + required this.m, + required this.l, + required this.xl, + required this.xxl, + }); + + final double xs; + final double s; + final double m; + final double l; + final double xl; + final double xxl; +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/text_style/base/default_text_style.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/text_style/base/default_text_style.dart new file mode 100644 index 0000000000..006f364f96 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/text_style/base/default_text_style.dart @@ -0,0 +1,537 @@ +import 'package:flutter/widgets.dart'; + +abstract class TextThemeType { + const TextThemeType({ + required this.fontFamily, + }); + + final String fontFamily; + + TextStyle standard({ + String? family, + Color? color, + FontWeight? weight, + }); + + TextStyle enhanced({ + String? family, + Color? color, + FontWeight? weight, + }); + + TextStyle prominent({ + String? family, + Color? color, + FontWeight? weight, + }); + + TextStyle underline({ + String? family, + Color? color, + FontWeight? weight, + }); +} + +class TextThemeHeading1 extends TextThemeType { + const TextThemeHeading1({ + required super.fontFamily, + }); + + @override + TextStyle standard({ + String? family, + Color? color, + FontWeight? weight, + }) => + _defaultTextStyle( + family: family ?? super.fontFamily, + fontSize: 36, + height: 40 / 36, + color: color, + weight: weight ?? FontWeight.w400, + ); + + @override + TextStyle enhanced({ + String? family, + Color? color, + FontWeight? weight, + }) => + _defaultTextStyle( + family: family ?? super.fontFamily, + fontSize: 36, + height: 40 / 36, + color: color, + weight: weight ?? FontWeight.w600, + ); + + @override + TextStyle prominent({ + String? family, + Color? color, + FontWeight? weight, + }) => + _defaultTextStyle( + family: family ?? super.fontFamily, + fontSize: 36, + height: 40 / 36, + color: color, + weight: weight ?? FontWeight.w700, + ); + + @override + TextStyle underline({ + String? family, + Color? color, + FontWeight? weight, + }) => + _defaultTextStyle( + family: family ?? super.fontFamily, + fontSize: 36, + height: 40 / 36, + color: color, + weight: weight ?? FontWeight.bold, + decoration: TextDecoration.underline, + ); + + static TextStyle _defaultTextStyle({ + required String family, + required double fontSize, + required double height, + TextDecoration decoration = TextDecoration.none, + Color? color, + FontWeight weight = FontWeight.bold, + }) => + TextStyle( + inherit: false, + fontSize: fontSize, + decoration: decoration, + fontStyle: FontStyle.normal, + fontWeight: weight, + height: height, + fontFamily: family, + color: color, + textBaseline: TextBaseline.alphabetic, + leadingDistribution: TextLeadingDistribution.even, + ); +} + +class TextThemeHeading2 extends TextThemeType { + const TextThemeHeading2({ + required super.fontFamily, + }); + + @override + TextStyle standard({String? family, Color? color, FontWeight? weight}) => + _defaultTextStyle( + family: family ?? super.fontFamily, + color: color, + weight: weight ?? FontWeight.w400, + ); + + @override + TextStyle enhanced({String? family, Color? color, FontWeight? weight}) => + _defaultTextStyle( + family: family ?? super.fontFamily, + color: color, + weight: weight ?? FontWeight.w600, + ); + + @override + TextStyle prominent({String? family, Color? color, FontWeight? weight}) => + _defaultTextStyle( + family: family ?? super.fontFamily, + color: color, + weight: weight ?? FontWeight.w700, + ); + + @override + TextStyle underline({String? family, Color? color, FontWeight? weight}) => + _defaultTextStyle( + family: family ?? super.fontFamily, + color: color, + weight: weight ?? FontWeight.w400, + decoration: TextDecoration.underline, + ); + + static TextStyle _defaultTextStyle({ + required String family, + double fontSize = 24, + double height = 32 / 24, + TextDecoration decoration = TextDecoration.none, + FontWeight weight = FontWeight.w400, + Color? color, + }) => + TextStyle( + inherit: false, + fontSize: fontSize, + decoration: decoration, + fontStyle: FontStyle.normal, + fontWeight: weight, + height: height, + fontFamily: family, + color: color, + textBaseline: TextBaseline.alphabetic, + leadingDistribution: TextLeadingDistribution.even, + ); +} + +class TextThemeHeading3 extends TextThemeType { + const TextThemeHeading3({ + required super.fontFamily, + }); + + @override + TextStyle standard({String? family, Color? color, FontWeight? weight}) => + _defaultTextStyle( + family: family ?? super.fontFamily, + color: color, + weight: weight ?? FontWeight.w400, + ); + + @override + TextStyle enhanced({String? family, Color? color, FontWeight? weight}) => + _defaultTextStyle( + family: family ?? super.fontFamily, + color: color, + weight: weight ?? FontWeight.w600, + ); + + @override + TextStyle prominent({String? family, Color? color, FontWeight? weight}) => + _defaultTextStyle( + family: family ?? super.fontFamily, + color: color, + weight: weight ?? FontWeight.w700, + ); + + @override + TextStyle underline({String? family, Color? color, FontWeight? weight}) => + _defaultTextStyle( + family: family ?? super.fontFamily, + color: color, + weight: weight ?? FontWeight.w400, + decoration: TextDecoration.underline, + ); + + static TextStyle _defaultTextStyle({ + required String family, + double fontSize = 20, + double height = 28 / 20, + TextDecoration decoration = TextDecoration.none, + FontWeight weight = FontWeight.w400, + Color? color, + }) => + TextStyle( + inherit: false, + fontSize: fontSize, + decoration: decoration, + fontStyle: FontStyle.normal, + fontWeight: weight, + height: height, + fontFamily: family, + color: color, + textBaseline: TextBaseline.alphabetic, + leadingDistribution: TextLeadingDistribution.even, + ); +} + +class TextThemeHeading4 extends TextThemeType { + const TextThemeHeading4({ + required super.fontFamily, + }); + + @override + TextStyle standard({String? family, Color? color, FontWeight? weight}) => + _defaultTextStyle( + family: family ?? super.fontFamily, + color: color, + weight: weight ?? FontWeight.w400, + ); + + @override + TextStyle enhanced({String? family, Color? color, FontWeight? weight}) => + _defaultTextStyle( + family: family ?? super.fontFamily, + color: color, + weight: weight ?? FontWeight.w600, + ); + + @override + TextStyle prominent({String? family, Color? color, FontWeight? weight}) => + _defaultTextStyle( + family: family ?? super.fontFamily, + color: color, + weight: weight ?? FontWeight.w700, + ); + + @override + TextStyle underline({String? family, Color? color, FontWeight? weight}) => + _defaultTextStyle( + family: family ?? super.fontFamily, + color: color, + weight: weight ?? FontWeight.w400, + decoration: TextDecoration.underline, + ); + + static TextStyle _defaultTextStyle({ + required String family, + double fontSize = 16, + double height = 22 / 16, + TextDecoration decoration = TextDecoration.none, + FontWeight weight = FontWeight.w400, + Color? color, + }) => + TextStyle( + inherit: false, + fontSize: fontSize, + decoration: decoration, + fontStyle: FontStyle.normal, + fontWeight: weight, + height: height, + fontFamily: family, + color: color, + textBaseline: TextBaseline.alphabetic, + leadingDistribution: TextLeadingDistribution.even, + ); +} + +class TextThemeHeadline extends TextThemeType { + const TextThemeHeadline({ + required super.fontFamily, + }); + + @override + TextStyle standard({String? family, Color? color, FontWeight? weight}) => + _defaultTextStyle( + family: family ?? super.fontFamily, + color: color, + weight: weight ?? FontWeight.normal, + ); + + @override + TextStyle enhanced({String? family, Color? color, FontWeight? weight}) => + _defaultTextStyle( + family: family ?? super.fontFamily, + color: color, + weight: weight ?? FontWeight.w600, + ); + + @override + TextStyle prominent({String? family, Color? color, FontWeight? weight}) => + _defaultTextStyle( + family: family ?? super.fontFamily, + color: color, + weight: weight ?? FontWeight.bold, + ); + + @override + TextStyle underline({String? family, Color? color, FontWeight? weight}) => + _defaultTextStyle( + family: family ?? super.fontFamily, + color: color, + weight: weight ?? FontWeight.normal, + decoration: TextDecoration.underline, + ); + + static TextStyle _defaultTextStyle({ + required String family, + double fontSize = 24, + double height = 36 / 24, + TextDecoration decoration = TextDecoration.none, + FontWeight weight = FontWeight.normal, + Color? color, + }) => + TextStyle( + inherit: false, + fontSize: fontSize, + decoration: decoration, + fontStyle: FontStyle.normal, + fontWeight: weight, + height: height, + fontFamily: family, + textBaseline: TextBaseline.alphabetic, + leadingDistribution: TextLeadingDistribution.even, + color: color, + ); +} + +class TextThemeTitle extends TextThemeType { + const TextThemeTitle({ + required super.fontFamily, + }); + + @override + TextStyle standard({String? family, Color? color, FontWeight? weight}) => + _defaultTextStyle( + family: family ?? super.fontFamily, + color: color, + weight: weight ?? FontWeight.normal, + ); + + @override + TextStyle enhanced({String? family, Color? color, FontWeight? weight}) => + _defaultTextStyle( + family: family ?? super.fontFamily, + color: color, + weight: weight ?? FontWeight.w600, + ); + + @override + TextStyle prominent({String? family, Color? color, FontWeight? weight}) => + _defaultTextStyle( + family: family ?? super.fontFamily, + color: color, + weight: weight ?? FontWeight.bold, + ); + + @override + TextStyle underline({String? family, Color? color, FontWeight? weight}) => + _defaultTextStyle( + family: family ?? super.fontFamily, + color: color, + weight: weight ?? FontWeight.normal, + decoration: TextDecoration.underline, + ); + + static TextStyle _defaultTextStyle({ + required String family, + double fontSize = 20, + double height = 28 / 20, + FontWeight weight = FontWeight.normal, + TextDecoration decoration = TextDecoration.none, + Color? color, + }) => + TextStyle( + inherit: false, + fontSize: fontSize, + decoration: decoration, + fontStyle: FontStyle.normal, + fontWeight: weight, + height: height, + fontFamily: family, + textBaseline: TextBaseline.alphabetic, + leadingDistribution: TextLeadingDistribution.even, + color: color, + ); +} + +class TextThemeBody extends TextThemeType { + const TextThemeBody({ + required super.fontFamily, + }); + + @override + TextStyle standard({String? family, Color? color, FontWeight? weight}) => + _defaultTextStyle( + family: family ?? super.fontFamily, + color: color, + weight: weight ?? FontWeight.normal, + ); + + @override + TextStyle enhanced({String? family, Color? color, FontWeight? weight}) => + _defaultTextStyle( + family: family ?? super.fontFamily, + color: color, + weight: weight ?? FontWeight.w600, + ); + + @override + TextStyle prominent({String? family, Color? color, FontWeight? weight}) => + _defaultTextStyle( + family: family ?? super.fontFamily, + color: color, + weight: weight ?? FontWeight.bold, + ); + + @override + TextStyle underline({String? family, Color? color, FontWeight? weight}) => + _defaultTextStyle( + family: family ?? super.fontFamily, + color: color, + weight: weight ?? FontWeight.normal, + decoration: TextDecoration.underline, + ); + + static TextStyle _defaultTextStyle({ + required String family, + double fontSize = 14, + double height = 20 / 14, + FontWeight weight = FontWeight.normal, + TextDecoration decoration = TextDecoration.none, + Color? color, + }) => + TextStyle( + inherit: false, + fontSize: fontSize, + decoration: decoration, + fontStyle: FontStyle.normal, + fontWeight: weight, + height: height, + fontFamily: family, + textBaseline: TextBaseline.alphabetic, + leadingDistribution: TextLeadingDistribution.even, + color: color, + ); +} + +class TextThemeCaption extends TextThemeType { + const TextThemeCaption({ + required super.fontFamily, + }); + + @override + TextStyle standard({String? family, Color? color, FontWeight? weight}) => + _defaultTextStyle( + family: family ?? super.fontFamily, + color: color, + weight: weight ?? FontWeight.normal, + ); + + @override + TextStyle enhanced({String? family, Color? color, FontWeight? weight}) => + _defaultTextStyle( + family: family ?? super.fontFamily, + color: color, + weight: weight ?? FontWeight.w600, + ); + + @override + TextStyle prominent({String? family, Color? color, FontWeight? weight}) => + _defaultTextStyle( + family: family ?? super.fontFamily, + color: color, + weight: weight ?? FontWeight.bold, + ); + + @override + TextStyle underline({String? family, Color? color, FontWeight? weight}) => + _defaultTextStyle( + family: family ?? super.fontFamily, + color: color, + weight: weight ?? FontWeight.normal, + decoration: TextDecoration.underline, + ); + + static TextStyle _defaultTextStyle({ + required String family, + double fontSize = 12, + double height = 16 / 12, + FontWeight weight = FontWeight.normal, + TextDecoration decoration = TextDecoration.none, + Color? color, + }) => + TextStyle( + inherit: false, + fontSize: fontSize, + decoration: decoration, + fontStyle: FontStyle.normal, + fontWeight: weight, + height: height, + fontFamily: family, + textBaseline: TextBaseline.alphabetic, + leadingDistribution: TextLeadingDistribution.even, + color: color, + ); +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/text_style/text_style.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/text_style/text_style.dart new file mode 100644 index 0000000000..89c1278d93 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/text_style/text_style.dart @@ -0,0 +1,35 @@ +import 'package:appflowy_ui/src/theme/definition/text_style/base/default_text_style.dart'; + +class AppFlowyBaseTextStyle { + factory AppFlowyBaseTextStyle.customFontFamily(String fontFamily) => + AppFlowyBaseTextStyle( + heading1: TextThemeHeading1(fontFamily: fontFamily), + heading2: TextThemeHeading2(fontFamily: fontFamily), + heading3: TextThemeHeading3(fontFamily: fontFamily), + heading4: TextThemeHeading4(fontFamily: fontFamily), + headline: TextThemeHeadline(fontFamily: fontFamily), + title: TextThemeTitle(fontFamily: fontFamily), + body: TextThemeBody(fontFamily: fontFamily), + caption: TextThemeCaption(fontFamily: fontFamily), + ); + + const AppFlowyBaseTextStyle({ + this.heading1 = const TextThemeHeading1(fontFamily: ''), + this.heading2 = const TextThemeHeading2(fontFamily: ''), + this.heading3 = const TextThemeHeading3(fontFamily: ''), + this.heading4 = const TextThemeHeading4(fontFamily: ''), + this.headline = const TextThemeHeadline(fontFamily: ''), + this.title = const TextThemeTitle(fontFamily: ''), + this.body = const TextThemeBody(fontFamily: ''), + this.caption = const TextThemeCaption(fontFamily: ''), + }); + + final TextThemeType heading1; + final TextThemeType heading2; + final TextThemeType heading3; + final TextThemeType heading4; + final TextThemeType headline; + final TextThemeType title; + final TextThemeType body; + final TextThemeType caption; +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/theme_data.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/theme_data.dart new file mode 100644 index 0000000000..1da45cfd2a --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/definition/theme_data.dart @@ -0,0 +1,91 @@ +import 'border_radius/border_radius.dart'; +import 'color_scheme/color_scheme.dart'; +import 'shadow/shadow.dart'; +import 'spacing/spacing.dart'; +import 'text_style/text_style.dart'; + +/// [AppFlowyThemeData] defines the structure of the design system, and contains +/// the data that all child widgets will have access to. +class AppFlowyThemeData { + const AppFlowyThemeData({ + required this.textColorScheme, + required this.textStyle, + required this.iconColorScheme, + required this.borderColorScheme, + required this.backgroundColorScheme, + required this.fillColorScheme, + required this.surfaceColorScheme, + required this.borderRadius, + required this.spacing, + required this.shadow, + required this.brandColorScheme, + required this.otherColorsColorScheme, + }); + + final AppFlowyTextColorScheme textColorScheme; + + final AppFlowyBaseTextStyle textStyle; + + final AppFlowyIconColorScheme iconColorScheme; + + final AppFlowyBorderColorScheme borderColorScheme; + + final AppFlowyBackgroundColorScheme backgroundColorScheme; + + final AppFlowyFillColorScheme fillColorScheme; + + final AppFlowySurfaceColorScheme surfaceColorScheme; + + final AppFlowyBorderRadius borderRadius; + + final AppFlowySpacing spacing; + + final AppFlowyShadow shadow; + + final AppFlowyBrandColorScheme brandColorScheme; + + final AppFlowyOtherColorsColorScheme otherColorsColorScheme; + + static AppFlowyThemeData lerp( + AppFlowyThemeData begin, + AppFlowyThemeData end, + double t, + ) { + return AppFlowyThemeData( + textColorScheme: begin.textColorScheme.lerp(end.textColorScheme, t), + textStyle: end.textStyle, + iconColorScheme: begin.iconColorScheme.lerp(end.iconColorScheme, t), + borderColorScheme: begin.borderColorScheme.lerp(end.borderColorScheme, t), + backgroundColorScheme: + begin.backgroundColorScheme.lerp(end.backgroundColorScheme, t), + fillColorScheme: begin.fillColorScheme.lerp(end.fillColorScheme, t), + surfaceColorScheme: + begin.surfaceColorScheme.lerp(end.surfaceColorScheme, t), + borderRadius: end.borderRadius, + spacing: end.spacing, + shadow: end.shadow, + brandColorScheme: begin.brandColorScheme.lerp(end.brandColorScheme, t), + otherColorsColorScheme: + begin.otherColorsColorScheme.lerp(end.otherColorsColorScheme, t), + ); + } +} + +/// [AppFlowyThemeBuilder] is used to build the light and dark themes. Extend +/// this class to create a built-in theme, or use the [CustomTheme] class to +/// create a custom theme from JSON data. +/// +/// See also: +/// +/// - [AppFlowyThemeData] for the main theme data class. +abstract class AppFlowyThemeBuilder { + const AppFlowyThemeBuilder(); + + AppFlowyThemeData light({ + String? fontFamily, + }); + + AppFlowyThemeData dark({ + String? fontFamily, + }); +} diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/theme.dart b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/theme.dart new file mode 100644 index 0000000000..000b7a0372 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/lib/src/theme/theme.dart @@ -0,0 +1,8 @@ +export 'appflowy_theme.dart'; +export 'data/built_in_themes.dart'; +export 'definition/border_radius/border_radius.dart'; +export 'definition/color_scheme/color_scheme.dart'; +export 'definition/theme_data.dart'; +export 'definition/spacing/spacing.dart'; +export 'definition/shadow/shadow.dart'; +export 'definition/text_style/text_style.dart'; diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/pubspec.yaml b/frontend/appflowy_flutter/packages/appflowy_ui/pubspec.yaml new file mode 100644 index 0000000000..2f5633bb1e --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/pubspec.yaml @@ -0,0 +1,17 @@ +name: appflowy_ui +description: "A Flutter package for AppFlowy UI components and widgets" +version: 1.0.0 +homepage: https://github.com/appflowy-io/appflowy + +environment: + sdk: ^3.6.2 + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + flutter_lints: ^5.0.0 + +dev_dependencies: + flutter_test: + sdk: flutter diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/script/Primitive.Mode 1.tokens.json b/frontend/appflowy_flutter/packages/appflowy_ui/script/Primitive.Mode 1.tokens.json new file mode 100644 index 0000000000..c46354b599 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/script/Primitive.Mode 1.tokens.json @@ -0,0 +1,984 @@ +{ + "Neutral": { + "100": { + "$type": "color", + "$value": "#f8faff" + }, + "200": { + "$type": "color", + "$value": "#e4e8f5" + }, + "300": { + "$type": "color", + "$value": "#ced3e6" + }, + "400": { + "$type": "color", + "$value": "#b5bbd3" + }, + "500": { + "$type": "color", + "$value": "#989eb7" + }, + "600": { + "$type": "color", + "$value": "#6f748c" + }, + "700": { + "$type": "color", + "$value": "#54596e" + }, + "800": { + "$type": "color", + "$value": "#3c3f4e" + }, + "900": { + "$type": "color", + "$value": "#272930" + }, + "1000": { + "$type": "color", + "$value": "#21232a" + }, + "black": { + "$type": "color", + "$value": "#000000" + }, + "alpha-black-60": { + "$type": "color", + "$value": "#00000099" + }, + "white": { + "$type": "color", + "$value": "#ffffff" + }, + "alpha-white-0": { + "$type": "color", + "$value": "#ffffff00" + }, + "alpha-white-20": { + "$type": "color", + "$value": "#ffffff33" + }, + "alpha-white-30": { + "$type": "color", + "$value": "#ffffff4d" + }, + "alpha-grey-100-05": { + "$type": "color", + "$value": "#f9fafd0d" + }, + "alpha-grey-100-10": { + "$type": "color", + "$value": "#f9fafd1a" + }, + "alpha-grey-1000-05": { + "$type": "color", + "$value": "#1f23290d" + }, + "alpha-grey-1000-10": { + "$type": "color", + "$value": "#1f23291a" + }, + "alpha-grey-1000-70": { + "$type": "color", + "$value": "#1f2329b2" + }, + "alpha-grey-1000-80": { + "$type": "color", + "$value": "#1f2329cc" + } + }, + "Blue": { + "100": { + "$type": "color", + "$value": "#e3f6ff" + }, + "200": { + "$type": "color", + "$value": "#a9e2ff" + }, + "300": { + "$type": "color", + "$value": "#80d2ff" + }, + "400": { + "$type": "color", + "$value": "#4ec1ff" + }, + "500": { + "$type": "color", + "$value": "#00b5ff" + }, + "600": { + "$type": "color", + "$value": "#0092d6" + }, + "700": { + "$type": "color", + "$value": "#0078c0" + }, + "800": { + "$type": "color", + "$value": "#0065a9" + }, + "900": { + "$type": "color", + "$value": "#00508f" + }, + "1000": { + "$type": "color", + "$value": "#003c77" + }, + "alpha-blue-500-15": { + "$type": "color", + "$value": "#00b5ff26" + } + }, + "Green": { + "100": { + "$type": "color", + "$value": "#ecf9f5" + }, + "200": { + "$type": "color", + "$value": "#c3e5d8" + }, + "300": { + "$type": "color", + "$value": "#9ad1bc" + }, + "400": { + "$type": "color", + "$value": "#71bd9f" + }, + "500": { + "$type": "color", + "$value": "#48a982" + }, + "600": { + "$type": "color", + "$value": "#248569" + }, + "700": { + "$type": "color", + "$value": "#29725d" + }, + "800": { + "$type": "color", + "$value": "#2e6050" + }, + "900": { + "$type": "color", + "$value": "#305548" + }, + "1000": { + "$type": "color", + "$value": "#305244" + } + }, + "Purple": { + "100": { + "$type": "color", + "$value": "#f1e0ff" + }, + "200": { + "$type": "color", + "$value": "#e1b3ff" + }, + "300": { + "$type": "color", + "$value": "#d185ff" + }, + "400": { + "$type": "color", + "$value": "#bc58ff" + }, + "500": { + "$type": "color", + "$value": "#9327ff" + }, + "600": { + "$type": "color", + "$value": "#7a1dcc" + }, + "700": { + "$type": "color", + "$value": "#6617b3" + }, + "800": { + "$type": "color", + "$value": "#55138f" + }, + "900": { + "$type": "color", + "$value": "#470c72" + }, + "1000": { + "$type": "color", + "$value": "#380758" + } + }, + "Magenta": { + "100": { + "$type": "color", + "$value": "#ffe5ef" + }, + "200": { + "$type": "color", + "$value": "#ffb8d1" + }, + "300": { + "$type": "color", + "$value": "#ff8ab2" + }, + "400": { + "$type": "color", + "$value": "#ff5c93" + }, + "500": { + "$type": "color", + "$value": "#fb006d" + }, + "600": { + "$type": "color", + "$value": "#d2005f" + }, + "700": { + "$type": "color", + "$value": "#d2005f" + }, + "800": { + "$type": "color", + "$value": "#850040" + }, + "900": { + "$type": "color", + "$value": "#610031" + }, + "1000": { + "$type": "color", + "$value": "#400022" + } + }, + "Red": { + "100": { + "$type": "color", + "$value": "#ffd2dd" + }, + "200": { + "$type": "color", + "$value": "#ffa5b4" + }, + "300": { + "$type": "color", + "$value": "#ff7d87" + }, + "400": { + "$type": "color", + "$value": "#ff5050" + }, + "500": { + "$type": "color", + "$value": "#f33641" + }, + "600": { + "$type": "color", + "$value": "#e71d32" + }, + "700": { + "$type": "color", + "$value": "#ad1625" + }, + "800": { + "$type": "color", + "$value": "#8c101c" + }, + "900": { + "$type": "color", + "$value": "#6e0a1e" + }, + "1000": { + "$type": "color", + "$value": "#4c0a17" + }, + "alpha-red-500-10": { + "$type": "color", + "$value": "#f336411a" + } + }, + "Orange": { + "100": { + "$type": "color", + "$value": "#fff3d5" + }, + "200": { + "$type": "color", + "$value": "#ffe4ab" + }, + "300": { + "$type": "color", + "$value": "#ffd181" + }, + "400": { + "$type": "color", + "$value": "#ffbe62" + }, + "500": { + "$type": "color", + "$value": "#ffa02e" + }, + "600": { + "$type": "color", + "$value": "#db7e21" + }, + "700": { + "$type": "color", + "$value": "#b75f17" + }, + "800": { + "$type": "color", + "$value": "#93450e" + }, + "900": { + "$type": "color", + "$value": "#7a3108" + }, + "1000": { + "$type": "color", + "$value": "#602706" + } + }, + "Yellow": { + "100": { + "$type": "color", + "$value": "#fff9b2" + }, + "200": { + "$type": "color", + "$value": "#ffec66" + }, + "300": { + "$type": "color", + "$value": "#ffdf1a" + }, + "400": { + "$type": "color", + "$value": "#ffcc00" + }, + "500": { + "$type": "color", + "$value": "#ffce00" + }, + "600": { + "$type": "color", + "$value": "#e6b800" + }, + "700": { + "$type": "color", + "$value": "#cc9f00" + }, + "800": { + "$type": "color", + "$value": "#b38a00" + }, + "900": { + "$type": "color", + "$value": "#9a7500" + }, + "1000": { + "$type": "color", + "$value": "#7f6200" + } + }, + "Subtle_Color": { + "Rose": { + "100": { + "$type": "color", + "$value": "#fcf2f2" + }, + "200": { + "$type": "color", + "$value": "#fae3e3" + }, + "300": { + "$type": "color", + "$value": "#fad9d9" + }, + "400": { + "$type": "color", + "$value": "#edadad" + }, + "500": { + "$type": "color", + "$value": "#cc4e4e" + }, + "600": { + "$type": "color", + "$value": "#702828" + } + }, + "Papaya": { + "100": { + "$type": "color", + "$value": "#fcf4f0" + }, + "200": { + "$type": "color", + "$value": "#fae8de" + }, + "300": { + "$type": "color", + "$value": "#fadfd2" + }, + "400": { + "$type": "color", + "$value": "#f0bda3" + }, + "500": { + "$type": "color", + "$value": "#d67240" + }, + "600": { + "$type": "color", + "$value": "#6b3215" + } + }, + "Tangerine": { + "100": { + "$type": "color", + "$value": "#fff7ed" + }, + "200": { + "$type": "color", + "$value": "#fcedd9" + }, + "300": { + "$type": "color", + "$value": "#fae5ca" + }, + "400": { + "$type": "color", + "$value": "#f2cb99" + }, + "500": { + "$type": "color", + "$value": "#db8f2c" + }, + "600": { + "$type": "color", + "$value": "#613b0a" + } + }, + "Mango": { + "100": { + "$type": "color", + "$value": "#fff9ec" + }, + "200": { + "$type": "color", + "$value": "#fcf1d7" + }, + "300": { + "$type": "color", + "$value": "#fae9c3" + }, + "400": { + "$type": "color", + "$value": "#f5d68e" + }, + "500": { + "$type": "color", + "$value": "#e0a416" + }, + "600": { + "$type": "color", + "$value": "#5c4102" + } + }, + "Lemon": { + "100": { + "$type": "color", + "$value": "#fffbe8" + }, + "200": { + "$type": "color", + "$value": "#fcf5cf" + }, + "300": { + "$type": "color", + "$value": "#faefb9" + }, + "400": { + "$type": "color", + "$value": "#f5e282" + }, + "500": { + "$type": "color", + "$value": "#e0bb00" + }, + "600": { + "$type": "color", + "$value": "#574800" + } + }, + "Olive": { + "100": { + "$type": "color", + "$value": "#f9fae6" + }, + "200": { + "$type": "color", + "$value": "#f6f7d0" + }, + "300": { + "$type": "color", + "$value": "#f0f2b3" + }, + "400": { + "$type": "color", + "$value": "#dbde83" + }, + "500": { + "$type": "color", + "$value": "#adb204" + }, + "600": { + "$type": "color", + "$value": "#4a4c03" + } + }, + "Lime": { + "100": { + "$type": "color", + "$value": "#f6f9e6" + }, + "200": { + "$type": "color", + "$value": "#eef5ce" + }, + "300": { + "$type": "color", + "$value": "#e7f0bb" + }, + "400": { + "$type": "color", + "$value": "#cfdb91" + }, + "500": { + "$type": "color", + "$value": "#92a822" + }, + "600": { + "$type": "color", + "$value": "#414d05" + } + }, + "Grass": { + "100": { + "$type": "color", + "$value": "#f4faeb" + }, + "200": { + "$type": "color", + "$value": "#e9f5d7" + }, + "300": { + "$type": "color", + "$value": "#def0c5" + }, + "400": { + "$type": "color", + "$value": "#bfd998" + }, + "500": { + "$type": "color", + "$value": "#75a828" + }, + "600": { + "$type": "color", + "$value": "#334d0c" + } + }, + "Forest": { + "100": { + "$type": "color", + "$value": "#f1faf0" + }, + "200": { + "$type": "color", + "$value": "#e2f5df" + }, + "300": { + "$type": "color", + "$value": "#d7f0d3" + }, + "400": { + "$type": "color", + "$value": "#a8d6a1" + }, + "500": { + "$type": "color", + "$value": "#49a33b" + }, + "600": { + "$type": "color", + "$value": "#1e4f16" + } + }, + "Jade": { + "100": { + "$type": "color", + "$value": "#f0faf6" + }, + "200": { + "$type": "color", + "$value": "#dff5eb" + }, + "300": { + "$type": "color", + "$value": "#cef0e1" + }, + "400": { + "$type": "color", + "$value": "#90d1b5" + }, + "500": { + "$type": "color", + "$value": "#1c9963" + }, + "600": { + "$type": "color", + "$value": "#075231" + } + }, + "Aqua": { + "100": { + "$type": "color", + "$value": "#f0f9fa" + }, + "200": { + "$type": "color", + "$value": "#dff3f5" + }, + "300": { + "$type": "color", + "$value": "#ccecf0" + }, + "400": { + "$type": "color", + "$value": "#83ccd4" + }, + "500": { + "$type": "color", + "$value": "#008e9e" + }, + "600": { + "$type": "color", + "$value": "#004e57" + } + }, + "Azure": { + "100": { + "$type": "color", + "$value": "#f0f6fa" + }, + "200": { + "$type": "color", + "$value": "#e1eef7" + }, + "300": { + "$type": "color", + "$value": "#d3e6f5" + }, + "400": { + "$type": "color", + "$value": "#88c0eb" + }, + "500": { + "$type": "color", + "$value": "#0877cc" + }, + "600": { + "$type": "color", + "$value": "#154469" + } + }, + "Denim": { + "100": { + "$type": "color", + "$value": "#f0f3fa" + }, + "200": { + "$type": "color", + "$value": "#e3ebfa" + }, + "300": { + "$type": "color", + "$value": "#d7e2f7" + }, + "400": { + "$type": "color", + "$value": "#9ab6ed" + }, + "500": { + "$type": "color", + "$value": "#3267d1" + }, + "600": { + "$type": "color", + "$value": "#223c70" + } + }, + "Mauve": { + "100": { + "$type": "color", + "$value": "#f2f2fc" + }, + "200": { + "$type": "color", + "$value": "#e6e6fa" + }, + "300": { + "$type": "color", + "$value": "#dcdcf7" + }, + "400": { + "$type": "color", + "$value": "#aeaef5" + }, + "500": { + "$type": "color", + "$value": "#5555e0" + }, + "600": { + "$type": "color", + "$value": "#36366b" + } + }, + "Lavender": { + "100": { + "$type": "color", + "$value": "#f6f3fc" + }, + "200": { + "$type": "color", + "$value": "#ebe3fa" + }, + "300": { + "$type": "color", + "$value": "#e4daf7" + }, + "400": { + "$type": "color", + "$value": "#c1aaf0" + }, + "500": { + "$type": "color", + "$value": "#8153db" + }, + "600": { + "$type": "color", + "$value": "#462f75" + } + }, + "Lilac": { + "100": { + "$type": "color", + "$value": "#f7f0fa" + }, + "200": { + "$type": "color", + "$value": "#f0e1f7" + }, + "300": { + "$type": "color", + "$value": "#edd7f7" + }, + "400": { + "$type": "color", + "$value": "#d3a9e8" + }, + "500": { + "$type": "color", + "$value": "#9e4cc7" + }, + "600": { + "$type": "color", + "$value": "#562d6b" + } + }, + "Mallow": { + "100": { + "$type": "color", + "$value": "#faf0fa" + }, + "200": { + "$type": "color", + "$value": "#f5e1f4" + }, + "300": { + "$type": "color", + "$value": "#f5d7f4" + }, + "400": { + "$type": "color", + "$value": "#dea4dc" + }, + "500": { + "$type": "color", + "$value": "#b240af" + }, + "600": { + "$type": "color", + "$value": "#632861" + } + }, + "Camellia": { + "100": { + "$type": "color", + "$value": "#f9eff3" + }, + "200": { + "$type": "color", + "$value": "#f7e1eb" + }, + "300": { + "$type": "color", + "$value": "#f7d7e5" + }, + "400": { + "$type": "color", + "$value": "#e5a3c0" + }, + "500": { + "$type": "color", + "$value": "#c24279" + }, + "600": { + "$type": "color", + "$value": "#6e2343" + } + }, + "Smoke": { + "100": { + "$type": "color", + "$value": "#f5f5f5" + }, + "200": { + "$type": "color", + "$value": "#e8e8e8" + }, + "300": { + "$type": "color", + "$value": "#dedede" + }, + "400": { + "$type": "color", + "$value": "#b8b8b8" + }, + "500": { + "$type": "color", + "$value": "#6e6e6e" + }, + "600": { + "$type": "color", + "$value": "#404040" + } + }, + "Iron": { + "100": { + "$type": "color", + "$value": "#f2f4f7" + }, + "200": { + "$type": "color", + "$value": "#e6e9f0" + }, + "300": { + "$type": "color", + "$value": "#dadee5" + }, + "400": { + "$type": "color", + "$value": "#b0b5bf" + }, + "500": { + "$type": "color", + "$value": "#666f80" + }, + "600": { + "$type": "color", + "$value": "#394152" + } + } + }, + "Spacing": { + "0": { + "$type": "dimension", + "$value": "0px" + }, + "100": { + "$type": "dimension", + "$value": "4px" + }, + "200": { + "$type": "dimension", + "$value": "6px" + }, + "300": { + "$type": "dimension", + "$value": "8px" + }, + "400": { + "$type": "dimension", + "$value": "12px" + }, + "500": { + "$type": "dimension", + "$value": "16px" + }, + "600": { + "$type": "dimension", + "$value": "20px" + }, + "1000": { + "$type": "dimension", + "$value": "1000px" + } + }, + "Border-Radius": { + "0": { + "$type": "dimension", + "$value": "0px" + }, + "100": { + "$type": "dimension", + "$value": "4px" + }, + "200": { + "$type": "dimension", + "$value": "6px" + }, + "300": { + "$type": "dimension", + "$value": "8px" + }, + "400": { + "$type": "dimension", + "$value": "12px" + }, + "500": { + "$type": "dimension", + "$value": "16px" + }, + "600": { + "$type": "dimension", + "$value": "20px" + }, + "1000": { + "$type": "dimension", + "$value": "1000px" + } + } +} \ No newline at end of file diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/script/Semantic.Dark Mode.tokens.json b/frontend/appflowy_flutter/packages/appflowy_ui/script/Semantic.Dark Mode.tokens.json new file mode 100644 index 0000000000..99d266c008 --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/script/Semantic.Dark Mode.tokens.json @@ -0,0 +1,1039 @@ +{ + "Text": { + "primary": { + "$type": "color", + "$value": "{Neutral.200}" + }, + "secondary": { + "$type": "color", + "$value": "{Neutral.400}" + }, + "tertiary": { + "$type": "color", + "$value": "{Neutral.600}" + }, + "quaternary": { + "$type": "color", + "$value": "{Neutral.1000}" + }, + "inverse": { + "$type": "color", + "$value": "{Neutral.1000}" + }, + "on-fill": { + "$type": "color", + "$value": "{Neutral.white}" + }, + "theme": { + "$type": "color", + "$value": "{Blue.500}" + }, + "theme-hover": { + "$type": "color", + "$value": "{Blue.600}" + }, + "action": { + "$type": "color", + "$value": "{Blue.500}" + }, + "action-hover": { + "$type": "color", + "$value": "{Blue.600}" + }, + "info": { + "$type": "color", + "$value": "{Blue.500}" + }, + "info-hover": { + "$type": "color", + "$value": "{Blue.600}" + }, + "success": { + "$type": "color", + "$value": "{Green.600}" + }, + "success-hover": { + "$type": "color", + "$value": "{Green.700}" + }, + "warning": { + "$type": "color", + "$value": "{Orange.600}" + }, + "warning-hover": { + "$type": "color", + "$value": "{Orange.700}" + }, + "error": { + "$type": "color", + "$value": "{Red.500}" + }, + "error-hover": { + "$type": "color", + "$value": "{Red.400}" + }, + "purple": { + "$type": "color", + "$value": "{Purple.500}" + }, + "purple-hover": { + "$type": "color", + "$value": "{Purple.600}" + } + }, + "Icon": { + "primary": { + "$type": "color", + "$value": "{Neutral.200}" + }, + "secondary": { + "$type": "color", + "$value": "{Neutral.400}" + }, + "tertiary": { + "$type": "color", + "$value": "{Neutral.600}" + }, + "quaternary": { + "$type": "color", + "$value": "{Neutral.1000}" + }, + "white": { + "$type": "color", + "$value": "{Neutral.white}" + }, + "purple-thick": { + "$type": "color", + "$value": "#ffffff" + }, + "purple-thick-hover": { + "$type": "color", + "$value": "#ffffff" + } + }, + "Border": { + "grey-primary": { + "$type": "color", + "$value": "{Neutral.100}" + }, + "grey-primary-hover": { + "$type": "color", + "$value": "{Neutral.200}" + }, + "grey-secondary": { + "$type": "color", + "$value": "{Neutral.300}" + }, + "grey-secondary-hover": { + "$type": "color", + "$value": "{Neutral.400}" + }, + "grey-tertiary": { + "$type": "color", + "$value": "{Neutral.800}" + }, + "grey-tertiary-hover": { + "$type": "color", + "$value": "{Neutral.700}" + }, + "grey-quaternary": { + "$type": "color", + "$value": "{Neutral.1000}" + }, + "grey-quaternary-hover": { + "$type": "color", + "$value": "{Neutral.900}" + }, + "transparent": { + "$type": "color", + "$value": "{Neutral.alpha-white-0}" + }, + "theme-thick": { + "$type": "color", + "$value": "{Blue.500}" + }, + "theme-thick-hover": { + "$type": "color", + "$value": "{Blue.600}" + }, + "info-thick": { + "$type": "color", + "$value": "{Blue.500}" + }, + "info-thick-hover": { + "$type": "color", + "$value": "{Blue.600}" + }, + "success-thick": { + "$type": "color", + "$value": "{Green.600}" + }, + "success-thick-hover": { + "$type": "color", + "$value": "{Green.700}" + }, + "warning-thick": { + "$type": "color", + "$value": "{Orange.600}" + }, + "warning-thick-hover": { + "$type": "color", + "$value": "{Orange.700}" + }, + "error-thick": { + "$type": "color", + "$value": "{Red.500}" + }, + "error-thick-hover": { + "$type": "color", + "$value": "{Red.400}" + }, + "purple-thick": { + "$type": "color", + "$value": "{Purple.500}" + }, + "purple-thick-hover": { + "$type": "color", + "$value": "{Purple.600}" + } + }, + "Fill": { + "primary": { + "$type": "color", + "$value": "{Neutral.100}" + }, + "primary-hover": { + "$type": "color", + "$value": "{Neutral.200}" + }, + "secondary": { + "$type": "color", + "$value": "{Neutral.300}" + }, + "secondary-hover": { + "$type": "color", + "$value": "{Neutral.400}" + }, + "tertiary": { + "$type": "color", + "$value": "{Neutral.600}" + }, + "tertiary-hover": { + "$type": "color", + "$value": "{Neutral.500}" + }, + "quaternary": { + "$type": "color", + "$value": "{Neutral.1000}" + }, + "quaternary-hover": { + "$type": "color", + "$value": "{Neutral.900}" + }, + "transparent": { + "$type": "color", + "$value": "{Neutral.alpha-white-0}" + }, + "primary-alpha-5": { + "$type": "color", + "$value": "{Neutral.alpha-grey-100-05}", + "$description": "Used for hover state, eg. button, navigation item, menu item and grid item." + }, + "primary-alpha-5-hover": { + "$type": "color", + "$value": "{Neutral.alpha-grey-100-10}" + }, + "primary-alpha-80": { + "$type": "color", + "$value": "{Neutral.alpha-grey-1000-80}" + }, + "primary-alpha-80-hover": { + "$type": "color", + "$value": "{Neutral.alpha-grey-1000-70}" + }, + "white": { + "$type": "color", + "$value": "{Neutral.white}" + }, + "white-alpha": { + "$type": "color", + "$value": "{Neutral.alpha-white-20}" + }, + "white-alpha-hover": { + "$type": "color", + "$value": "{Neutral.alpha-white-30}" + }, + "black": { + "$type": "color", + "$value": "{Neutral.black}" + }, + "theme-light": { + "$type": "color", + "$value": "{Blue.100}" + }, + "theme-light-hover": { + "$type": "color", + "$value": "{Blue.200}" + }, + "theme-thick": { + "$type": "color", + "$value": "{Blue.500}" + }, + "theme-thick-hover": { + "$type": "color", + "$value": "{Blue.400}" + }, + "theme-select": { + "$type": "color", + "$value": "{Blue.alpha-blue-500-15}" + }, + "info-light": { + "$type": "color", + "$value": "{Blue.100}" + }, + "info-light-hover": { + "$type": "color", + "$value": "{Blue.200}" + }, + "info-thick": { + "$type": "color", + "$value": "{Blue.500}" + }, + "info-thick-hover": { + "$type": "color", + "$value": "{Blue.600}" + }, + "success-light": { + "$type": "color", + "$value": "{Green.100}" + }, + "success-light-hover": { + "$type": "color", + "$value": "{Green.200}" + }, + "success-thick": { + "$type": "color", + "$value": "{Green.600}" + }, + "success-thick-hover": { + "$type": "color", + "$value": "{Green.700}" + }, + "warning-light": { + "$type": "color", + "$value": "{Orange.100}" + }, + "warning-light-hover": { + "$type": "color", + "$value": "{Orange.200}" + }, + "warning-thick": { + "$type": "color", + "$value": "{Orange.600}" + }, + "warning-thick-hover": { + "$type": "color", + "$value": "{Orange.700}" + }, + "error-light": { + "$type": "color", + "$value": "{Red.100}" + }, + "error-light-hover": { + "$type": "color", + "$value": "{Red.200}" + }, + "error-thick": { + "$type": "color", + "$value": "{Red.600}" + }, + "error-thick-hover": { + "$type": "color", + "$value": "{Red.500}" + }, + "error-select": { + "$type": "color", + "$value": "{Red.alpha-red-500-10}" + }, + "purple-light": { + "$type": "color", + "$value": "{Purple.100}" + }, + "purple-light-hover": { + "$type": "color", + "$value": "{Purple.200}" + }, + "purple-thick-hover": { + "$type": "color", + "$value": "{Purple.600}" + }, + "purple-thick": { + "$type": "color", + "$value": "{Purple.500}" + } + }, + "Surface": { + "primary": { + "$type": "color", + "$value": "{Neutral.900}" + }, + "overlay": { + "$type": "color", + "$value": "{Neutral.alpha-black-60}" + } + }, + "Background": { + "primary": { + "$type": "color", + "$value": "{Neutral.1000}" + }, + "secondary": { + "$type": "color", + "$value": "{Neutral.900}" + }, + "tertiary": { + "$type": "color", + "$value": "{Neutral.800}" + }, + "quaternary": { + "$type": "color", + "$value": "{Neutral.700}" + } + }, + "Badge_Color": { + "Rose": { + "rose-light-1": { + "$type": "color", + "$value": "#fcf2f2" + }, + "rose-light-2": { + "$type": "color", + "$value": "#fae3e3" + }, + "rose-light-3": { + "$type": "color", + "$value": "#fad9d9" + }, + "rose-thick-1": { + "$type": "color", + "$value": "#edadad" + }, + "rose-thick-2": { + "$type": "color", + "$value": "#cc4e4e" + }, + "rose-thick-3": { + "$type": "color", + "$value": "#702828" + } + }, + "Papaya": { + "papaya-light-1": { + "$type": "color", + "$value": "#fcf4f0" + }, + "papaya-light-2": { + "$type": "color", + "$value": "#fae8de" + }, + "papaya-light-3": { + "$type": "color", + "$value": "#fadfd2" + }, + "papaya-thick-1": { + "$type": "color", + "$value": "#f0bda3" + }, + "papaya-thick-2": { + "$type": "color", + "$value": "#d67240" + }, + "papaya-thick-3": { + "$type": "color", + "$value": "#6b3215" + } + }, + "Tangerine": { + "tangerine-light-1": { + "$type": "color", + "$value": "#fff7ed" + }, + "tangerine-light-2": { + "$type": "color", + "$value": "#fcedd9" + }, + "tangerine-light-3": { + "$type": "color", + "$value": "#fae5ca" + }, + "tangerine-thick-1": { + "$type": "color", + "$value": "#f2cb99" + }, + "tangerine-thick-2": { + "$type": "color", + "$value": "#db8f2c" + }, + "tangerine-thick-3": { + "$type": "color", + "$value": "#613b0a" + } + }, + "Mango": { + "mango-light-1": { + "$type": "color", + "$value": "#fff9ec" + }, + "mango-light-2": { + "$type": "color", + "$value": "#fcf1d7" + }, + "mango-light-3": { + "$type": "color", + "$value": "#fae9c3" + }, + "mango-thick-1": { + "$type": "color", + "$value": "#f5d68e" + }, + "mango-thick-2": { + "$type": "color", + "$value": "#e0a416" + }, + "mango-thick-3": { + "$type": "color", + "$value": "#5c4102" + } + }, + "Lemon": { + "lemon-light-1": { + "$type": "color", + "$value": "#fffbe8" + }, + "lemon-light-2": { + "$type": "color", + "$value": "#fcf5cf" + }, + "lemon-light-3": { + "$type": "color", + "$value": "#faefb9" + }, + "lemon-thick-1": { + "$type": "color", + "$value": "#f5e282" + }, + "lemon-thick-2": { + "$type": "color", + "$value": "#e0bb00" + }, + "lemon-thick-3": { + "$type": "color", + "$value": "#574800" + } + }, + "Olive": { + "olive-light-1": { + "$type": "color", + "$value": "#f9fae6" + }, + "olive-light-2": { + "$type": "color", + "$value": "#f6f7d0" + }, + "olive-light-3": { + "$type": "color", + "$value": "#f0f2b3" + }, + "olive-thick-1": { + "$type": "color", + "$value": "#dbde83" + }, + "olive-thick-2": { + "$type": "color", + "$value": "#adb204" + }, + "olive-thick-3": { + "$type": "color", + "$value": "#4a4c03" + } + }, + "Lime": { + "lime-light-1": { + "$type": "color", + "$value": "#f6f9e6" + }, + "lime-light-2": { + "$type": "color", + "$value": "#eef5ce" + }, + "lime-light-3": { + "$type": "color", + "$value": "#e7f0bb" + }, + "lime-thick-1": { + "$type": "color", + "$value": "#cfdb91" + }, + "lime-thick-2": { + "$type": "color", + "$value": "#92a822" + }, + "lime-thick-3": { + "$type": "color", + "$value": "#414d05" + } + }, + "Grass": { + "grass-light-1": { + "$type": "color", + "$value": "#f4faeb" + }, + "grass-light-2": { + "$type": "color", + "$value": "#e9f5d7" + }, + "grass-light-3": { + "$type": "color", + "$value": "#def0c5" + }, + "grass-thick-1": { + "$type": "color", + "$value": "#bfd998" + }, + "grass-thick-2": { + "$type": "color", + "$value": "#75a828" + }, + "grass-thick-3": { + "$type": "color", + "$value": "#334d0c" + } + }, + "Forest": { + "forest-light-1": { + "$type": "color", + "$value": "#f1faf0" + }, + "forest-light-2": { + "$type": "color", + "$value": "#e2f5df" + }, + "forest-light-3": { + "$type": "color", + "$value": "#d7f0d3" + }, + "forest-thick-1": { + "$type": "color", + "$value": "#a8d6a1" + }, + "forest-thick-2": { + "$type": "color", + "$value": "#49a33b" + }, + "forest-thick-3": { + "$type": "color", + "$value": "#1e4f16" + } + }, + "Jade": { + "jade-light-1": { + "$type": "color", + "$value": "#f0faf6" + }, + "jade-light-2": { + "$type": "color", + "$value": "#dff5eb" + }, + "jade-light-3": { + "$type": "color", + "$value": "#cef0e1" + }, + "jade-thick-1": { + "$type": "color", + "$value": "#90d1b5" + }, + "jade-thick-2": { + "$type": "color", + "$value": "#1c9963" + }, + "jade-thick-3": { + "$type": "color", + "$value": "#075231" + } + }, + "Aqua": { + "aqua-light-1": { + "$type": "color", + "$value": "#f0f9fa" + }, + "aqua-light-2": { + "$type": "color", + "$value": "#dff3f5" + }, + "aqua-light-3": { + "$type": "color", + "$value": "#ccecf0" + }, + "aqua-thick-1": { + "$type": "color", + "$value": "#83ccd4" + }, + "aqua-thick-2": { + "$type": "color", + "$value": "#008e9e" + }, + "aqua-thick-3": { + "$type": "color", + "$value": "#004e57" + } + }, + "Azure": { + "azure-light-1": { + "$type": "color", + "$value": "#f0f6fa" + }, + "azure-light-2": { + "$type": "color", + "$value": "#e1eef7" + }, + "azure-light-3": { + "$type": "color", + "$value": "#d3e6f5" + }, + "azure-thick-1": { + "$type": "color", + "$value": "#88c0eb" + }, + "azure-thick-2": { + "$type": "color", + "$value": "#0877cc" + }, + "azure-thick-3": { + "$type": "color", + "$value": "#154469" + } + }, + "Denim": { + "denim-light-1": { + "$type": "color", + "$value": "#f0f3fa" + }, + "denim-light-2": { + "$type": "color", + "$value": "#e3ebfa" + }, + "denim-light-3": { + "$type": "color", + "$value": "#d7e2f7" + }, + "denim-thick-1": { + "$type": "color", + "$value": "#9ab6ed" + }, + "denim-thick-2": { + "$type": "color", + "$value": "#3267d1" + }, + "denim-thick-3": { + "$type": "color", + "$value": "#223c70" + } + }, + "Mauve": { + "mauve-light-1": { + "$type": "color", + "$value": "#f2f2fc" + }, + "mauve-thick-2": { + "$type": "color", + "$value": "#5555e0" + }, + "mauve-thick-3": { + "$type": "color", + "$value": "#36366b" + }, + "mauve-thick-1": { + "$type": "color", + "$value": "#aeaef5" + } + }, + "Lavender": { + "lavender-light-1": { + "$type": "color", + "$value": "#f6f3fc" + }, + "lavender-light-2": { + "$type": "color", + "$value": "#ebe3fa" + }, + "lavender-light-3": { + "$type": "color", + "$value": "#e4daf7" + }, + "lavender-thick-1": { + "$type": "color", + "$value": "#c1aaf0" + }, + "lavender-thick-2": { + "$type": "color", + "$value": "#8153db" + }, + "lavender-thick-3": { + "$type": "color", + "$value": "#462f75" + } + }, + "Lilac": { + "liliac-light-1": { + "$type": "color", + "$value": "#f7f0fa" + }, + "liliac-light-2": { + "$type": "color", + "$value": "#f0e1f7" + }, + "liliac-light-3": { + "$type": "color", + "$value": "#edd7f7" + }, + "liliac-thick-1": { + "$type": "color", + "$value": "#d3a9e8" + }, + "liliac-thick-2": { + "$type": "color", + "$value": "#9e4cc7" + }, + "liliac-thick-3": { + "$type": "color", + "$value": "#562d6b" + } + }, + "Mallow": { + "mallow-light-1": { + "$type": "color", + "$value": "#faf0fa" + }, + "mallow-light-2": { + "$type": "color", + "$value": "#f5e1f4" + }, + "mallow-light-3": { + "$type": "color", + "$value": "#f5d7f4" + }, + "mallow-thick-1": { + "$type": "color", + "$value": "#dea4dc" + }, + "mallow-thick-2": { + "$type": "color", + "$value": "#b240af" + }, + "mallow-thick-3": { + "$type": "color", + "$value": "#632861" + } + }, + "Camellia": { + "camellia-light-1": { + "$type": "color", + "$value": "#f9eff3" + }, + "camellia-light-2": { + "$type": "color", + "$value": "#f7e1eb" + }, + "camellia-light-3": { + "$type": "color", + "$value": "#f7d7e5" + }, + "camellia-thick-1": { + "$type": "color", + "$value": "#e5a3c0" + }, + "camellia-thick-2": { + "$type": "color", + "$value": "#c24279" + }, + "camellia-thick-3": { + "$type": "color", + "$value": "#6e2343" + } + }, + "Smoke": { + "smoke-light-1": { + "$type": "color", + "$value": "#f5f5f5" + }, + "smoke-light-2": { + "$type": "color", + "$value": "#e8e8e8" + }, + "smoke-light-3": { + "$type": "color", + "$value": "#dedede" + }, + "smoke-thick-1": { + "$type": "color", + "$value": "#b8b8b8" + }, + "smoke-thick-2": { + "$type": "color", + "$value": "#6e6e6e" + }, + "smoke-thick-3": { + "$type": "color", + "$value": "#404040" + } + }, + "Iron": { + "icon-light-1": { + "$type": "color", + "$value": "#f2f4f7" + }, + "icon-light-2": { + "$type": "color", + "$value": "#e6e9f0" + }, + "icon-light-3": { + "$type": "color", + "$value": "#dadee5" + }, + "icon-thick-1": { + "$type": "color", + "$value": "#b0b5bf" + }, + "icon-thick-2": { + "$type": "color", + "$value": "#666f80" + }, + "icon-thick-3": { + "$type": "color", + "$value": "#394152" + } + } + }, + "Shadow": { + "sm": { + "$type": "dimension", + "$value": "0px" + }, + "md": { + "$type": "dimension", + "$value": "0px" + } + }, + "Brand": { + "Skyline": { + "$type": "color", + "$value": "#00b5ff" + }, + "Aqua": { + "$type": "color", + "$value": "#00c8ff" + }, + "Violet": { + "$type": "color", + "$value": "#9327ff" + }, + "Amethyst": { + "$type": "color", + "$value": "#8427e0" + }, + "Berry": { + "$type": "color", + "$value": "#e3006d" + }, + "Coral": { + "$type": "color", + "$value": "#fb006d" + }, + "Golden": { + "$type": "color", + "$value": "#f7931e" + }, + "Amber": { + "$type": "color", + "$value": "#ffbd00" + }, + "Lemon": { + "$type": "color", + "$value": "#ffce00" + } + }, + "Other_Colors": { + "text-highlight": { + "$type": "color", + "$value": "{Blue.200}" + } + }, + "Spacing": { + "spacing-0": { + "$type": "dimension", + "$value": "{Spacing.0}" + }, + "spacing-xs": { + "$type": "dimension", + "$value": "{Spacing.100}" + }, + "spacing-s": { + "$type": "dimension", + "$value": "{Spacing.200}" + }, + "spacing-m": { + "$type": "dimension", + "$value": "{Spacing.300}" + }, + "spacing-l": { + "$type": "dimension", + "$value": "{Spacing.400}" + }, + "spacing-xl": { + "$type": "dimension", + "$value": "{Spacing.500}" + }, + "spacing-xxl": { + "$type": "dimension", + "$value": "{Spacing.600}" + }, + "spacing-full": { + "$type": "dimension", + "$value": "{Spacing.1000}" + } + }, + "Border_Radius": { + "border-radius-0": { + "$type": "dimension", + "$value": "{Border-Radius.0}" + }, + "border-radius-xs": { + "$type": "dimension", + "$value": "{Border-Radius.100}" + }, + "border-radius-s": { + "$type": "dimension", + "$value": "{Border-Radius.200}" + }, + "border-radius-m": { + "$type": "dimension", + "$value": "{Border-Radius.300}" + }, + "border-radius-l": { + "$type": "dimension", + "$value": "{Border-Radius.400}" + }, + "border-radius-xl": { + "$type": "dimension", + "$value": "{Border-Radius.500}" + }, + "border-radius-xxl": { + "$type": "dimension", + "$value": "{Border-Radius.600}" + }, + "border-radius-full": { + "$type": "dimension", + "$value": "{Border-Radius.1000}" + } + } +} \ No newline at end of file diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/script/Semantic.Light Mode.tokens.json b/frontend/appflowy_flutter/packages/appflowy_ui/script/Semantic.Light Mode.tokens.json new file mode 100644 index 0000000000..4e6b0543dc --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/script/Semantic.Light Mode.tokens.json @@ -0,0 +1,1039 @@ +{ + "Text": { + "primary": { + "$type": "color", + "$value": "{Neutral.1000}" + }, + "secondary": { + "$type": "color", + "$value": "{Neutral.600}" + }, + "tertiary": { + "$type": "color", + "$value": "{Neutral.400}" + }, + "quaternary": { + "$type": "color", + "$value": "{Neutral.200}" + }, + "inverse": { + "$type": "color", + "$value": "{Neutral.white}" + }, + "on-fill": { + "$type": "color", + "$value": "{Neutral.white}" + }, + "theme": { + "$type": "color", + "$value": "{Blue.500}" + }, + "theme-hover": { + "$type": "color", + "$value": "{Blue.600}" + }, + "action": { + "$type": "color", + "$value": "{Blue.500}" + }, + "action-hover": { + "$type": "color", + "$value": "{Blue.600}" + }, + "info": { + "$type": "color", + "$value": "{Blue.500}" + }, + "info-hover": { + "$type": "color", + "$value": "{Blue.600}" + }, + "success": { + "$type": "color", + "$value": "{Green.600}" + }, + "success-hover": { + "$type": "color", + "$value": "{Green.700}" + }, + "warning": { + "$type": "color", + "$value": "{Orange.600}" + }, + "warning-hover": { + "$type": "color", + "$value": "{Orange.700}" + }, + "error": { + "$type": "color", + "$value": "{Red.600}" + }, + "error-hover": { + "$type": "color", + "$value": "{Red.700}" + }, + "purple": { + "$type": "color", + "$value": "{Purple.500}" + }, + "purple-hover": { + "$type": "color", + "$value": "{Purple.600}" + } + }, + "Icon": { + "primary": { + "$type": "color", + "$value": "{Neutral.1000}" + }, + "secondary": { + "$type": "color", + "$value": "{Neutral.600}" + }, + "tertiary": { + "$type": "color", + "$value": "{Neutral.400}" + }, + "quaternary": { + "$type": "color", + "$value": "{Neutral.200}" + }, + "white": { + "$type": "color", + "$value": "{Neutral.white}" + }, + "purple-thick": { + "$type": "color", + "$value": "{Purple.500}" + }, + "purple-thick-hover": { + "$type": "color", + "$value": "{Purple.600}" + } + }, + "Border": { + "grey-primary": { + "$type": "color", + "$value": "{Neutral.1000}" + }, + "grey-primary-hover": { + "$type": "color", + "$value": "{Neutral.900}" + }, + "grey-secondary": { + "$type": "color", + "$value": "{Neutral.800}" + }, + "grey-secondary-hover": { + "$type": "color", + "$value": "{Neutral.700}" + }, + "grey-tertiary": { + "$type": "color", + "$value": "{Neutral.300}" + }, + "grey-tertiary-hover": { + "$type": "color", + "$value": "{Neutral.400}" + }, + "grey-quaternary": { + "$type": "color", + "$value": "{Neutral.100}" + }, + "grey-quaternary-hover": { + "$type": "color", + "$value": "{Neutral.200}" + }, + "transparent": { + "$type": "color", + "$value": "{Neutral.alpha-white-0}" + }, + "theme-thick": { + "$type": "color", + "$value": "{Blue.500}" + }, + "theme-thick-hover": { + "$type": "color", + "$value": "{Blue.600}" + }, + "info-thick": { + "$type": "color", + "$value": "{Blue.500}" + }, + "info-thick-hover": { + "$type": "color", + "$value": "{Blue.600}" + }, + "success-thick": { + "$type": "color", + "$value": "{Green.600}" + }, + "success-thick-hover": { + "$type": "color", + "$value": "{Green.700}" + }, + "warning-thick": { + "$type": "color", + "$value": "{Orange.600}" + }, + "warning-thick-hover": { + "$type": "color", + "$value": "{Orange.700}" + }, + "error-thick": { + "$type": "color", + "$value": "{Red.600}" + }, + "error-thick-hover": { + "$type": "color", + "$value": "{Red.700}" + }, + "purple-thick": { + "$type": "color", + "$value": "{Purple.500}" + }, + "purple-thick-hover": { + "$type": "color", + "$value": "{Purple.600}" + } + }, + "Fill": { + "primary": { + "$type": "color", + "$value": "{Neutral.1000}" + }, + "primary-hover": { + "$type": "color", + "$value": "{Neutral.900}" + }, + "secondary": { + "$type": "color", + "$value": "{Neutral.600}" + }, + "secondary-hover": { + "$type": "color", + "$value": "{Neutral.500}" + }, + "tertiary": { + "$type": "color", + "$value": "{Neutral.300}" + }, + "tertiary-hover": { + "$type": "color", + "$value": "{Neutral.400}" + }, + "quaternary": { + "$type": "color", + "$value": "{Neutral.100}" + }, + "quaternary-hover": { + "$type": "color", + "$value": "{Neutral.200}" + }, + "transparent": { + "$type": "color", + "$value": "{Neutral.alpha-white-0}" + }, + "primary-alpha-5": { + "$type": "color", + "$value": "{Neutral.alpha-grey-1000-05}", + "$description": "Used for hover state, eg. button, navigation item, menu item and grid item." + }, + "primary-alpha-5-hover": { + "$type": "color", + "$value": "{Neutral.alpha-grey-1000-10}" + }, + "primary-alpha-80": { + "$type": "color", + "$value": "{Neutral.alpha-grey-1000-80}" + }, + "primary-alpha-80-hover": { + "$type": "color", + "$value": "{Neutral.alpha-grey-1000-70}" + }, + "white": { + "$type": "color", + "$value": "{Neutral.white}" + }, + "white-alpha": { + "$type": "color", + "$value": "{Neutral.alpha-white-20}" + }, + "white-alpha-hover": { + "$type": "color", + "$value": "{Neutral.alpha-white-30}" + }, + "black": { + "$type": "color", + "$value": "{Neutral.black}" + }, + "theme-light": { + "$type": "color", + "$value": "{Blue.100}" + }, + "theme-light-hover": { + "$type": "color", + "$value": "{Blue.200}" + }, + "theme-thick": { + "$type": "color", + "$value": "{Blue.500}" + }, + "theme-thick-hover": { + "$type": "color", + "$value": "{Blue.600}" + }, + "theme-select": { + "$type": "color", + "$value": "{Blue.alpha-blue-500-15}" + }, + "info-light": { + "$type": "color", + "$value": "{Blue.100}" + }, + "info-light-hover": { + "$type": "color", + "$value": "{Blue.200}" + }, + "info-thick": { + "$type": "color", + "$value": "{Blue.500}" + }, + "info-thick-hover": { + "$type": "color", + "$value": "{Blue.600}" + }, + "success-light": { + "$type": "color", + "$value": "{Green.100}" + }, + "success-light-hover": { + "$type": "color", + "$value": "{Green.200}" + }, + "success-thick": { + "$type": "color", + "$value": "{Green.600}" + }, + "success-thick-hover": { + "$type": "color", + "$value": "{Green.700}" + }, + "warning-light": { + "$type": "color", + "$value": "{Orange.100}" + }, + "warning-light-hover": { + "$type": "color", + "$value": "{Orange.200}" + }, + "warning-thick": { + "$type": "color", + "$value": "{Orange.600}" + }, + "warning-thick-hover": { + "$type": "color", + "$value": "{Orange.700}" + }, + "error-light": { + "$type": "color", + "$value": "{Red.100}" + }, + "error-light-hover": { + "$type": "color", + "$value": "{Red.200}" + }, + "error-thick": { + "$type": "color", + "$value": "{Red.600}" + }, + "error-thick-hover": { + "$type": "color", + "$value": "{Red.700}" + }, + "error-select": { + "$type": "color", + "$value": "{Red.alpha-red-500-10}" + }, + "purple-light": { + "$type": "color", + "$value": "{Purple.100}" + }, + "purple-light-hover": { + "$type": "color", + "$value": "{Purple.200}" + }, + "purple-thick-hover": { + "$type": "color", + "$value": "{Purple.600}" + }, + "purple-thick": { + "$type": "color", + "$value": "{Purple.500}" + } + }, + "Surface": { + "primary": { + "$type": "color", + "$value": "{Neutral.white}" + }, + "overlay": { + "$type": "color", + "$value": "{Neutral.alpha-black-60}" + } + }, + "Background": { + "primary": { + "$type": "color", + "$value": "{Neutral.white}" + }, + "secondary": { + "$type": "color", + "$value": "{Neutral.100}" + }, + "tertiary": { + "$type": "color", + "$value": "{Neutral.200}" + }, + "quaternary": { + "$type": "color", + "$value": "{Neutral.300}" + } + }, + "Badge_Color": { + "Rose": { + "rose-light-1": { + "$type": "color", + "$value": "{Subtle_Color.Rose.100}" + }, + "rose-light-2": { + "$type": "color", + "$value": "{Subtle_Color.Rose.200}" + }, + "rose-light-3": { + "$type": "color", + "$value": "{Subtle_Color.Rose.300}" + }, + "rose-thick-1": { + "$type": "color", + "$value": "{Subtle_Color.Rose.400}" + }, + "rose-thick-2": { + "$type": "color", + "$value": "{Subtle_Color.Rose.500}" + }, + "rose-thick-3": { + "$type": "color", + "$value": "{Subtle_Color.Rose.600}" + } + }, + "Papaya": { + "papaya-light-1": { + "$type": "color", + "$value": "{Subtle_Color.Papaya.100}" + }, + "papaya-light-2": { + "$type": "color", + "$value": "{Subtle_Color.Papaya.200}" + }, + "papaya-light-3": { + "$type": "color", + "$value": "{Subtle_Color.Papaya.300}" + }, + "papaya-thick-1": { + "$type": "color", + "$value": "{Subtle_Color.Papaya.400}" + }, + "papaya-thick-2": { + "$type": "color", + "$value": "{Subtle_Color.Papaya.500}" + }, + "papaya-thick-3": { + "$type": "color", + "$value": "{Subtle_Color.Papaya.600}" + } + }, + "Tangerine": { + "tangerine-light-1": { + "$type": "color", + "$value": "{Subtle_Color.Tangerine.100}" + }, + "tangerine-light-2": { + "$type": "color", + "$value": "{Subtle_Color.Tangerine.200}" + }, + "tangerine-light-3": { + "$type": "color", + "$value": "{Subtle_Color.Tangerine.300}" + }, + "tangerine-thick-1": { + "$type": "color", + "$value": "{Subtle_Color.Tangerine.400}" + }, + "tangerine-thick-2": { + "$type": "color", + "$value": "{Subtle_Color.Tangerine.500}" + }, + "tangerine-thick-3": { + "$type": "color", + "$value": "{Subtle_Color.Tangerine.600}" + } + }, + "Mango": { + "mango-light-1": { + "$type": "color", + "$value": "{Subtle_Color.Mango.100}" + }, + "mango-light-2": { + "$type": "color", + "$value": "{Subtle_Color.Mango.200}" + }, + "mango-light-3": { + "$type": "color", + "$value": "{Subtle_Color.Mango.300}" + }, + "mango-thick-1": { + "$type": "color", + "$value": "{Subtle_Color.Mango.400}" + }, + "mango-thick-2": { + "$type": "color", + "$value": "{Subtle_Color.Mango.500}" + }, + "mango-thick-3": { + "$type": "color", + "$value": "{Subtle_Color.Mango.600}" + } + }, + "Lemon": { + "lemon-light-1": { + "$type": "color", + "$value": "{Subtle_Color.Lemon.100}" + }, + "lemon-light-2": { + "$type": "color", + "$value": "{Subtle_Color.Lemon.200}" + }, + "lemon-light-3": { + "$type": "color", + "$value": "{Subtle_Color.Lemon.300}" + }, + "lemon-thick-1": { + "$type": "color", + "$value": "{Subtle_Color.Lemon.400}" + }, + "lemon-thick-2": { + "$type": "color", + "$value": "{Subtle_Color.Lemon.500}" + }, + "lemon-thick-3": { + "$type": "color", + "$value": "{Subtle_Color.Lemon.600}" + } + }, + "Olive": { + "olive-light-1": { + "$type": "color", + "$value": "{Subtle_Color.Olive.100}" + }, + "olive-light-2": { + "$type": "color", + "$value": "{Subtle_Color.Olive.200}" + }, + "olive-light-3": { + "$type": "color", + "$value": "{Subtle_Color.Olive.300}" + }, + "olive-thick-1": { + "$type": "color", + "$value": "{Subtle_Color.Olive.400}" + }, + "olive-thick-2": { + "$type": "color", + "$value": "{Subtle_Color.Olive.500}" + }, + "olive-thick-3": { + "$type": "color", + "$value": "{Subtle_Color.Olive.600}" + } + }, + "Lime": { + "lime-light-1": { + "$type": "color", + "$value": "{Subtle_Color.Lime.100}" + }, + "lime-light-2": { + "$type": "color", + "$value": "{Subtle_Color.Lime.200}" + }, + "lime-light-3": { + "$type": "color", + "$value": "{Subtle_Color.Lime.300}" + }, + "lime-thick-1": { + "$type": "color", + "$value": "{Subtle_Color.Lime.400}" + }, + "lime-thick-2": { + "$type": "color", + "$value": "{Subtle_Color.Lime.500}" + }, + "lime-thick-3": { + "$type": "color", + "$value": "{Subtle_Color.Lime.600}" + } + }, + "Grass": { + "grass-light-1": { + "$type": "color", + "$value": "{Subtle_Color.Grass.100}" + }, + "grass-light-2": { + "$type": "color", + "$value": "{Subtle_Color.Grass.200}" + }, + "grass-light-3": { + "$type": "color", + "$value": "{Subtle_Color.Grass.300}" + }, + "grass-thick-1": { + "$type": "color", + "$value": "{Subtle_Color.Grass.400}" + }, + "grass-thick-2": { + "$type": "color", + "$value": "{Subtle_Color.Grass.500}" + }, + "grass-thick-3": { + "$type": "color", + "$value": "{Subtle_Color.Grass.600}" + } + }, + "Forest": { + "forest-light-1": { + "$type": "color", + "$value": "{Subtle_Color.Forest.100}" + }, + "forest-light-2": { + "$type": "color", + "$value": "{Subtle_Color.Forest.200}" + }, + "forest-light-3": { + "$type": "color", + "$value": "{Subtle_Color.Forest.300}" + }, + "forest-thick-1": { + "$type": "color", + "$value": "{Subtle_Color.Forest.400}" + }, + "forest-thick-2": { + "$type": "color", + "$value": "{Subtle_Color.Forest.500}" + }, + "forest-thick-3": { + "$type": "color", + "$value": "{Subtle_Color.Forest.600}" + } + }, + "Jade": { + "jade-light-1": { + "$type": "color", + "$value": "{Subtle_Color.Jade.100}" + }, + "jade-light-2": { + "$type": "color", + "$value": "{Subtle_Color.Jade.200}" + }, + "jade-light-3": { + "$type": "color", + "$value": "{Subtle_Color.Jade.300}" + }, + "jade-thick-1": { + "$type": "color", + "$value": "{Subtle_Color.Jade.400}" + }, + "jade-thick-2": { + "$type": "color", + "$value": "{Subtle_Color.Jade.500}" + }, + "jade-thick-3": { + "$type": "color", + "$value": "{Subtle_Color.Jade.600}" + } + }, + "Aqua": { + "aqua-light-1": { + "$type": "color", + "$value": "{Subtle_Color.Aqua.100}" + }, + "aqua-light-2": { + "$type": "color", + "$value": "{Subtle_Color.Aqua.200}" + }, + "aqua-light-3": { + "$type": "color", + "$value": "{Subtle_Color.Aqua.300}" + }, + "aqua-thick-1": { + "$type": "color", + "$value": "{Subtle_Color.Aqua.400}" + }, + "aqua-thick-2": { + "$type": "color", + "$value": "{Subtle_Color.Aqua.500}" + }, + "aqua-thick-3": { + "$type": "color", + "$value": "{Subtle_Color.Aqua.600}" + } + }, + "Azure": { + "azure-light-1": { + "$type": "color", + "$value": "{Subtle_Color.Azure.100}" + }, + "azure-light-2": { + "$type": "color", + "$value": "{Subtle_Color.Azure.200}" + }, + "azure-light-3": { + "$type": "color", + "$value": "{Subtle_Color.Azure.300}" + }, + "azure-thick-1": { + "$type": "color", + "$value": "{Subtle_Color.Azure.400}" + }, + "azure-thick-2": { + "$type": "color", + "$value": "{Subtle_Color.Azure.500}" + }, + "azure-thick-3": { + "$type": "color", + "$value": "{Subtle_Color.Azure.600}" + } + }, + "Denim": { + "denim-light-1": { + "$type": "color", + "$value": "{Subtle_Color.Denim.100}" + }, + "denim-light-2": { + "$type": "color", + "$value": "{Subtle_Color.Denim.200}" + }, + "denim-light-3": { + "$type": "color", + "$value": "{Subtle_Color.Denim.300}" + }, + "denim-thick-1": { + "$type": "color", + "$value": "{Subtle_Color.Denim.400}" + }, + "denim-thick-2": { + "$type": "color", + "$value": "{Subtle_Color.Denim.500}" + }, + "denim-thick-3": { + "$type": "color", + "$value": "{Subtle_Color.Denim.600}" + } + }, + "Mauve": { + "mauve-light-1": { + "$type": "color", + "$value": "{Subtle_Color.Mauve.100}" + }, + "mauve-thick-2": { + "$type": "color", + "$value": "{Subtle_Color.Mauve.500}" + }, + "mauve-thick-3": { + "$type": "color", + "$value": "{Subtle_Color.Mauve.600}" + }, + "mauve-thick-1": { + "$type": "color", + "$value": "{Subtle_Color.Mauve.400}" + } + }, + "Lavender": { + "lavender-light-1": { + "$type": "color", + "$value": "{Subtle_Color.Lavender.100}" + }, + "lavender-light-2": { + "$type": "color", + "$value": "{Subtle_Color.Lavender.200}" + }, + "lavender-light-3": { + "$type": "color", + "$value": "{Subtle_Color.Lavender.300}" + }, + "lavender-thick-1": { + "$type": "color", + "$value": "{Subtle_Color.Lavender.400}" + }, + "lavender-thick-2": { + "$type": "color", + "$value": "{Subtle_Color.Lavender.500}" + }, + "lavender-thick-3": { + "$type": "color", + "$value": "{Subtle_Color.Lavender.600}" + } + }, + "Lilac": { + "liliac-light-1": { + "$type": "color", + "$value": "{Subtle_Color.Lilac.100}" + }, + "liliac-light-2": { + "$type": "color", + "$value": "{Subtle_Color.Lilac.200}" + }, + "liliac-light-3": { + "$type": "color", + "$value": "{Subtle_Color.Lilac.300}" + }, + "liliac-thick-1": { + "$type": "color", + "$value": "{Subtle_Color.Lilac.400}" + }, + "liliac-thick-2": { + "$type": "color", + "$value": "{Subtle_Color.Lilac.500}" + }, + "liliac-thick-3": { + "$type": "color", + "$value": "{Subtle_Color.Lilac.600}" + } + }, + "Mallow": { + "mallow-light-1": { + "$type": "color", + "$value": "{Subtle_Color.Mallow.100}" + }, + "mallow-light-2": { + "$type": "color", + "$value": "{Subtle_Color.Mallow.200}" + }, + "mallow-light-3": { + "$type": "color", + "$value": "{Subtle_Color.Mallow.300}" + }, + "mallow-thick-1": { + "$type": "color", + "$value": "{Subtle_Color.Mallow.400}" + }, + "mallow-thick-2": { + "$type": "color", + "$value": "{Subtle_Color.Mallow.500}" + }, + "mallow-thick-3": { + "$type": "color", + "$value": "{Subtle_Color.Mallow.600}" + } + }, + "Camellia": { + "camellia-light-1": { + "$type": "color", + "$value": "{Subtle_Color.Camellia.100}" + }, + "camellia-light-2": { + "$type": "color", + "$value": "{Subtle_Color.Camellia.200}" + }, + "camellia-light-3": { + "$type": "color", + "$value": "{Subtle_Color.Camellia.300}" + }, + "camellia-thick-1": { + "$type": "color", + "$value": "{Subtle_Color.Camellia.400}" + }, + "camellia-thick-2": { + "$type": "color", + "$value": "{Subtle_Color.Camellia.500}" + }, + "camellia-thick-3": { + "$type": "color", + "$value": "{Subtle_Color.Camellia.600}" + } + }, + "Smoke": { + "smoke-light-1": { + "$type": "color", + "$value": "{Subtle_Color.Smoke.100}" + }, + "smoke-light-2": { + "$type": "color", + "$value": "{Subtle_Color.Smoke.200}" + }, + "smoke-light-3": { + "$type": "color", + "$value": "{Subtle_Color.Smoke.300}" + }, + "smoke-thick-1": { + "$type": "color", + "$value": "{Subtle_Color.Smoke.400}" + }, + "smoke-thick-2": { + "$type": "color", + "$value": "{Subtle_Color.Smoke.500}" + }, + "smoke-thick-3": { + "$type": "color", + "$value": "{Subtle_Color.Smoke.600}" + } + }, + "Iron": { + "icon-light-1": { + "$type": "color", + "$value": "{Subtle_Color.Iron.100}" + }, + "icon-light-2": { + "$type": "color", + "$value": "{Subtle_Color.Iron.200}" + }, + "icon-light-3": { + "$type": "color", + "$value": "{Subtle_Color.Iron.300}" + }, + "icon-thick-1": { + "$type": "color", + "$value": "{Subtle_Color.Iron.400}" + }, + "icon-thick-2": { + "$type": "color", + "$value": "{Subtle_Color.Iron.500}" + }, + "icon-thick-3": { + "$type": "color", + "$value": "{Subtle_Color.Iron.600}" + } + } + }, + "Shadow": { + "sm": { + "$type": "dimension", + "$value": "0px" + }, + "md": { + "$type": "dimension", + "$value": "0px" + } + }, + "Brand": { + "Skyline": { + "$type": "color", + "$value": "#00b5ff" + }, + "Aqua": { + "$type": "color", + "$value": "#00c8ff" + }, + "Violet": { + "$type": "color", + "$value": "#9327ff" + }, + "Amethyst": { + "$type": "color", + "$value": "#8427e0" + }, + "Berry": { + "$type": "color", + "$value": "#e3006d" + }, + "Coral": { + "$type": "color", + "$value": "#fb006d" + }, + "Golden": { + "$type": "color", + "$value": "#f7931e" + }, + "Amber": { + "$type": "color", + "$value": "#ffbd00" + }, + "Lemon": { + "$type": "color", + "$value": "#ffce00" + } + }, + "Other_Colors": { + "text-highlight": { + "$type": "color", + "$value": "{Blue.200}" + } + }, + "Spacing": { + "spacing-0": { + "$type": "dimension", + "$value": "{Spacing.0}" + }, + "spacing-xs": { + "$type": "dimension", + "$value": "{Spacing.100}" + }, + "spacing-s": { + "$type": "dimension", + "$value": "{Spacing.200}" + }, + "spacing-m": { + "$type": "dimension", + "$value": "{Spacing.300}" + }, + "spacing-l": { + "$type": "dimension", + "$value": "{Spacing.400}" + }, + "spacing-xl": { + "$type": "dimension", + "$value": "{Spacing.500}" + }, + "spacing-xxl": { + "$type": "dimension", + "$value": "{Spacing.600}" + }, + "spacing-full": { + "$type": "dimension", + "$value": "{Spacing.1000}" + } + }, + "Border_Radius": { + "border-radius-0": { + "$type": "dimension", + "$value": "{Border-Radius.0}" + }, + "border-radius-xs": { + "$type": "dimension", + "$value": "{Border-Radius.100}" + }, + "border-radius-s": { + "$type": "dimension", + "$value": "{Border-Radius.200}" + }, + "border-radius-m": { + "$type": "dimension", + "$value": "{Border-Radius.300}" + }, + "border-radius-l": { + "$type": "dimension", + "$value": "{Border-Radius.400}" + }, + "border-radius-xl": { + "$type": "dimension", + "$value": "{Border-Radius.500}" + }, + "border-radius-xxl": { + "$type": "dimension", + "$value": "{Border-Radius.600}" + }, + "border-radius-full": { + "$type": "dimension", + "$value": "{Border-Radius.1000}" + } + } +} \ No newline at end of file diff --git a/frontend/appflowy_flutter/packages/appflowy_ui/script/generate_theme.dart b/frontend/appflowy_flutter/packages/appflowy_ui/script/generate_theme.dart new file mode 100644 index 0000000000..bddcdb4eae --- /dev/null +++ b/frontend/appflowy_flutter/packages/appflowy_ui/script/generate_theme.dart @@ -0,0 +1,300 @@ +// ignore_for_file: avoid_print, depend_on_referenced_packages + +import 'dart:convert'; +import 'dart:io'; + +import 'package:collection/collection.dart'; + +void main() { + generatePrimitive(); + generateSemantic(); +} + +void generatePrimitive() { + // 1. Load the JSON file. + final jsonString = + File('script/Primitive.Mode 1.tokens.json').readAsStringSync(); + final jsonData = jsonDecode(jsonString) as Map; + + // 2. Prepare the output code. + final buffer = StringBuffer(); + + buffer.writeln(''' +// ignore_for_file: constant_identifier_names, non_constant_identifier_names +// +// AUTO-GENERATED - DO NOT EDIT DIRECTLY +// +// This file is auto-generated by the generate_theme.dart script +// Generation time: ${DateTime.now().toIso8601String()} +// +// To modify these colors, edit the source JSON files and run the script: +// +// dart run script/generate_theme.dart +// +import 'package:flutter/material.dart'; + +class AppFlowyPrimitiveTokens { + AppFlowyPrimitiveTokens._();'''); + + // 3. Process each color category. + jsonData.forEach((categoryName, categoryData) { + categoryData.forEach((tokenName, tokenData) { + processPrimitiveTokenData( + buffer, + tokenData, + '${categoryName}_$tokenName', + ); + }); + }); + + buffer.writeln('}'); + + // 4. Write the output to a Dart file. + final outputFile = File('lib/src/theme/data/appflowy_default/primitive.dart'); + outputFile.writeAsStringSync(buffer.toString()); + + print('Successfully generated ${outputFile.path}'); +} + +void processPrimitiveTokenData( + StringBuffer buffer, + Map tokenData, + final String currentTokenName, +) { + if (tokenData + case { + r'$type': 'color', + r'$value': final String colorValue, + }) { + final dartColorValue = convertColor(colorValue); + final dartTokenName = currentTokenName.replaceAll('-', '_').toCamelCase(); + + buffer.writeln(''' + + /// $colorValue + static Color get $dartTokenName => Color(0x$dartColorValue);'''); + } else { + tokenData.forEach((key, value) { + if (value is Map) { + processPrimitiveTokenData(buffer, value, '${currentTokenName}_$key'); + } + }); + } +} + +void generateSemantic() { + // 1. Load the JSON file. + final lightJsonString = + File('script/Semantic.Light Mode.tokens.json').readAsStringSync(); + final darkJsonString = + File('script/Semantic.Dark Mode.tokens.json').readAsStringSync(); + final lightJsonData = jsonDecode(lightJsonString) as Map; + final darkJsonData = jsonDecode(darkJsonString) as Map; + + // 2. Prepare the output code. + final buffer = StringBuffer(); + + buffer.writeln(''' +// ignore_for_file: constant_identifier_names, non_constant_identifier_names +// +// AUTO-GENERATED - DO NOT EDIT DIRECTLY +// +// This file is auto-generated by the generate_theme.dart script +// Generation time: ${DateTime.now().toIso8601String()} +// +// To modify these colors, edit the source JSON files and run the script: +// +// dart run script/generate_theme.dart +// +import 'package:appflowy_ui/appflowy_ui.dart'; +import 'package:flutter/material.dart'; + +import '../shared.dart'; +import 'primitive.dart'; + +class AppFlowyDefaultTheme implements AppFlowyThemeBuilder {'''); + + // 3. Process light mode semantic tokens + buffer.writeln(''' + @override + AppFlowyThemeData light() { + final textStyle = AppFlowyBaseTextStyle(); + final borderRadius = AppFlowySharedTokens.buildBorderRadius(); + final spacing = AppFlowySharedTokens.buildSpacing(); + final shadow = AppFlowySharedTokens.buildShadow(Brightness.light);'''); + + lightJsonData.forEach((categoryName, categoryData) { + if ([ + 'Spacing', + 'Border_Radius', + 'Shadow', + 'Badge_Color', + ].contains(categoryName)) { + return; + } + + final fullCategoryName = "${categoryName}_color_scheme".toCamelCase(); + final className = 'AppFlowy${fullCategoryName.toCapitalize()}'; + + buffer + ..writeln() + ..writeln(' final $fullCategoryName = $className('); + + categoryData.forEach((tokenName, tokenData) { + processSemanticTokenData(buffer, tokenData, tokenName); + }); + buffer.writeln(' );'); + }); + + buffer.writeln(); + buffer.writeln(''' + return AppFlowyThemeData( + textStyle: textStyle, + textColorScheme: textColorScheme, + borderColorScheme: borderColorScheme, + fillColorScheme: fillColorScheme, + surfaceColorScheme: surfaceColorScheme, + backgroundColorScheme: backgroundColorScheme, + iconColorScheme: iconColorScheme, + brandColorScheme: brandColorScheme, + otherColorsColorScheme: otherColorsColorScheme, + borderRadius: borderRadius, + spacing: spacing, + shadow: shadow, + ); + }'''); + + buffer.writeln(); + + buffer.writeln(''' + @override + AppFlowyThemeData dark() { + final textStyle = AppFlowyBaseTextStyle(); + final borderRadius = AppFlowySharedTokens.buildBorderRadius(); + final spacing = AppFlowySharedTokens.buildSpacing(); + final shadow = AppFlowySharedTokens.buildShadow(Brightness.dark);'''); + + darkJsonData.forEach((categoryName, categoryData) { + if ([ + 'Spacing', + 'Border_Radius', + 'Shadow', + 'Badge_Color', + ].contains(categoryName)) { + return; + } + + final fullCategoryName = "${categoryName}_color_scheme".toCamelCase(); + final className = 'AppFlowy${fullCategoryName.toCapitalize()}'; + + buffer + ..writeln() + ..writeln(' final $fullCategoryName = $className('); + + categoryData.forEach((tokenName, tokenData) { + if (tokenData is Map) { + processSemanticTokenData(buffer, tokenData, tokenName); + } + }); + buffer.writeln(' );'); + }); + + buffer.writeln(); + + buffer.writeln(''' + return AppFlowyThemeData( + textStyle: textStyle, + textColorScheme: textColorScheme, + borderColorScheme: borderColorScheme, + fillColorScheme: fillColorScheme, + surfaceColorScheme: surfaceColorScheme, + backgroundColorScheme: backgroundColorScheme, + iconColorScheme: iconColorScheme, + brandColorScheme: brandColorScheme, + otherColorsColorScheme: otherColorsColorScheme, + borderRadius: borderRadius, + spacing: spacing, + shadow: shadow, + ); + }'''); + + buffer.writeln('}'); + + // 4. Write the output to a Dart file. + final outputFile = File('lib/src/theme/data/appflowy_default/semantic.dart'); + outputFile.writeAsStringSync(buffer.toString()); + + print('Successfully generated ${outputFile.path}'); +} + +void processSemanticTokenData( + StringBuffer buffer, + Map json, + final String currentTokenName, +) { + if (json + case { + r'$type': 'color', + r'$value': final String value, + }) { + final semanticTokenName = + currentTokenName.replaceAll('-', '_').toCamelCase(); + + final String colorValueOrPrimitiveToken; + if (value.isColor) { + colorValueOrPrimitiveToken = 'Color(0x${convertColor(value)})'; + } else { + final primitiveToken = value + .replaceAll(RegExp(r'\{|\}'), '') + .replaceAll(RegExp(r'\.|-'), '_') + .toCamelCase(); + colorValueOrPrimitiveToken = 'AppFlowyPrimitiveTokens.$primitiveToken'; + } + + buffer.writeln(' $semanticTokenName: $colorValueOrPrimitiveToken,'); + } else { + json.forEach((key, value) { + if (value is Map) { + processSemanticTokenData( + buffer, + value, + '${currentTokenName}_$key', + ); + } + }); + } +} + +String convertColor(String hexColor) { + String color = hexColor.toUpperCase().replaceAll('#', ''); + if (color.length == 6) { + color = 'FF$color'; // Add missing alpha channel + } else if (color.length == 8) { + color = color.substring(6) + color.substring(0, 6); // Rearrange to ARGB + } + return color; +} + +extension on String { + String toCamelCase() { + return split('_').mapIndexed((index, part) { + if (index == 0) { + return part.toLowerCase(); + } else { + return part[0].toUpperCase() + part.substring(1).toLowerCase(); + } + }).join(); + } + + String toCapitalize() { + if (isEmpty) { + return this; + } + return '${this[0].toUpperCase()}${substring(1)}'; + } + + bool get isColor => + startsWith('#') || + (startsWith('0x') && length == 10) || + (startsWith('0xFF') && length == 12); +} diff --git a/frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/colorscheme.dart b/frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/colorscheme.dart index 9cd3a06313..4178edd294 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/colorscheme.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/colorscheme.dart @@ -90,6 +90,7 @@ class FlowyColorScheme { required this.scrollbarColor, required this.scrollbarHoverColor, required this.lightIconColor, + required this.toolbarHoverColor, }); final Color surface; @@ -154,6 +155,7 @@ class FlowyColorScheme { final Color scrollbarHoverColor; final Color lightIconColor; + final Color toolbarHoverColor; factory FlowyColorScheme.fromJson(Map json) => _$FlowyColorSchemeFromJson(json); diff --git a/frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/dandelion.dart b/frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/dandelion.dart index 2aa455b404..8d49b8dfa1 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/dandelion.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/dandelion.dart @@ -86,6 +86,7 @@ class DandelionColorScheme extends FlowyColorScheme { scrollbarColor: const Color(0x3F171717), scrollbarHoverColor: const Color(0x7F171717), lightIconColor: const Color(0xFF8F959E), + toolbarHoverColor: const Color(0xFFF2F4F7), ); const DandelionColorScheme.dark() @@ -144,5 +145,6 @@ class DandelionColorScheme extends FlowyColorScheme { scrollbarColor: const Color(0x40FFFFFF), scrollbarHoverColor: const Color(0x80FFFFFF), lightIconColor: const Color(0xFF8F959E), + toolbarHoverColor: _lightShader6, ); } diff --git a/frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/default_colorscheme.dart b/frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/default_colorscheme.dart index f829d3a67e..0e39de8fa8 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/default_colorscheme.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/default_colorscheme.dart @@ -83,6 +83,7 @@ class DefaultColorScheme extends FlowyColorScheme { scrollbarColor: const Color(0x3F171717), scrollbarHoverColor: const Color(0x7F171717), lightIconColor: const Color(0xFF8F959E), + toolbarHoverColor: const Color(0xFFF2F4F7), ); const DefaultColorScheme.dark() @@ -141,5 +142,6 @@ class DefaultColorScheme extends FlowyColorScheme { scrollbarColor: const Color(0x40FFFFFF), scrollbarHoverColor: const Color(0x80FFFFFF), lightIconColor: const Color(0xFF8F959E), + toolbarHoverColor: ColorSchemeConstants.lightShader6, ); } diff --git a/frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/lavender.dart b/frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/lavender.dart index 97ff5221de..590d26db3e 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/lavender.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/lavender.dart @@ -82,6 +82,7 @@ class LavenderColorScheme extends FlowyColorScheme { scrollbarColor: const Color(0x3F171717), scrollbarHoverColor: const Color(0x7F171717), lightIconColor: const Color(0xFF8F959E), + toolbarHoverColor: const Color(0xFFF2F4F7), ); const LavenderColorScheme.dark() @@ -140,5 +141,6 @@ class LavenderColorScheme extends FlowyColorScheme { scrollbarColor: const Color(0x40FFFFFF), scrollbarHoverColor: const Color(0x80FFFFFF), lightIconColor: const Color(0xFF8F959E), + toolbarHoverColor: _lightShader6, ); } diff --git a/frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/lemonade.dart b/frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/lemonade.dart index 5d9ff8b97c..3f39ae4c84 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/lemonade.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra/lib/colorscheme/lemonade.dart @@ -88,63 +88,64 @@ class LemonadeColorScheme extends FlowyColorScheme { scrollbarColor: const Color(0x3F171717), scrollbarHoverColor: const Color(0x7F171717), lightIconColor: const Color(0xFF8F959E), + toolbarHoverColor: const Color(0xFFF2F4F7), ); const LemonadeColorScheme.dark() : super( - surface: const Color(0xff292929), - hover: const Color(0xff1f1f1f), - selector: _darkShader2, - red: const Color(0xfffb006d), - yellow: const Color(0xffffd667), - green: const Color(0xff66cf80), - shader1: _white, - shader2: _darkShader2, - shader3: const Color(0xff828282), - shader4: const Color(0xffbdbdbd), - shader5: _darkShader5, - shader6: _darkShader6, - shader7: _white, - bg1: const Color(0xFFD5A200), - bg2: _black, - bg3: _darkMain1, - bg4: const Color(0xff2c144b), - tint1: const Color(0x4d9327FF), - tint2: const Color(0x66FC0088), - tint3: const Color(0x4dFC00E2), - tint4: const Color(0x80BE5B00), - tint5: const Color(0x33F8EE00), - tint6: const Color(0x4d6DC300), - tint7: const Color(0x5900BD2A), - tint8: const Color(0x80008890), - tint9: const Color(0x4d0029FF), - main1: _darkMain1, - main2: _darkMain1, - shadow: _black, - sidebarBg: const Color(0xff232B38), - divider: _darkShader3, - topbarBg: _darkShader1, - icon: _darkShader5, - text: _darkShader5, - secondaryText: _darkShader5, - strongText: Colors.white, - input: _darkInput, - hint: _darkShader5, - primary: _darkMain1, - onPrimary: _darkShader1, - hoverBG1: _darkMain1, - hoverBG2: _darkMain1, - hoverBG3: _darkShader3, - hoverFG: _darkShader1, - questionBubbleBG: _darkShader3, - progressBarBGColor: _darkShader3, - toolbarColor: _darkInput, - toggleButtonBGColor: _darkShader1, - calendarWeekendBGColor: const Color(0xff121212), - gridRowCountColor: _darkMain1, - borderColor: ColorSchemeConstants.darkBorderColor, - scrollbarColor: const Color(0x40FFFFFF), - scrollbarHoverColor: const Color(0x80FFFFFF), - lightIconColor: const Color(0xFF8F959E), - ); + surface: const Color(0xff292929), + hover: const Color(0xff1f1f1f), + selector: _darkShader2, + red: const Color(0xfffb006d), + yellow: const Color(0xffffd667), + green: const Color(0xff66cf80), + shader1: _white, + shader2: _darkShader2, + shader3: const Color(0xff828282), + shader4: const Color(0xffbdbdbd), + shader5: _darkShader5, + shader6: _darkShader6, + shader7: _white, + bg1: const Color(0xFFD5A200), + bg2: _black, + bg3: _darkMain1, + bg4: const Color(0xff2c144b), + tint1: const Color(0x4d9327FF), + tint2: const Color(0x66FC0088), + tint3: const Color(0x4dFC00E2), + tint4: const Color(0x80BE5B00), + tint5: const Color(0x33F8EE00), + tint6: const Color(0x4d6DC300), + tint7: const Color(0x5900BD2A), + tint8: const Color(0x80008890), + tint9: const Color(0x4d0029FF), + main1: _darkMain1, + main2: _darkMain1, + shadow: _black, + sidebarBg: const Color(0xff232B38), + divider: _darkShader3, + topbarBg: _darkShader1, + icon: _darkShader5, + text: _darkShader5, + secondaryText: _darkShader5, + strongText: Colors.white, + input: _darkInput, + hint: _darkShader5, + primary: _darkMain1, + onPrimary: _darkShader1, + hoverBG1: _darkMain1, + hoverBG2: _darkMain1, + hoverBG3: _darkShader3, + hoverFG: _darkShader1, + questionBubbleBG: _darkShader3, + progressBarBGColor: _darkShader3, + toolbarColor: _darkInput, + toggleButtonBGColor: _darkShader1, + calendarWeekendBGColor: const Color(0xff121212), + gridRowCountColor: _darkMain1, + borderColor: ColorSchemeConstants.darkBorderColor, + scrollbarColor: const Color(0x40FFFFFF), + scrollbarHoverColor: const Color(0x80FFFFFF), + lightIconColor: const Color(0xFF8F959E), + toolbarHoverColor: _lightShader6); } diff --git a/frontend/appflowy_flutter/packages/flowy_infra/lib/language.dart b/frontend/appflowy_flutter/packages/flowy_infra/lib/language.dart index 0b6ff4fb3f..6f37058f00 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra/lib/language.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra/lib/language.dart @@ -48,6 +48,8 @@ String languageFromLocale(Locale locale) { default: return locale.languageCode; } + case "mr": + return "मराठी"; case "he": return "עברית"; case "hu": diff --git a/frontend/appflowy_flutter/packages/flowy_infra/lib/theme_extension.dart b/frontend/appflowy_flutter/packages/flowy_infra/lib/theme_extension.dart index a2bfd16b06..9ce1f0323d 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra/lib/theme_extension.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra/lib/theme_extension.dart @@ -41,6 +41,7 @@ class AFThemeExtension extends ThemeExtension { required this.borderColor, required this.scrollbarColor, required this.scrollbarHoverColor, + required this.toolbarHoverColor, required this.lightIconColor, }); @@ -86,6 +87,7 @@ class AFThemeExtension extends ThemeExtension { final Color scrollbarColor; final Color scrollbarHoverColor; + final Color toolbarHoverColor; final Color lightIconColor; @override @@ -123,6 +125,7 @@ class AFThemeExtension extends ThemeExtension { Color? scrollbarColor, Color? scrollbarHoverColor, Color? lightIconColor, + Color? toolbarHoverColor, }) => AFThemeExtension( warning: warning ?? this.warning, @@ -159,6 +162,7 @@ class AFThemeExtension extends ThemeExtension { scrollbarColor: scrollbarColor ?? this.scrollbarColor, scrollbarHoverColor: scrollbarHoverColor ?? this.scrollbarHoverColor, lightIconColor: lightIconColor ?? this.lightIconColor, + toolbarHoverColor: toolbarHoverColor ?? this.toolbarHoverColor, ); @override @@ -215,6 +219,8 @@ class AFThemeExtension extends ThemeExtension { scrollbarHoverColor: Color.lerp(scrollbarHoverColor, other.scrollbarHoverColor, t)!, lightIconColor: Color.lerp(lightIconColor, other.lightIconColor, t)!, + toolbarHoverColor: + Color.lerp(toolbarHoverColor, other.toolbarHoverColor, t)!, ); } } diff --git a/frontend/appflowy_flutter/packages/flowy_infra/lib/utils/color_converter.dart b/frontend/appflowy_flutter/packages/flowy_infra/lib/utils/color_converter.dart index 19ca90d78f..4f80f81e62 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra/lib/utils/color_converter.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra/lib/utils/color_converter.dart @@ -13,5 +13,12 @@ class ColorConverter implements JsonConverter { } @override - String toJson(Color color) => "0x${color.value.toRadixString(16)}"; + String toJson(Color color) { + final alpha = (color.a * 255).toInt().toRadixString(16).padLeft(2, '0'); + final red = (color.r * 255).toInt().toRadixString(16).padLeft(2, '0'); + final green = (color.g * 255).toInt().toRadixString(16).padLeft(2, '0'); + final blue = (color.b * 255).toInt().toRadixString(16).padLeft(2, '0'); + + return '0x$alpha$red$green$blue'.toLowerCase(); + } } diff --git a/frontend/appflowy_flutter/packages/flowy_infra/pubspec.yaml b/frontend/appflowy_flutter/packages/flowy_infra/pubspec.yaml index cb5cbb9cee..9bf0245dc0 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra/pubspec.yaml +++ b/frontend/appflowy_flutter/packages/flowy_infra/pubspec.yaml @@ -1,5 +1,5 @@ name: flowy_infra -description: A new Flutter package project. +description: AppFlowy Infra. version: 0.0.1 homepage: https://appflowy.io @@ -15,50 +15,14 @@ dependencies: path: ^1.8.2 time: ">=2.0.0" uuid: ">=2.2.2" - bloc: ^8.1.2 + bloc: ^9.0.0 freezed_annotation: ^2.1.0 file_picker: ^8.0.2 file: ^7.0.0 + analyzer: 6.11.0 dev_dependencies: build_runner: ^2.4.9 flutter_lints: ^3.0.1 freezed: ^2.4.7 json_serializable: ^6.5.4 - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter. -flutter: - - # To add assets to your package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # - # For details regarding assets in packages, see - # https://flutter.dev/assets-and-images/#from-packages - # - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - - # To add custom fonts to your package, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts in packages, see - # https://flutter.dev/custom-fonts/#from-packages diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/flowy_infra_ui_platform_interface/pubspec.yaml b/frontend/appflowy_flutter/packages/flowy_infra_ui/flowy_infra_ui_platform_interface/pubspec.yaml index f2e3eb8749..4a8ad910cb 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/flowy_infra_ui_platform_interface/pubspec.yaml +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/flowy_infra_ui_platform_interface/pubspec.yaml @@ -1,7 +1,7 @@ name: flowy_infra_ui_platform_interface description: A new Flutter package project. version: 0.0.1 -homepage: +homepage: https://github.com/appflowy-io/appflowy environment: sdk: ">=2.12.0 <3.0.0" @@ -17,5 +17,3 @@ dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^3.0.1 - -flutter: \ No newline at end of file diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/flowy_infra_ui_web/pubspec.yaml b/frontend/appflowy_flutter/packages/flowy_infra_ui/flowy_infra_ui_web/pubspec.yaml index bbdac0d2e4..d4364a6400 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/flowy_infra_ui_web/pubspec.yaml +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/flowy_infra_ui_web/pubspec.yaml @@ -1,7 +1,7 @@ name: flowy_infra_ui_web description: A new Flutter package project. version: 0.0.1 -homepage: +homepage: https://github.com/appflowy-io/appflowy publish_to: none environment: @@ -25,4 +25,4 @@ flutter: platforms: web: pluginClass: FlowyInfraUIPlugin - fileName: flowy_infra_ui_web.dart \ No newline at end of file + fileName: flowy_infra_ui_web.dart diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/src/flowy_overlay/appflowy_popover.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/src/flowy_overlay/appflowy_popover.dart index 190d840a41..6a154d4d48 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/src/flowy_overlay/appflowy_popover.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/src/flowy_overlay/appflowy_popover.dart @@ -4,6 +4,23 @@ import 'package:flutter/material.dart'; export 'package:appflowy_popover/appflowy_popover.dart'; +class ShadowConstants { + ShadowConstants._(); + + static const List lightSmall = [ + BoxShadow(offset: Offset(0, 4), blurRadius: 20, color: Color(0x1A1F2329)), + ]; + static const List lightMedium = [ + BoxShadow(offset: Offset(0, 4), blurRadius: 32, color: Color(0x121F2225)), + ]; + static const List darkSmall = [ + BoxShadow(offset: Offset(0, 2), blurRadius: 16, color: Color(0x7A000000)), + ]; + static const List darkMedium = [ + BoxShadow(offset: Offset(0, 4), blurRadius: 32, color: Color(0x7A000000)), + ]; +} + class AppFlowyPopover extends StatelessWidget { const AppFlowyPopover({ super.key, @@ -25,6 +42,7 @@ class AppFlowyPopover extends StatelessWidget { this.skipTraversal = false, this.decorationColor, this.borderRadius, + this.popoverDecoration, this.animationDuration = const Duration(), this.slideDistance = 5.0, this.beginScaleFactor = 0.9, @@ -56,6 +74,7 @@ class AppFlowyPopover extends StatelessWidget { final double endScaleFactor; final double beginOpacity; final double endOpacity; + final Decoration? popoverDecoration; /// The widget that will be used to trigger the popover. /// @@ -102,6 +121,7 @@ class AppFlowyPopover extends StatelessWidget { popupBuilder: (context) => _PopoverContainer( constraints: constraints, margin: margin, + decoration: popoverDecoration, decorationColor: decorationColor, borderRadius: borderRadius, child: popupBuilder(context), @@ -116,6 +136,7 @@ class _PopoverContainer extends StatelessWidget { const _PopoverContainer({ this.decorationColor, this.borderRadius, + this.decoration, required this.child, required this.margin, required this.constraints, @@ -126,6 +147,7 @@ class _PopoverContainer extends StatelessWidget { final EdgeInsets margin; final Color? decorationColor; final BorderRadius? borderRadius; + final Decoration? decoration; @override Widget build(BuildContext context) { @@ -133,10 +155,11 @@ class _PopoverContainer extends StatelessWidget { type: MaterialType.transparency, child: Container( padding: margin, - decoration: context.getPopoverDecoration( - color: decorationColor, - borderRadius: borderRadius, - ), + decoration: decoration ?? + context.getPopoverDecoration( + color: decorationColor, + borderRadius: borderRadius, + ), constraints: constraints, child: child, ), @@ -144,7 +167,7 @@ class _PopoverContainer extends StatelessWidget { } } -extension on BuildContext { +extension PopoverDecoration on BuildContext { /// The decoration of the popover. /// /// Don't customize the entire decoration of the popover, @@ -156,26 +179,9 @@ extension on BuildContext { final borderColor = Theme.of(this).brightness == Brightness.light ? ColorSchemeConstants.lightBorderColor : ColorSchemeConstants.darkBorderColor; - final shadows = [ - const BoxShadow( - color: Color(0x0A1F2329), - blurRadius: 24, - offset: Offset(0, 8), - spreadRadius: 8, - ), - const BoxShadow( - color: Color(0x0A1F2329), - blurRadius: 12, - offset: Offset(0, 6), - spreadRadius: 0, - ), - const BoxShadow( - color: Color(0x0F1F2329), - blurRadius: 8, - offset: Offset(0, 4), - spreadRadius: -8, - ) - ]; + final shadows = Theme.of(this).brightness == Brightness.light + ? ShadowConstants.lightSmall + : ShadowConstants.darkSmall; return ShapeDecoration( color: color ?? Theme.of(this).cardColor, shape: RoundedRectangleBorder( diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_popover_layout.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_popover_layout.dart index 0d4bacde52..fb29bb0637 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_popover_layout.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/src/flowy_overlay/flowy_popover_layout.dart @@ -1,5 +1,7 @@ import 'dart:math' as math; + import 'package:flutter/material.dart'; + import 'flowy_overlay.dart'; class PopoverLayoutDelegate extends SingleChildLayoutDelegate { @@ -133,8 +135,6 @@ class PopoverLayoutDelegate extends SingleChildLayoutDelegate { case AnchorDirection.custom: childConstraints = constraints.loosen(); break; - default: - throw UnimplementedError(); } return childConstraints; } diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/src/flowy_overlay/layout.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/src/flowy_overlay/layout.dart index 87dd63b715..84e7bb8ebd 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/src/flowy_overlay/layout.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/src/flowy_overlay/layout.dart @@ -1,5 +1,7 @@ import 'dart:math' as math; + import 'package:flutter/material.dart'; + import 'flowy_overlay.dart'; class OverlayLayoutDelegate extends SingleChildLayoutDelegate { @@ -133,8 +135,6 @@ class OverlayLayoutDelegate extends SingleChildLayoutDelegate { case AnchorDirection.custom: childConstraints = constraints.loosen(); break; - default: - throw UnimplementedError(); } return childConstraints; } diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/src/flowy_overlay/list_overlay.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/src/flowy_overlay/list_overlay.dart index 2b61839ac3..d1964e0c83 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/src/flowy_overlay/list_overlay.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/src/flowy_overlay/list_overlay.dart @@ -128,7 +128,7 @@ class OverlayContainer extends StatelessWidget { padding: padding, decoration: FlowyDecoration.decoration( Theme.of(context).colorScheme.surface, - Theme.of(context).colorScheme.shadow.withOpacity(0.15), + Theme.of(context).colorScheme.shadow.withValues(alpha: 0.15), ), constraints: constraints, child: child, diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart index 0e5b656a35..0273807980 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart @@ -72,7 +72,7 @@ class FlowyIconTextButton extends StatelessWidget { style: HoverStyle( borderRadius: radius ?? Corners.s6Border, hoverColor: color, - borderColor: borderColor ?? Colors.transparent, + border: borderColor == null ? null : Border.all(color: borderColor!), ), onHover: disable ? null : onHover, isSelected: () => isSelected, @@ -220,7 +220,7 @@ class FlowyButton extends StatelessWidget { style: HoverStyle( borderRadius: radius ?? Corners.s6Border, hoverColor: color, - borderColor: borderColor ?? Colors.transparent, + border: borderColor == null ? null : Border.all(color: borderColor!), backgroundColor: backgroundColor ?? Colors.transparent, ), onHover: disable ? null : onHover, diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/hover.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/hover.dart index b9e97860f7..e734c4bb68 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/hover.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/hover.dart @@ -82,8 +82,7 @@ class _FlowyHoverState extends State { } class HoverStyle { - final Color borderColor; - final double borderWidth; + final BoxBorder? border; final Color? hoverColor; final Color? foregroundColorOnHover; final BorderRadius borderRadius; @@ -91,8 +90,7 @@ class HoverStyle { final Color backgroundColor; const HoverStyle({ - this.borderColor = Colors.transparent, - this.borderWidth = 0, + this.border, this.borderRadius = const BorderRadius.all(Radius.circular(6)), this.contentMargin = EdgeInsets.zero, this.backgroundColor = Colors.transparent, @@ -101,13 +99,12 @@ class HoverStyle { }); const HoverStyle.transparent({ - this.borderColor = Colors.transparent, - this.borderWidth = 0, this.borderRadius = const BorderRadius.all(Radius.circular(6)), this.contentMargin = EdgeInsets.zero, this.backgroundColor = Colors.transparent, this.foregroundColorOnHover, - }) : hoverColor = Colors.transparent; + }) : hoverColor = Colors.transparent, + border = null; } class FlowyHoverContainer extends StatelessWidget { @@ -124,11 +121,6 @@ class FlowyHoverContainer extends StatelessWidget { @override Widget build(BuildContext context) { - final hoverBorder = Border.all( - color: style.borderColor, - width: style.borderWidth, - ); - final theme = Theme.of(context); final textTheme = theme.textTheme; final iconTheme = theme.iconTheme; @@ -147,7 +139,7 @@ class FlowyHoverContainer extends StatelessWidget { return Container( margin: style.contentMargin, decoration: BoxDecoration( - border: hoverBorder, + border: style.border, color: applyStyle ? style.hoverColor ?? Theme.of(context).colorScheme.secondary : style.backgroundColor, diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/primary_rounded_button.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/primary_rounded_button.dart index c227d8b8ef..61f92fd073 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/primary_rounded_button.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/primary_rounded_button.dart @@ -49,11 +49,12 @@ class PrimaryRoundedButton extends StatelessWidget { figmaLineHeight: figmaLineHeight, color: textColor ?? Theme.of(context).colorScheme.onPrimary, textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, ), margin: margin ?? const EdgeInsets.symmetric(horizontal: 14.0), backgroundColor: backgroundColor ?? Theme.of(context).colorScheme.primary, - hoverColor: - hoverColor ?? Theme.of(context).colorScheme.primary.withOpacity(0.9), + hoverColor: hoverColor ?? + Theme.of(context).colorScheme.primary.withValues(alpha: 0.9), radius: BorderRadius.circular(radius ?? 10.0), onTap: onTap, ); diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart index 76e9eefbc2..360578a4a6 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart @@ -15,7 +15,6 @@ class FlowyText extends StatelessWidget { final TextDecoration? decoration; final Color? decorationColor; final double? decorationThickness; - final bool selectable; final String? fontFamily; final List? fallbackFontFamily; final bool withTooltip; @@ -41,7 +40,6 @@ class FlowyText extends StatelessWidget { this.maxLines = 1, this.decoration, this.decorationColor, - this.selectable = false, this.fontFamily, this.fallbackFontFamily, // // https://api.flutter.dev/flutter/painting/TextStyle/height.html @@ -63,7 +61,6 @@ class FlowyText extends StatelessWidget { this.maxLines = 1, this.decoration, this.decorationColor, - this.selectable = false, this.fontFamily, this.fallbackFontFamily, this.lineHeight, @@ -86,7 +83,6 @@ class FlowyText extends StatelessWidget { this.maxLines = 1, this.decoration, this.decorationColor, - this.selectable = false, this.fontFamily, this.fallbackFontFamily, this.lineHeight, @@ -108,7 +104,6 @@ class FlowyText extends StatelessWidget { this.maxLines = 1, this.decoration, this.decorationColor, - this.selectable = false, this.fontFamily, this.fallbackFontFamily, this.lineHeight, @@ -130,7 +125,6 @@ class FlowyText extends StatelessWidget { this.maxLines = 1, this.decoration, this.decorationColor, - this.selectable = false, this.fontFamily, this.fallbackFontFamily, this.lineHeight, @@ -153,7 +147,6 @@ class FlowyText extends StatelessWidget { this.maxLines = 1, this.decoration, this.decorationColor, - this.selectable = false, this.lineHeight, this.withTooltip = false, this.strutStyle = const StrutStyle(forceStrutHeight: true), @@ -211,32 +204,21 @@ class FlowyText extends StatelessWidget { : null, ); - if (selectable) { - child = IntrinsicHeight( - child: SelectableText( - text, - maxLines: maxLines, - textAlign: textAlign, - style: textStyle, - ), - ); - } else { - child = Text( - text, - maxLines: maxLines, - textAlign: textAlign, - overflow: overflow ?? TextOverflow.clip, - style: textStyle, - strutStyle: !isEmoji || (isEmoji && optimizeEmojiAlign) - ? StrutStyle.fromTextStyle( - textStyle, - forceStrutHeight: true, - leadingDistribution: TextLeadingDistribution.even, - height: lineHeight, - ) - : null, - ); - } + child = Text( + text, + maxLines: maxLines, + textAlign: textAlign, + overflow: overflow ?? TextOverflow.clip, + style: textStyle, + strutStyle: !isEmoji || (isEmoji && optimizeEmojiAlign) + ? StrutStyle.fromTextStyle( + textStyle, + forceStrutHeight: true, + leadingDistribution: TextLeadingDistribution.even, + height: lineHeight, + ) + : null, + ); if (withTooltip) { child = FlowyTooltip( diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text_input.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text_input.dart index bc391daf9e..95fd82363e 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text_input.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text_input.dart @@ -1,11 +1,10 @@ import 'dart:async'; import 'dart:math' as math; +import 'package:flowy_infra/size.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flowy_infra/size.dart'; - class FlowyFormTextInput extends StatelessWidget { static EdgeInsets kDefaultTextInputPadding = const EdgeInsets.only(bottom: 2); @@ -68,7 +67,7 @@ class FlowyFormTextInput extends StatelessWidget { hintStyle: Theme.of(context) .textTheme .bodyMedium! - .copyWith(color: Theme.of(context).hintColor.withOpacity(0.7)), + .copyWith(color: Theme.of(context).hintColor.withValues(alpha: 0.7)), isDense: true, inputBorder: const ThinUnderlineBorder( borderSide: BorderSide(width: 5, color: Colors.red), diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/toolbar_button.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/toolbar_button.dart new file mode 100644 index 0000000000..96a22a6f85 --- /dev/null +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/toolbar_button.dart @@ -0,0 +1,43 @@ +import 'package:flowy_infra/size.dart'; +import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; +import 'package:flutter/material.dart'; + +class FlowyToolbarButton extends StatelessWidget { + final Widget child; + final VoidCallback? onPressed; + final EdgeInsets padding; + final String? tooltip; + + const FlowyToolbarButton({ + super.key, + this.onPressed, + this.tooltip, + this.padding = const EdgeInsets.symmetric(vertical: 8, horizontal: 6), + required this.child, + }); + + @override + Widget build(BuildContext context) { + final tooltipMessage = tooltip ?? ''; + + return FlowyTooltip( + message: tooltipMessage, + padding: EdgeInsets.zero, + child: RawMaterialButton( + clipBehavior: Clip.antiAlias, + constraints: const BoxConstraints(minWidth: 36, minHeight: 32), + hoverElevation: 0, + highlightElevation: 0, + padding: EdgeInsets.zero, + shape: const RoundedRectangleBorder(borderRadius: Corners.s6Border), + hoverColor: Colors.transparent, + focusColor: Colors.transparent, + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + elevation: 0, + onPressed: onPressed, + child: child, + ), + ); + } +} diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/buttons/base_styled_button.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/buttons/base_styled_button.dart index 8087c712fe..c81c81f356 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/buttons/base_styled_button.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/buttons/base_styled_button.dart @@ -121,7 +121,7 @@ class BaseStyledBtnState extends State { fillColor: Colors.transparent, hoverColor: widget.hoverColor ?? Colors.transparent, highlightColor: widget.highlightColor ?? Colors.transparent, - focusColor: widget.focusColor ?? Colors.grey.withOpacity(0.35), + focusColor: widget.focusColor ?? Colors.grey.withValues(alpha: 0.35), constraints: BoxConstraints( minHeight: widget.minHeight ?? 0, minWidth: widget.minWidth ?? 0), onPressed: widget.onPressed, diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/dialog/styled_dialogs.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/dialog/styled_dialogs.dart index e29f778d84..7048ed32ec 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/dialog/styled_dialogs.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/dialog/styled_dialogs.dart @@ -96,7 +96,7 @@ class Dialogs { {required Widget child}) async { return await Navigator.of(context).push( StyledDialogRoute( - barrier: DialogBarrier(color: Colors.black.withOpacity(0.4)), + barrier: DialogBarrier(color: Colors.black.withValues(alpha: 0.4)), pageBuilder: (BuildContext buildContext, Animation animation, Animation secondaryAnimation) { return SafeArea(child: child); diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/flowy_tooltip.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/flowy_tooltip.dart index c4c3263d39..5b0b791c6c 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/flowy_tooltip.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/flowy_tooltip.dart @@ -10,6 +10,7 @@ class FlowyTooltip extends StatelessWidget { this.preferBelow, this.margin, this.verticalOffset, + this.padding, this.child, }); @@ -19,6 +20,7 @@ class FlowyTooltip extends StatelessWidget { final EdgeInsetsGeometry? margin; final Widget? child; final double? verticalOffset; + final EdgeInsets? padding; @override Widget build(BuildContext context) { @@ -29,10 +31,11 @@ class FlowyTooltip extends StatelessWidget { return Tooltip( margin: margin, verticalOffset: verticalOffset ?? 16.0, - padding: const EdgeInsets.symmetric( - horizontal: 12.0, - vertical: 8.0, - ), + padding: padding ?? + const EdgeInsets.symmetric( + horizontal: 12.0, + vertical: 8.0, + ), decoration: BoxDecoration( color: context.tooltipBackgroundColor(), borderRadius: BorderRadius.circular(10.0), @@ -47,10 +50,77 @@ class FlowyTooltip extends StatelessWidget { } } +class ManualTooltip extends StatefulWidget { + const ManualTooltip({ + super.key, + this.message, + this.richMessage, + this.preferBelow, + this.margin, + this.verticalOffset, + this.padding, + this.showAutomaticlly = false, + this.child, + }); + + final String? message; + final InlineSpan? richMessage; + final bool? preferBelow; + final EdgeInsetsGeometry? margin; + final Widget? child; + final double? verticalOffset; + final EdgeInsets? padding; + final bool showAutomaticlly; + + @override + State createState() => _ManualTooltipState(); +} + +class _ManualTooltipState extends State { + final key = GlobalKey(); + + @override + void initState() { + if (widget.showAutomaticlly) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) key.currentState?.ensureTooltipVisible(); + }); + } + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Tooltip( + key: key, + margin: widget.margin, + verticalOffset: widget.verticalOffset ?? 16.0, + triggerMode: widget.showAutomaticlly ? TooltipTriggerMode.manual : null, + padding: widget.padding ?? + const EdgeInsets.symmetric( + horizontal: 12.0, + vertical: 8.0, + ), + decoration: BoxDecoration( + color: context.tooltipBackgroundColor(), + borderRadius: BorderRadius.circular(10.0), + ), + waitDuration: _tooltipWaitDuration, + message: widget.message, + textStyle: widget.message != null ? context.tooltipTextStyle() : null, + richMessage: widget.richMessage, + preferBelow: widget.preferBelow, + child: widget.child, + ); + } +} + extension FlowyToolTipExtension on BuildContext { double tooltipFontSize() => 14.0; + double tooltipHeight({double? fontSize}) => 20.0 / (fontSize ?? tooltipFontSize()); + Color tooltipFontColor() => Theme.of(this).brightness == Brightness.light ? Colors.white : Colors.black; @@ -66,7 +136,7 @@ extension FlowyToolTipExtension on BuildContext { } TextStyle? tooltipHintTextStyle({double? fontSize}) => tooltipTextStyle( - fontColor: tooltipFontColor().withOpacity(0.7), + fontColor: tooltipFontColor().withValues(alpha: 0.7), fontSize: fontSize, ); diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/pubspec.yaml b/frontend/appflowy_flutter/packages/flowy_infra_ui/pubspec.yaml index 62cb26d4e0..b5b5c22bc7 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/pubspec.yaml +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/pubspec.yaml @@ -31,6 +31,8 @@ dependencies: flowy_svg: path: ../flowy_svg + analyzer: 6.11.0 + dev_dependencies: build_runner: ^2.4.9 provider: ^6.0.5 diff --git a/frontend/appflowy_flutter/packages/flowy_svg/bin/flowy_svg.dart b/frontend/appflowy_flutter/packages/flowy_svg/bin/flowy_svg.dart index cbf114156d..e87bb3fa01 100644 --- a/frontend/appflowy_flutter/packages/flowy_svg/bin/flowy_svg.dart +++ b/frontend/appflowy_flutter/packages/flowy_svg/bin/flowy_svg.dart @@ -242,7 +242,13 @@ String varNameFor(File file, Options options) { return simplified; } -const sizeMap = {r'$16x': 's', r'$24x': 'm', r'$32x': 'lg', r'$40x': 'xl'}; +const sizeMap = { + r'$16x': 's', + r'$20x': 'm', + r'$24x': 'm', + r'$32x': 'lg', + r'$40x': 'xl' +}; /// cleans the path segment before rejoining the path into a variable name String clean(String segment) { diff --git a/frontend/appflowy_flutter/packages/flowy_svg/lib/src/flowy_svg.dart b/frontend/appflowy_flutter/packages/flowy_svg/lib/src/flowy_svg.dart index cba112dc2b..1f861156eb 100644 --- a/frontend/appflowy_flutter/packages/flowy_svg/lib/src/flowy_svg.dart +++ b/frontend/appflowy_flutter/packages/flowy_svg/lib/src/flowy_svg.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +export 'package:flutter_svg/flutter_svg.dart'; + /// The class for FlowySvgData that the code generator will implement class FlowySvgData { /// The svg data @@ -80,7 +82,7 @@ class FlowySvg extends StatelessWidget { Widget build(BuildContext context) { Color? iconColor = color ?? Theme.of(context).iconTheme.color; if (opacity != null) { - iconColor = iconColor?.withOpacity(opacity!); + iconColor = iconColor?.withValues(alpha: opacity!); } final textScaleFactor = MediaQuery.textScalerOf(context).scale(1); diff --git a/frontend/appflowy_flutter/pubspec.lock b/frontend/appflowy_flutter/pubspec.lock index 87a5128960..1a393e1180 100644 --- a/frontend/appflowy_flutter/pubspec.lock +++ b/frontend/appflowy_flutter/pubspec.lock @@ -5,18 +5,23 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" + sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" url: "https://pub.dev" source: hosted - version: "67.0.0" - analyzer: + version: "76.0.0" + _macros: dependency: transitive + description: dart + source: sdk + version: "0.3.3" + analyzer: + dependency: "direct main" description: name: analyzer - sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" + sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" url: "https://pub.dev" source: hosted - version: "6.4.1" + version: "6.11.0" animations: dependency: transitive description: @@ -25,22 +30,54 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.11" + ansicolor: + dependency: transitive + description: + name: ansicolor + sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f" + url: "https://pub.dev" + source: hosted + version: "2.0.3" any_date: dependency: "direct main" description: name: any_date - sha256: "3981efcc15edd1673bcfc1aec298cc6079029fbffb3734c7eae8ceeb878f911e" + sha256: e9ed245ba44ccebf3c2d6daa3592213f409821128593d448b219a1f8e9bd17a1 url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.1.1" app_links: dependency: "direct main" description: name: app_links - sha256: "3ced568a5d9e309e99af71285666f1f3117bddd0bd5b3317979dccc1a40cada4" + sha256: "433df2e61b10519407475d7f69e470789d23d593f28224c38ba1068597be7950" url: "https://pub.dev" source: hosted - version: "3.5.1" + version: "6.3.3" + app_links_linux: + dependency: transitive + description: + name: app_links_linux + sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81 + url: "https://pub.dev" + source: hosted + version: "1.0.3" + app_links_platform_interface: + dependency: transitive + description: + name: app_links_platform_interface + sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + app_links_web: + dependency: transitive + description: + name: app_links_web + sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555 + url: "https://pub.dev" + source: hosted + version: "1.0.4" appflowy_backend: dependency: "direct main" description: @@ -52,8 +89,8 @@ packages: dependency: "direct main" description: path: "." - ref: "5517c8704c0dbeaeda5601e9baadb4cc2b29990d" - resolved-ref: "5517c8704c0dbeaeda5601e9baadb4cc2b29990d" + ref: e8317c0d1af8d23dc5707b02ea43864536b6de91 + resolved-ref: e8317c0d1af8d23dc5707b02ea43864536b6de91 url: "https://github.com/AppFlowy-IO/appflowy-board.git" source: git version: "0.1.2" @@ -61,17 +98,17 @@ packages: dependency: "direct main" description: path: "." - ref: c68e5f6 - resolved-ref: c68e5f6c585205083e27e875b822656425b2853f + ref: "680222f" + resolved-ref: "680222f503f90d07c08c99c42764f9b08fd0f46c" url: "https://github.com/AppFlowy-IO/appflowy-editor.git" source: git - version: "4.0.0" + version: "5.1.0" appflowy_editor_plugins: dependency: "direct main" description: path: "packages/appflowy_editor_plugins" - ref: "8047c21" - resolved-ref: "8047c21868273d544684522eb61e4ac2d2041409" + ref: "4efcff7" + resolved-ref: "4efcff720ed01dd4d0f5f88a9f1ff6f79f423caa" url: "https://github.com/AppFlowy-IO/AppFlowy-plugins.git" source: git version: "0.0.6" @@ -89,6 +126,13 @@ packages: relative: true source: path version: "0.0.1" + appflowy_ui: + dependency: "direct main" + description: + path: "packages/appflowy_ui" + relative: true + source: path + version: "1.0.0" archive: dependency: "direct main" description: @@ -101,10 +145,10 @@ packages: dependency: transitive description: name: args - sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.6.0" async: dependency: transitive description: @@ -121,22 +165,57 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.4" + auto_updater: + dependency: "direct main" + description: + path: "packages/auto_updater" + ref: "1d81a824f3633f1d0200ba51b78fe0f9ce429458" + resolved-ref: "1d81a824f3633f1d0200ba51b78fe0f9ce429458" + url: "https://github.com/LucasXu0/auto_updater.git" + source: git + version: "1.0.0" + auto_updater_macos: + dependency: "direct overridden" + description: + path: "packages/auto_updater_macos" + ref: "1d81a824f3633f1d0200ba51b78fe0f9ce429458" + resolved-ref: "1d81a824f3633f1d0200ba51b78fe0f9ce429458" + url: "https://github.com/LucasXu0/auto_updater.git" + source: git + version: "1.0.0" + auto_updater_platform_interface: + dependency: "direct overridden" + description: + path: "packages/auto_updater_platform_interface" + ref: "1d81a824f3633f1d0200ba51b78fe0f9ce429458" + resolved-ref: "1d81a824f3633f1d0200ba51b78fe0f9ce429458" + url: "https://github.com/LucasXu0/auto_updater.git" + source: git + version: "1.0.0" + auto_updater_windows: + dependency: transitive + description: + name: auto_updater_windows + sha256: "2bba20a71eee072f49b7267fedd5c4f1406c4b1b1e5b83932c634dbab75b80c9" + url: "https://pub.dev" + source: hosted + version: "1.0.0" avatar_stack: dependency: "direct main" description: name: avatar_stack - sha256: e4a1576f7478add964bbb8aa5e530db39288fbbf81c30c4fb4b81162dd68aa49 + sha256: "354527ba139956fd6439e2c49199d8298d72afdaa6c4cd6f37f26b97faf21f7e" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "3.0.0" barcode: dependency: transitive description: name: barcode - sha256: ab180ce22c6555d77d45f0178a523669db67f95856e3378259ef2ffeb43e6003 + sha256: "7b6729c37e3b7f34233e2318d866e8c48ddb46c1f7ad01ff7bb2a8de1da2b9f4" url: "https://pub.dev" source: hosted - version: "2.2.8" + version: "2.2.9" bidi: dependency: transitive description: @@ -189,18 +268,18 @@ packages: dependency: "direct main" description: name: bloc - sha256: "106842ad6569f0b60297619e9e0b1885c2fb9bf84812935490e6c5275777804e" + sha256: "52c10575f4445c61dd9e0cafcc6356fdd827c4c64dd7945ef3c4105f6b6ac189" url: "https://pub.dev" source: hosted - version: "8.1.4" + version: "9.0.0" bloc_test: dependency: "direct dev" description: name: bloc_test - sha256: "165a6ec950d9252ebe36dc5335f2e6eb13055f33d56db0eeb7642768849b43d2" + sha256: "1dd549e58be35148bc22a9135962106aa29334bc1e3f285994946a1057b29d7b" url: "https://pub.dev" source: hosted - version: "9.1.7" + version: "10.0.0" boolean_selector: dependency: transitive description: @@ -213,50 +292,50 @@ packages: dependency: transitive description: name: build - sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" build_config: dependency: transitive description: name: build_config - sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" build_daemon: dependency: transitive description: name: build_daemon - sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" + sha256: "294a2edaf4814a378725bfe6358210196f5ea37af89ecd81bfa32960113d4948" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.0.3" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + sha256: "99d3980049739a985cf9b21f30881f46db3ebc62c5b8d5e60e27440876b1ba1e" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.3" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "644dc98a0f179b872f612d3eb627924b578897c629788e858157fa5e704ca0c7" + sha256: "74691599a5bc750dc96a6b4bfd48f7d9d66453eab04c7f4063134800d6a5c573" url: "https://pub.dev" source: hosted - version: "2.4.11" + version: "2.4.14" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: e3c79f69a64bdfcd8a776a3c28db4eb6e3fb5356d013ae5eb2e52007706d5dbe + sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021" url: "https://pub.dev" source: hosted - version: "7.3.1" + version: "8.0.0" built_collection: dependency: transitive description: @@ -269,10 +348,10 @@ packages: dependency: transitive description: name: built_value - sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb + sha256: "28a712df2576b63c6c005c465989a348604960c0958d28be5303ba9baa841ac2" url: "https://pub.dev" source: hosted - version: "8.9.2" + version: "8.9.3" cached_network_image: dependency: "direct main" description: @@ -342,18 +421,18 @@ packages: dependency: transitive description: name: code_builder - sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 + sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" url: "https://pub.dev" source: hosted - version: "4.10.0" + version: "4.10.1" collection: dependency: "direct main" description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" connectivity_plus: dependency: "direct main" description: @@ -374,26 +453,26 @@ packages: dependency: transitive description: name: convert - sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" coverage: dependency: transitive description: name: coverage - sha256: c1fb2dce3c0085f39dc72668e85f8e0210ec7de05345821ff58530567df345a5 + sha256: e3493833ea012784c740e341952298f1cc77f1f01b1bbc3eb4eecf6984fb7f43 url: "https://pub.dev" source: hosted - version: "1.9.2" + version: "1.11.1" cross_cache: dependency: transitive description: name: cross_cache - sha256: ed30348320a7fefe4195c26cfcbabc76b7108ce3d364c4dd7c1b1c681a4cfe28 + sha256: "80329477264c73f09945ee780ccdc84df9231f878dc7227d132d301e34ff310b" url: "https://pub.dev" source: hosted - version: "0.0.2" + version: "0.0.4" cross_file: dependency: "direct main" description: @@ -406,26 +485,26 @@ packages: dependency: transitive description: name: crypto - sha256: ec30d999af904f33454ba22ed9a86162b35e52b44ac4807d1d93c288041d7d27 + sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.6" csslib: dependency: transitive description: name: csslib - sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.2" dart_style: dependency: transitive description: name: dart_style - sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" + sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" url: "https://pub.dev" source: hosted - version: "2.3.6" + version: "2.3.7" dbus: dependency: transitive description: @@ -446,26 +525,26 @@ packages: dependency: "direct main" description: name: desktop_drop - sha256: d55a010fe46c8e8fcff4ea4b451a9ff84a162217bdb3b2a0aa1479776205e15d + sha256: "03abf1c0443afdd1d65cf8fa589a2f01c67a11da56bbb06f6ea1de79d5628e94" url: "https://pub.dev" source: hosted - version: "0.4.4" + version: "0.5.0" device_info_plus: dependency: "direct main" description: name: device_info_plus - sha256: a7fd703482b391a87d60b6061d04dfdeab07826b96f9abd8f5ed98068acc0074 + sha256: "72d146c6d7098689ff5c5f66bcf593ac11efc530095385356e131070333e64da" url: "https://pub.dev" source: hosted - version: "10.1.2" + version: "11.3.0" device_info_plus_platform_interface: dependency: transitive description: name: device_info_plus_platform_interface - sha256: "282d3cf731045a2feb66abfe61bbc40870ae50a3ed10a4d3d217556c35c8c2ba" + sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2" url: "https://pub.dev" source: hosted - version: "7.0.1" + version: "7.0.2" diff_match_patch: dependency: transitive description: @@ -534,26 +613,34 @@ packages: dependency: "direct main" description: name: envied - sha256: bbff9c76120e4dc5e2e36a46690cf0a26feb65e7765633f4e8d916bcd173a450 + sha256: "08a9012e5d93e1a816919a52e37c7b8367e73ebb8d52d1ca7dd6fcd875a2cd2c" url: "https://pub.dev" source: hosted - version: "0.5.4+1" + version: "1.0.1" envied_generator: dependency: "direct dev" description: name: envied_generator - sha256: "517b70de08d13dcd40e97b4e5347e216a0b1c75c99e704f3c85c0474a392d14a" + sha256: "9a49ca9f3744069661c4f2c06993647699fae2773bca10b593fbb3228d081027" url: "https://pub.dev" source: hosted - version: "0.5.4+1" + version: "1.0.1" equatable: dependency: "direct main" description: name: equatable - sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" url: "https://pub.dev" source: hosted - version: "2.0.5" + version: "2.0.7" + event_bus: + dependency: "direct main" + description: + name: event_bus + sha256: "1a55e97923769c286d295240048fc180e7b0768902c3c2e869fe059aafa15304" + url: "https://pub.dev" + source: hosted + version: "2.0.1" expandable: dependency: "direct main" description: @@ -566,18 +653,18 @@ packages: dependency: "direct main" description: name: extended_text_field - sha256: "954c7eea1e82728a742f7ddf09b9a51cef087d4f52b716ba88cb3eb78ccd7c6e" + sha256: "3996195c117c6beb734026a7bc0ba80d7e4e84e4edd4728caa544d8209ab4d7d" url: "https://pub.dev" source: hosted - version: "15.0.0" + version: "16.0.2" extended_text_library: dependency: "direct main" description: name: extended_text_library - sha256: "55d09098ec56fab0d9a8a68950ca0bbf2efa1327937f7cec6af6dfa066234829" + sha256: "13d99f8a10ead472d5e2cf4770d3d047203fe5054b152e9eb5dc692a71befbba" url: "https://pub.dev" source: hosted - version: "12.0.0" + version: "12.0.1" fake_async: dependency: transitive description: @@ -603,29 +690,29 @@ packages: source: hosted version: "7.0.0" file_picker: - dependency: transitive + dependency: "direct overridden" description: name: file_picker - sha256: "167bb619cdddaa10ef2907609feb8a79c16dfa479d3afaf960f8e223f754bf12" + sha256: "16dc141db5a2ccc6520ebb6a2eb5945b1b09e95085c021d9f914f8ded7f1465c" url: "https://pub.dev" source: hosted - version: "8.1.2" + version: "8.1.4" file_selector_linux: dependency: transitive description: name: file_selector_linux - sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" + sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" url: "https://pub.dev" source: hosted - version: "0.9.2+1" + version: "0.9.3+2" file_selector_macos: dependency: transitive description: name: file_selector_macos - sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385 + sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc" url: "https://pub.dev" source: hosted - version: "0.9.4" + version: "0.9.4+2" file_selector_platform_interface: dependency: transitive description: @@ -638,34 +725,34 @@ packages: dependency: transitive description: name: file_selector_windows - sha256: "2ad726953f6e8affbc4df8dc78b77c3b4a060967a291e528ef72ae846c60fb69" + sha256: "8f5d2f6590d51ecd9179ba39c64f722edc15226cc93dcc8698466ad36a4a85a4" url: "https://pub.dev" source: hosted - version: "0.9.3+2" + version: "0.9.3+3" fixnum: dependency: "direct main" description: name: fixnum - sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" flex_color_picker: dependency: "direct main" description: name: flex_color_picker - sha256: "809af4ec82ede3b140ed0219b97d548de99e47aa4b99b14a10f705a2dbbcba5e" + sha256: c083b79f1c57eaeed9f464368be376951230b3cb1876323b784626152a86e480 url: "https://pub.dev" source: hosted - version: "3.5.1" + version: "3.7.0" flex_seed_scheme: dependency: transitive description: name: flex_seed_scheme - sha256: "7d97ba5c20f0e5cb1e3e2c17c865e1f797d129de31fc1f75d2dcce9470d6373c" + sha256: d3ba3c5c92d2d79d45e94b4c6c71d01fac3c15017da1545880c53864da5dfeb0 url: "https://pub.dev" source: hosted - version: "3.3.0" + version: "3.5.0" flowy_infra: dependency: "direct main" description: @@ -703,18 +790,18 @@ packages: dependency: "direct main" description: name: flutter_animate - sha256: "7c8a6594a9252dad30cc2ef16e33270b6248c4dedc3b3d06c86c4f3f4dc05ae5" + sha256: "7befe2d3252728afb77aecaaea1dec88a89d35b9b1d2eea6d04479e8af9117b5" url: "https://pub.dev" source: hosted - version: "4.5.0" + version: "4.5.2" flutter_bloc: dependency: "direct main" description: name: flutter_bloc - sha256: b594505eac31a0518bdcb4b5b79573b8d9117b193cc80cc12e17d639b10aa27a + sha256: "1046d719fbdf230330d3443187cc33cc11963d15c9089f6cc56faa42a4c5f0cc" url: "https://pub.dev" source: hosted - version: "8.1.6" + version: "9.1.0" flutter_cache_manager: dependency: "direct main" description: @@ -757,8 +844,8 @@ packages: dependency: "direct main" description: path: "." - ref: "8a9fa49" - resolved-ref: "8a9fa491cb3b86baf78b0a33c2c37a29d1cae028" + ref: "355aa56" + resolved-ref: "355aa56e9c74a91e00370a882739e0bb98c30bd8" url: "https://github.com/LucasXu0/emoji_mart.git" source: git version: "1.0.2" @@ -790,10 +877,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" + sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "5.0.0" flutter_localizations: dependency: transitive description: flutter @@ -811,26 +898,26 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "9ee02950848f61c4129af3d6ec84a1cfc0e47931abc746b03e7a3bc3e8ff6eda" + sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e" url: "https://pub.dev" source: hosted - version: "2.0.22" + version: "2.0.24" flutter_shaders: dependency: transitive description: name: flutter_shaders - sha256: "02750b545c01ff4d8e9bbe8f27a7731aa3778402506c67daa1de7f5fc3f4befe" + sha256: "34794acadd8275d971e02df03afee3dee0f98dbfb8c4837082ad0034f612a3e2" url: "https://pub.dev" source: hosted - version: "0.1.2" + version: "0.1.3" flutter_slidable: dependency: "direct main" description: name: flutter_slidable - sha256: "2c5611c0b44e20d180e4342318e1bbc28b0a44ad2c442f5df16962606fd3e8e3" + sha256: a857de7ea701f276fd6a6c4c67ae885b60729a3449e42766bb0e655171042801 url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" flutter_staggered_grid_view: dependency: "direct main" description: @@ -840,21 +927,21 @@ packages: source: hosted version: "0.7.0" flutter_sticky_header: - dependency: transitive + dependency: "direct overridden" description: name: flutter_sticky_header - sha256: "017f398fbb45a589e01491861ca20eb6570a763fd9f3888165a978e11248c709" + sha256: "7f76d24d119424ca0c95c146b8627a457e8de8169b0d584f766c2c545db8f8be" url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.7.0" flutter_svg: dependency: transitive description: name: flutter_svg - sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" + sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b url: "https://pub.dev" source: hosted - version: "2.0.10+1" + version: "2.0.17" flutter_test: dependency: "direct dev" description: flutter @@ -864,10 +951,10 @@ packages: dependency: "direct main" description: name: flutter_tex - sha256: "816074d8a49dd2301704aaf481f9b052b935b8790018a88b69a60d003d5e89e4" + sha256: ef7896946052e150514a2afe10f6e33e4fe0e7e4fc51195b65da811cb33c59ab url: "https://pub.dev" source: hosted - version: "4.0.9" + version: "4.0.13" flutter_web_plugins: dependency: transitive description: flutter @@ -877,18 +964,18 @@ packages: dependency: "direct main" description: name: fluttertoast - sha256: "95f349437aeebe524ef7d6c9bde3e6b4772717cf46a0eb6a3ceaddc740b297cc" + sha256: "24467dc20bbe49fd63e57d8e190798c4d22cbbdac30e54209d153a15273721d1" url: "https://pub.dev" source: hosted - version: "8.2.8" + version: "8.2.10" freezed: dependency: "direct dev" description: name: freezed - sha256: a434911f643466d78462625df76fd9eb13e57348ff43fe1f77bbe909522c67a1 + sha256: "44c19278dd9d89292cf46e97dc0c1e52ce03275f40a97c5a348e802a924bf40e" url: "https://pub.dev" source: hosted - version: "2.5.2" + version: "2.5.7" freezed_annotation: dependency: "direct main" description: @@ -914,10 +1001,10 @@ packages: dependency: "direct main" description: name: get_it - sha256: d85128a5dae4ea777324730dc65edd9c9f43155c109d5cc0a69cab74139fbac1 + sha256: f126a3e286b7f5b578bf436d5592968706c4c1de28a228b870ce375d9f743103 url: "https://pub.dev" source: hosted - version: "7.7.0" + version: "8.0.3" glob: dependency: transitive description: @@ -930,10 +1017,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: "2ddb88e9ad56ae15ee144ed10e33886777eb5ca2509a914850a5faa7b52ff459" + sha256: "7c2d40b59890a929824f30d442e810116caf5088482629c894b9e4478c67472d" url: "https://pub.dev" source: hosted - version: "14.2.7" + version: "14.6.3" google_fonts: dependency: "direct main" description: @@ -994,10 +1081,18 @@ packages: dependency: transitive description: name: html - sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" + sha256: "1fc58edeaec4307368c60d59b7e15b9d658b57d7f3125098b6294153c75337ec" url: "https://pub.dev" source: hosted - version: "0.15.4" + version: "0.15.5" + html2md: + dependency: "direct main" + description: + name: html2md + sha256: "465cf8ffa1b510fe0e97941579bf5b22e2d575f2cecb500a9c0254efe33a8036" + url: "https://pub.dev" + source: hosted + version: "1.3.2" http: dependency: "direct main" description: @@ -1010,18 +1105,18 @@ packages: dependency: transitive description: name: http_multi_server - sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" http_parser: dependency: transitive description: name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.1.2" iconsax_flutter: dependency: transitive description: @@ -1050,26 +1145,26 @@ packages: dependency: transitive description: name: image_picker_android - sha256: "8c5abf0dcc24fe6e8e0b4a5c0b51a5cf30cefdf6407a3213dae61edc75a70f56" + sha256: b62d34a506e12bb965e824b6db4fbf709ee4589cf5d3e99b45ab2287b008ee0c url: "https://pub.dev" source: hosted - version: "0.8.12+12" + version: "0.8.12+20" image_picker_for_web: dependency: transitive description: name: image_picker_for_web - sha256: "65d94623e15372c5c51bebbcb820848d7bcb323836e12dfdba60b5d3a8b39e50" + sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.6" image_picker_ios: dependency: transitive description: name: image_picker_ios - sha256: "6703696ad49f5c3c8356d576d7ace84d1faf459afb07accbb0fae780753ff447" + sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100" url: "https://pub.dev" source: hosted - version: "0.8.12" + version: "0.8.12+2" image_picker_linux: dependency: transitive description: @@ -1090,10 +1185,10 @@ packages: dependency: transitive description: name: image_picker_platform_interface - sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80" + sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.10.1" image_picker_windows: dependency: transitive description: @@ -1119,10 +1214,10 @@ packages: dependency: transitive description: name: io - sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" irondash_engine_context: dependency: transitive description: @@ -1167,10 +1262,10 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b + sha256: c2fcb3920cf2b6ae6845954186420fca40bc0a8abcc84903b7801f17d7050d7c url: "https://pub.dev" source: hosted - version: "6.8.0" + version: "6.9.0" keyboard_height_plugin: dependency: "direct main" description: @@ -1183,18 +1278,18 @@ packages: dependency: "direct main" description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.7" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.8" leak_tracker_testing: dependency: transitive description: @@ -1231,10 +1326,10 @@ packages: dependency: transitive description: name: lints - sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" + sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "5.1.1" loading_indicator: dependency: transitive description: @@ -1251,30 +1346,30 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.6" - logger: - dependency: transitive - description: - name: logger - sha256: "697d067c60c20999686a0add96cf6aba723b3aa1f83ecf806a8097231529ec32" - url: "https://pub.dev" - source: hosted - version: "2.4.0" logging: dependency: transitive description: name: logging - sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" + macros: + dependency: transitive + description: + name: macros + sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" + url: "https://pub.dev" + source: hosted + version: "0.1.3-main.0" markdown: dependency: "direct main" description: name: markdown - sha256: ef2a1298144e3f985cc736b22e0ccdaf188b5b3970648f2d9dc13efd1d9df051 + sha256: "935e23e1ff3bc02d390bad4d4be001208ee92cc217cb5b5a6c19bc14aaa318c1" url: "https://pub.dev" source: hosted - version: "7.2.2" + version: "7.3.0" markdown_widget: dependency: "direct main" description: @@ -1295,34 +1390,34 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: "direct main" description: name: mime - sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" url: "https://pub.dev" source: hosted - version: "1.0.6" + version: "2.0.0" mockito: dependency: transitive description: name: mockito - sha256: "6841eed20a7befac0ce07df8116c8b8233ed1f4486a7647c7fc5a02ae6163917" + sha256: f99d8d072e249f719a5531735d146d8cf04c580d93920b04de75bef6dfb2daf6 url: "https://pub.dev" source: hosted - version: "5.4.4" + version: "5.4.5" mocktail: dependency: "direct dev" description: @@ -1367,10 +1462,10 @@ packages: dependency: "direct main" description: name: numerus - sha256: "49cd96fe774dd1f574fc9117ed67e8a2b06a612f723e87ef3119456a7729d837" + sha256: a17a3f34527497e89378696a76f382b40dc534c4a57b3778de246ebc1ce2ca99 url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.0" octo_image: dependency: transitive description: @@ -1383,34 +1478,34 @@ packages: dependency: "direct main" description: name: open_filex - sha256: ba425ea49affd0a98a234aa9344b9ea5d4c4f7625a1377961eae9fe194c3d523 + sha256: dcb7bd3d32db8db5260253a62f1564c02c2c8df64bc0187cd213f65f827519bd url: "https://pub.dev" source: hosted - version: "4.5.0" + version: "4.6.0" package_config: dependency: transitive description: name: package_config - sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" package_info_plus: dependency: "direct main" description: name: package_info_plus - sha256: a75164ade98cb7d24cfd0a13c6408927c6b217fa60dee5a7ff5c116a58f28918 + sha256: "739e0a5c3c4055152520fa321d0645ee98e932718b4c8efeeb51451968fe0790" url: "https://pub.dev" source: hosted - version: "8.0.2" + version: "8.1.3" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66 + sha256: a5ef9986efc7bf772f2696183a3992615baa76c1ffb1189318dd8803778fb05b url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" path: dependency: "direct main" description: @@ -1431,34 +1526,34 @@ packages: dependency: transitive description: name: path_parsing - sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.1.0" path_provider: dependency: "direct main" description: name: path_provider - sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7" + sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" url: "https://pub.dev" source: hosted - version: "2.2.10" + version: "2.2.15" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" path_provider_linux: dependency: transitive description: @@ -1495,10 +1590,10 @@ packages: dependency: transitive description: name: pdf - sha256: "05df53f8791587402493ac97b9869d3824eccbc77d97855f4545cf72df3cae07" + sha256: "28eacad99bffcce2e05bba24e50153890ad0255294f4dd78a17075a2ba5c8416" url: "https://pub.dev" source: hosted - version: "3.11.1" + version: "3.11.3" percent_indicator: dependency: "direct main" description: @@ -1519,10 +1614,10 @@ packages: dependency: transitive description: name: permission_handler_android - sha256: "76e4ab092c1b240d31177bb64d2b0bea43f43d0e23541ec866151b9f7b2490fa" + sha256: "71bbecfee799e65aff7c744761a57e817e73b738fedf62ab7afd5593da21f9f1" url: "https://pub.dev" source: hosted - version: "12.0.12" + version: "12.0.13" permission_handler_apple: dependency: transitive description: @@ -1535,10 +1630,10 @@ packages: dependency: transitive description: name: permission_handler_html - sha256: af26edbbb1f2674af65a8f4b56e1a6f526156bc273d0e65dd8075fab51c78851 + sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" url: "https://pub.dev" source: hosted - version: "0.1.3+2" + version: "0.1.3+5" permission_handler_platform_interface: dependency: transitive description: @@ -1575,10 +1670,10 @@ packages: dependency: transitive description: name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" plugin_platform_interface: dependency: "direct dev" description: @@ -1623,18 +1718,18 @@ packages: dependency: transitive description: name: pub_semver - sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" pubspec_parse: dependency: transitive description: name: pubspec_parse - sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8 + sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.5.0" qr: dependency: transitive description: @@ -1654,10 +1749,11 @@ packages: reorderable_tabbar: dependency: "direct main" description: - name: reorderable_tabbar - sha256: dd19d7b6f60f0dec4be02ba0a2c860f9acbe5a392cb8b5b8c1417cbfcbfe923f - url: "https://pub.dev" - source: hosted + path: "." + ref: "93c4977" + resolved-ref: "93c4977ffab68906694cdeaea262be6045543c94" + url: "https://github.com/LucasXu0/reorderable_tabbar" + source: git version: "1.0.6" reorderables: dependency: "direct main" @@ -1683,6 +1779,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.27.7" + saver_gallery: + dependency: "direct main" + description: + name: saver_gallery + sha256: bf59475e50b73d666630bed7a5fdb621fed92d637f64e3c61ce81653ec6a833c + url: "https://pub.dev" + source: hosted + version: "4.0.1" scaled_app: dependency: "direct main" description: @@ -1695,10 +1799,42 @@ packages: dependency: transitive description: name: screen_retriever - sha256: "6ee02c8a1158e6dae7ca430da79436e3b1c9563c8cf02f524af997c201ac2b90" + sha256: "570dbc8e4f70bac451e0efc9c9bb19fa2d6799a11e6ef04f946d7886d2e23d0c" url: "https://pub.dev" source: hosted - version: "0.1.9" + version: "0.2.0" + screen_retriever_linux: + dependency: transitive + description: + name: screen_retriever_linux + sha256: f7f8120c92ef0784e58491ab664d01efda79a922b025ff286e29aa123ea3dd18 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_macos: + dependency: transitive + description: + name: screen_retriever_macos + sha256: "71f956e65c97315dd661d71f828708bd97b6d358e776f1a30d5aa7d22d78a149" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_platform_interface: + dependency: transitive + description: + name: screen_retriever_platform_interface + sha256: ee197f4581ff0d5608587819af40490748e1e39e648d7680ecf95c05197240c0 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + screen_retriever_windows: + dependency: transitive + description: + name: screen_retriever_windows + sha256: "449ee257f03ca98a57288ee526a301a430a344a161f9202b4fcc38576716fe13" + url: "https://pub.dev" + source: hosted + version: "0.2.0" scroll_to_index: dependency: "direct main" description: @@ -1735,42 +1871,42 @@ packages: dependency: "direct main" description: name: share_plus - sha256: "468c43f285207c84bcabf5737f33b914ceb8eb38398b91e5e3ad1698d1b72a52" + sha256: fce43200aa03ea87b91ce4c3ac79f0cecd52e2a7a56c7a4185023c271fbfa6da url: "https://pub.dev" source: hosted - version: "10.0.2" + version: "10.1.4" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface - sha256: "6ababf341050edff57da8b6990f11f4e99eaba837865e2e6defe16d039619db5" + sha256: cc012a23fc2d479854e6c80150696c4a5f5bb62cb89af4de1c505cf78d0a5d0b url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "5.0.2" shared_preferences: dependency: "direct main" description: name: shared_preferences - sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051" + sha256: a752ce92ea7540fc35a0d19722816e04d0e72828a4200e83a98cf1a1eb524c9a url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.5" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "480ba4345773f56acda9abf5f50bd966f581dac5d514e5fc4a18c62976bbba7e" + sha256: bf808be89fe9dc467475e982c1db6c2faf3d2acf54d526cd5ec37d86c99dbd84 url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.1" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" url: "https://pub.dev" source: hosted - version: "2.5.2" + version: "2.5.4" shared_preferences_linux: dependency: transitive description: @@ -1816,10 +1952,10 @@ packages: dependency: transitive description: name: shelf - sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.4.2" shelf_packages_handler: dependency: transitive description: @@ -1840,10 +1976,10 @@ packages: dependency: transitive description: name: shelf_web_socket - sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "2.0.1" simple_gesture_detector: dependency: transitive description: @@ -1864,7 +2000,7 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" sliver_tools: dependency: transitive description: @@ -1885,10 +2021,10 @@ packages: dependency: transitive description: name: source_helper - sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd" + sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" url: "https://pub.dev" source: hosted - version: "1.3.4" + version: "1.3.5" source_map_stack_trace: dependency: transitive description: @@ -1901,10 +2037,10 @@ packages: dependency: transitive description: name: source_maps - sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" + sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" url: "https://pub.dev" source: hosted - version: "0.10.12" + version: "0.10.13" source_span: dependency: transitive description: @@ -1925,26 +2061,50 @@ packages: dependency: transitive description: name: sqflite - sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d + sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb" url: "https://pub.dev" source: hosted - version: "2.3.3+1" + version: "2.4.1" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3" + url: "https://pub.dev" + source: hosted + version: "2.4.0" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4" + sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709" url: "https://pub.dev" source: hosted - version: "2.5.4" + version: "2.5.4+6" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c" + url: "https://pub.dev" + source: hosted + version: "2.4.1+1" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" stack_trace: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.0" stream_channel: dependency: transitive description: @@ -1957,18 +2117,18 @@ packages: dependency: transitive description: name: stream_transform - sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" string_scanner: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" string_validator: dependency: "direct main" description: @@ -1989,18 +2149,18 @@ packages: dependency: "direct main" description: name: super_clipboard - sha256: cfeb142360fac67e0da1ca339accb892eb790c6528a218a008eef1709d96ed0f + sha256: "4a6ae6dfaa282ec1f2bff750976f535517ed8ca842d5deae13985eb11c00ac1f" url: "https://pub.dev" source: hosted - version: "0.8.22" + version: "0.8.24" super_native_extensions: dependency: transitive description: name: super_native_extensions - sha256: "6a7cfb7d212da7023b86fb99c736081e9c2cd982265d15dc5fe6381a32dbc875" + sha256: a433bba8186cd6b707560c42535bf284804665231c00bca86faf1aa4968b7637 url: "https://pub.dev" source: hosted - version: "0.8.22" + version: "0.8.24" sync_http: dependency: transitive description: @@ -2013,10 +2173,10 @@ packages: dependency: "direct main" description: name: synchronized - sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558" + sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" url: "https://pub.dev" source: hosted - version: "3.1.0+1" + version: "3.3.0+3" tab_indicator_styler: dependency: transitive description: @@ -2029,10 +2189,34 @@ packages: dependency: "direct main" description: name: table_calendar - sha256: "4ca32b2fc919452c9974abd4c6ea611a63e33b9e4f0b8c38dba3ac1f4a6549d1" + sha256: b2896b7c86adf3a4d9c911d860120fe3dbe03c85db43b22fd61f14ee78cdbb63 url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" + talker: + dependency: "direct main" + description: + name: talker + sha256: "45abef5b92f9b9bd42c3f20133ad4b20ab12e1da2aa206fc0a40ea874bed7c5d" + url: "https://pub.dev" + source: hosted + version: "4.7.1" + talker_bloc_logger: + dependency: "direct main" + description: + name: talker_bloc_logger + sha256: "2214a5f6ef9ff33494dc6149321c270356962725cc8fc1a485d44b1d9b812ddd" + url: "https://pub.dev" + source: hosted + version: "4.7.1" + talker_logger: + dependency: transitive + description: + name: talker_logger + sha256: ed9b20b8c09efff9f6b7c63fc6630ee2f84aa92661ae09e5ba04e77272bf2ad2 + url: "https://pub.dev" + source: hosted + version: "4.7.1" term_glyph: dependency: transitive description: @@ -2045,50 +2229,50 @@ packages: dependency: transitive description: name: test - sha256: "7ee446762c2c50b3bd4ea96fe13ffac69919352bd3b4b17bac3f3465edc58073" + sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f" url: "https://pub.dev" source: hosted - version: "1.25.2" + version: "1.25.8" test_api: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.3" test_core: dependency: transitive description: name: test_core - sha256: "2bc4b4ecddd75309300d8096f781c0e3280ca1ef85beda558d33fcbedc2eead4" + sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.5" time: dependency: "direct main" description: name: time - sha256: ad8e018a6c9db36cb917a031853a1aae49467a93e0d464683e029537d848c221 + sha256: "370572cf5d1e58adcb3e354c47515da3f7469dac3a95b447117e728e7be6f461" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" timing: dependency: transitive description: name: timing - sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.2" toastification: dependency: "direct main" description: name: toastification - sha256: "441adf261f03b82db7067cba349756f70e9e2c0b7276bcba856210742f85f394" + sha256: "4d97fbfa463dfe83691044cba9f37cb185a79bb9205cfecb655fa1f6be126a13" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.0" tuple: dependency: transitive description: @@ -2101,10 +2285,10 @@ packages: dependency: transitive description: name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.4.0" universal_html: dependency: transitive description: @@ -2132,51 +2316,52 @@ packages: unsplash_client: dependency: "direct main" description: - name: unsplash_client - sha256: "9827f4c1036b7a6ac8cb3f404ac179df7441eee69371d9b17f181817fe502fd7" - url: "https://pub.dev" - source: hosted + path: "." + ref: a8411fc + resolved-ref: a8411fcead178834d1f4572f64dc78b059ffa221 + url: "https://github.com/LucasXu0/unsplash_client.git" + source: git version: "2.2.0" url_launcher: dependency: "direct main" description: name: url_launcher - sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" + sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.3.1" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: f0c73347dfcfa5b3db8bc06e1502668265d39c08f310c29bff4e28eea9699f79 + sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" url: "https://pub.dev" source: hosted - version: "6.3.9" + version: "6.3.14" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e + sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.3.2" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.2" url_launcher_platform_interface: dependency: "direct dev" description: @@ -2189,18 +2374,18 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" + sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.4.0" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.4" url_protocol: dependency: "direct main" description: @@ -2214,42 +2399,42 @@ packages: dependency: "direct overridden" description: name: uuid - sha256: f33d6bb662f0e4f79dcd7ada2e6170f3b3a2530c28fc41f49a411ddedd576a77 + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff url: "https://pub.dev" source: hosted - version: "4.5.0" + version: "4.5.1" value_layout_builder: dependency: transitive description: name: value_layout_builder - sha256: "98202ec1807e94ac72725b7f0d15027afde513c55c69ff3f41bcfccb950831bc" + sha256: c02511ea91ca5c643b514a33a38fa52536f74aa939ec367d02938b5ede6807fa url: "https://pub.dev" source: hosted - version: "0.3.1" + version: "0.4.0" vector_graphics: dependency: transitive description: name: vector_graphics - sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" + sha256: "27d5fefe86fb9aace4a9f8375b56b3c292b64d8c04510df230f849850d912cb7" url: "https://pub.dev" source: hosted - version: "1.1.11+1" + version: "1.1.15" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da + sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" url: "https://pub.dev" source: hosted - version: "1.1.11+1" + version: "1.1.13" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" + sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad" url: "https://pub.dev" source: hosted - version: "1.1.11+1" + version: "1.1.16" vector_math: dependency: transitive description: @@ -2258,6 +2443,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + version: + dependency: "direct main" + description: + name: version + sha256: "3d4140128e6ea10d83da32fef2fa4003fccbf6852217bb854845802f04191f94" + url: "https://pub.dev" + source: hosted + version: "3.0.2" visibility_detector: dependency: transitive description: @@ -2270,42 +2463,50 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.3.0" watcher: dependency: transitive description: name: watcher - sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" web: dependency: transitive description: name: web - sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.1.0" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "3.0.1" webdriver: dependency: transitive description: name: webdriver - sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" + sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.4" webkit_inspection_protocol: dependency: transitive description: @@ -2318,18 +2519,18 @@ packages: dependency: transitive description: name: webview_flutter - sha256: "6869c8786d179f929144b4a1f86e09ac0eddfe475984951ea6c634774c16b522" + sha256: "889a0a678e7c793c308c68739996227c9661590605e70b1f6cf6b9a6634f7aec" url: "https://pub.dev" source: hosted - version: "4.8.0" + version: "4.10.0" webview_flutter_android: dependency: transitive description: name: webview_flutter_android - sha256: ed021f27ae621bc97a6019fb601ab16331a3db4bf8afa305e9f6689bdb3edced + sha256: d1ee28f44894cbabb1d94cc42f9980297f689ff844d067ec50ff88d86e27d63f url: "https://pub.dev" source: hosted - version: "3.16.8" + version: "4.3.0" webview_flutter_platform_interface: dependency: transitive description: @@ -2342,52 +2543,52 @@ packages: dependency: transitive description: name: webview_flutter_plus - sha256: "57ec757eada4e23bfb015f5d5f84a45108cb2c29b1e77e23956768cd5e0c8468" + sha256: f883dfc94d03b1a2a17441c8e8a8e1941558ed3322f2b586cd06486114e18048 url: "https://pub.dev" source: hosted - version: "0.4.7" + version: "0.4.10" webview_flutter_wkwebview: dependency: transitive description: name: webview_flutter_wkwebview - sha256: "9c62cc46fa4f2d41e10ab81014c1de470a6c6f26051a2de32111b2ee55287feb" + sha256: "4adc14ea9a770cc9e2c8f1ac734536bd40e82615bd0fa6b94be10982de656cc7" url: "https://pub.dev" source: hosted - version: "3.14.0" + version: "3.17.0" win32: dependency: transitive description: name: win32 - sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a" + sha256: "154360849a56b7b67331c21f09a386562d88903f90a1099c5987afc1912e1f29" url: "https://pub.dev" source: hosted - version: "5.5.4" + version: "5.10.0" win32_registry: dependency: transitive description: name: win32_registry - sha256: "723b7f851e5724c55409bb3d5a32b203b3afe8587eaf5dafb93a5fed8ecda0d6" + sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852" url: "https://pub.dev" source: hosted - version: "1.1.4" + version: "1.1.5" window_manager: dependency: "direct main" description: name: window_manager - sha256: "8699323b30da4cdbe2aa2e7c9de567a6abd8a97d9a5c850a3c86dcd0b34bbfbf" + sha256: "732896e1416297c63c9e3fb95aea72d0355f61390263982a47fd519169dc5059" url: "https://pub.dev" source: hosted - version: "0.3.9" + version: "0.4.3" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.1.0" xml: - dependency: transitive + dependency: "direct main" description: name: xml sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 @@ -2398,10 +2599,10 @@ packages: dependency: transitive description: name: yaml - sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" sdks: - dart: ">=3.4.0 <4.0.0" - flutter: ">=3.22.0" + dart: ">=3.6.2 <4.0.0" + flutter: ">=3.27.4" diff --git a/frontend/appflowy_flutter/pubspec.yaml b/frontend/appflowy_flutter/pubspec.yaml index 759c16e474..1e92765ff6 100644 --- a/frontend/appflowy_flutter/pubspec.yaml +++ b/frontend/appflowy_flutter/pubspec.yaml @@ -4,35 +4,37 @@ description: Bring projects, wikis, and teams together with AI. AppFlowy is an your data. The best open source alternative to Notion. publish_to: "none" -version: 0.7.9 +version: 0.8.9 environment: - flutter: ">=3.22.0" + flutter: ">=3.27.4" sdk: ">=3.3.0 <4.0.0" dependencies: any_date: ^1.0.4 - app_links: ^3.5.0 + app_links: ^6.3.3 appflowy_backend: path: packages/appflowy_backend appflowy_board: git: url: https://github.com/AppFlowy-IO/appflowy-board.git - ref: 5517c8704c0dbeaeda5601e9baadb4cc2b29990d + ref: e8317c0d1af8d23dc5707b02ea43864536b6de91 appflowy_editor: appflowy_editor_plugins: appflowy_popover: path: packages/appflowy_popover appflowy_result: path: packages/appflowy_result - + appflowy_ui: + path: packages/appflowy_ui archive: ^3.4.10 auto_size_text_field: ^2.2.3 - avatar_stack: ^1.2.0 + auto_updater: ^1.0.0 + avatar_stack: ^3.0.0 # BitsDojo Window for Windows bitsdojo_window: ^0.1.6 - bloc: ^8.1.2 + bloc: ^9.0.0 cached_network_image: ^3.3.0 calendar_view: git: @@ -44,15 +46,15 @@ dependencies: # Desktop Drop uses Cross File (XFile) data type defer_pointer: ^0.0.2 - desktop_drop: ^0.4.4 + desktop_drop: ^0.5.0 device_info_plus: diffutil_dart: ^4.0.1 dotted_border: ^2.0.0+3 easy_localization: ^3.0.2 - envied: ^0.5.2 + envied: ^1.0.1 equatable: ^2.0.5 expandable: ^5.0.1 - extended_text_field: ^15.0.0 + extended_text_field: ^16.0.2 extended_text_library: ^12.0.0 file: ^7.0.0 fixnum: ^1.1.0 @@ -66,14 +68,14 @@ dependencies: flutter: sdk: flutter flutter_animate: ^4.5.0 - flutter_bloc: ^8.1.3 + flutter_bloc: ^9.1.0 flutter_cache_manager: ^3.3.1 - flutter_chat_core: ^0.0.2 - flutter_chat_ui: 2.0.0-dev.1 + flutter_chat_core: 0.0.2 + flutter_chat_ui: ^2.0.0-dev.1 flutter_emoji_mart: git: url: https://github.com/LucasXu0/emoji_mart.git - ref: "8a9fa49" + ref: "355aa56" flutter_math_fork: ^0.7.3 flutter_slidable: ^3.0.0 @@ -81,12 +83,13 @@ dependencies: flutter_tex: ^4.0.9 fluttertoast: ^8.2.6 freezed_annotation: ^2.2.0 - get_it: ^7.6.0 + get_it: ^8.0.3 go_router: ^14.2.0 google_fonts: ^6.1.0 highlight: ^0.7.0 hive_flutter: ^1.1.0 hotkey_manager: ^0.1.7 + html2md: ^1.3.2 http: ^1.0.0 image_picker: ^1.0.4 @@ -103,7 +106,7 @@ dependencies: local_notifier: ^0.1.5 markdown: markdown_widget: ^2.3.2+6 - mime: ^1.0.6 + mime: ^2.0.0 nanoid: ^1.0.0 numerus: ^2.1.2 @@ -112,7 +115,7 @@ dependencies: package_info_plus: ^8.0.2 path: ^1.8.3 path_provider: ^2.0.15 - percent_indicator: ^4.2.3 + percent_indicator: 4.2.3 permission_handler: ^11.3.1 protobuf: ^3.1.0 provider: ^6.0.5 @@ -129,10 +132,11 @@ dependencies: sized_context: ^1.0.0+4 string_validator: ^1.0.0 styled_widget: ^0.4.1 - super_clipboard: ^0.8.4 + super_clipboard: ^0.8.24 synchronized: ^3.1.0+1 table_calendar: ^3.0.9 time: ^2.1.3 + event_bus: ^2.0.1 toastification: ^2.0.0 universal_platform: ^1.1.0 @@ -141,13 +145,22 @@ dependencies: url_protocol: # Window Manager for MacOS and Linux - window_manager: ^0.3.9 + version: ^3.0.2 + xml: ^6.5.0 + window_manager: ^0.4.3 + saver_gallery: ^4.0.1 + talker_bloc_logger: ^4.7.1 + talker: ^4.7.1 + + analyzer: 6.11.0 dev_dependencies: - bloc_test: ^9.1.2 + # Introduce talker to log the bloc events, and only log the events in the development mode + + bloc_test: ^10.0.0 build_runner: ^2.4.9 - envied_generator: ^0.5.2 - flutter_lints: ^4.0.0 + envied_generator: ^1.0.1 + flutter_lints: ^5.0.0 flutter_test: sdk: flutter @@ -164,7 +177,7 @@ dev_dependencies: dependency_overrides: http: ^1.0.0 - device_info_plus: ^10.1.0 + device_info_plus: ^11.2.2 url_protocol: git: @@ -174,13 +187,13 @@ dependency_overrides: appflowy_editor: git: url: https://github.com/AppFlowy-IO/appflowy-editor.git - ref: "c68e5f6" + ref: "680222f" appflowy_editor_plugins: git: url: https://github.com/AppFlowy-IO/AppFlowy-plugins.git path: "packages/appflowy_editor_plugins" - ref: "8047c21" + ref: "4efcff7" sheet: git: @@ -196,14 +209,53 @@ dependency_overrides: commit: fbab857b1b1d209240a146d32f496379b9f62276 path: flutter_cache_manager + flutter_sticky_header: ^0.7.0 + + reorderable_tabbar: + git: + url: https://github.com/LucasXu0/reorderable_tabbar + ref: 93c4977 + # Don't upgrade file_picker until the issue is fixed + # https://github.com/miguelpruivo/flutter_file_picker/issues/1652 + file_picker: 8.1.4 + + auto_updater: + git: + url: https://github.com/LucasXu0/auto_updater.git + path: packages/auto_updater + ref: 1d81a824f3633f1d0200ba51b78fe0f9ce429458 + + auto_updater_macos: + git: + url: https://github.com/LucasXu0/auto_updater.git + path: packages/auto_updater_macos + ref: 1d81a824f3633f1d0200ba51b78fe0f9ce429458 + + auto_updater_platform_interface: + git: + url: https://github.com/LucasXu0/auto_updater.git + path: packages/auto_updater_platform_interface + ref: 1d81a824f3633f1d0200ba51b78fe0f9ce429458 + + unsplash_client: + git: + url: https://github.com/LucasXu0/unsplash_client.git + ref: a8411fc + + # auto_updater: + # path: /Users/lucas.xu/Desktop/auto_updater/packages/auto_updater + + # auto_updater_macos: + # path: /Users/lucas.xu/Desktop/auto_updater/packages/auto_updater_macos + + # auto_updater_platform_interface: + # path: /Users/lucas.xu/Desktop/auto_updater/packages/auto_updater_platform_interface + flutter: generate: true uses-material-design: true fonts: - - family: FlowyIconData - fonts: - - asset: assets/fonts/FlowyIconData.ttf - family: Poppins fonts: - asset: assets/google_fonts/Poppins/Poppins-ExtraLight.ttf @@ -229,6 +281,9 @@ flutter: - asset: assets/google_fonts/Roboto_Mono/RobotoMono-Regular.ttf - asset: assets/google_fonts/Roboto_Mono/RobotoMono-Italic.ttf style: italic + # White-label font configuration will be added here + # BEGIN: WHITE_LABEL_FONT + # END: WHITE_LABEL_FONT # To add assets to your application, add an assets section, like this: assets: @@ -237,6 +292,7 @@ flutter: - assets/images/built_in_cover_images/ - assets/flowy_icons/ - assets/flowy_icons/16x/ + - assets/flowy_icons/20x/ - assets/flowy_icons/24x/ - assets/flowy_icons/32x/ - assets/flowy_icons/40x/ @@ -244,6 +300,7 @@ flutter: - assets/images/login/ - assets/translations/ - assets/icons/icons.json + - assets/fonts/ # The following assets will be excluded in release. # BEGIN: EXCLUDE_IN_RELEASE diff --git a/frontend/appflowy_flutter/test/bloc_test/ai_writer_test/ai_writer_bloc_test.dart b/frontend/appflowy_flutter/test/bloc_test/ai_writer_test/ai_writer_bloc_test.dart new file mode 100644 index 0000000000..46b8118087 --- /dev/null +++ b/frontend/appflowy_flutter/test/bloc_test/ai_writer_test/ai_writer_bloc_test.dart @@ -0,0 +1,424 @@ +import 'dart:async'; + +import 'package:appflowy/ai/ai.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_cubit.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_entities.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; +import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:bloc_test/bloc_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +import '../../util.dart'; + +const _aiResponse = 'UPDATED:'; + +class _MockCompletionStream extends Mock implements CompletionStream {} + +class _MockAIRepository extends Mock implements AppFlowyAIService { + @override + Future<(String, CompletionStream)?> streamCompletion({ + String? objectId, + required String text, + PredefinedFormat? format, + List sourceIds = const [], + List history = const [], + required CompletionTypePB completionType, + required Future Function() onStart, + required Future Function(String text) processMessage, + required Future Function(String text) processAssistMessage, + required Future Function() onEnd, + required void Function(AIError error) onError, + required void Function(LocalAIStreamingState state) + onLocalAIStreamingStateChange, + }) async { + final stream = _MockCompletionStream(); + unawaited( + Future(() async { + await onStart(); + final lines = text.split('\n'); + for (final line in lines) { + if (line.isNotEmpty) { + await processMessage('$_aiResponse $line\n\n'); + } + } + await onEnd(); + }), + ); + return ('mock_id', stream); + } +} + +class _MockAIRepositoryLess extends Mock implements AppFlowyAIService { + @override + Future<(String, CompletionStream)?> streamCompletion({ + String? objectId, + required String text, + PredefinedFormat? format, + List sourceIds = const [], + List history = const [], + required CompletionTypePB completionType, + required Future Function() onStart, + required Future Function(String text) processMessage, + required Future Function(String text) processAssistMessage, + required Future Function() onEnd, + required void Function(AIError error) onError, + required void Function(LocalAIStreamingState state) + onLocalAIStreamingStateChange, + }) async { + final stream = _MockCompletionStream(); + unawaited( + Future(() async { + await onStart(); + // only return 1 line. + await processMessage('Hello World'); + await onEnd(); + }), + ); + return ('mock_id', stream); + } +} + +class _MockAIRepositoryMore extends Mock implements AppFlowyAIService { + @override + Future<(String, CompletionStream)?> streamCompletion({ + String? objectId, + required String text, + PredefinedFormat? format, + List sourceIds = const [], + List history = const [], + required CompletionTypePB completionType, + required Future Function() onStart, + required Future Function(String text) processMessage, + required Future Function(String text) processAssistMessage, + required Future Function() onEnd, + required void Function(AIError error) onError, + required void Function(LocalAIStreamingState state) + onLocalAIStreamingStateChange, + }) async { + final stream = _MockCompletionStream(); + unawaited( + Future(() async { + await onStart(); + // return 10 lines + for (var i = 0; i < 10; i++) { + await processMessage('Hello World\n\n'); + } + await onEnd(); + }), + ); + return ('mock_id', stream); + } +} + +class _MockErrorRepository extends Mock implements AppFlowyAIService { + @override + Future<(String, CompletionStream)?> streamCompletion({ + String? objectId, + required String text, + PredefinedFormat? format, + List sourceIds = const [], + List history = const [], + required CompletionTypePB completionType, + required Future Function() onStart, + required Future Function(String text) processMessage, + required Future Function(String text) processAssistMessage, + required Future Function() onEnd, + required void Function(AIError error) onError, + required void Function(LocalAIStreamingState state) + onLocalAIStreamingStateChange, + }) async { + final stream = _MockCompletionStream(); + unawaited( + Future(() async { + await onStart(); + onError( + const AIError( + message: 'Error', + code: AIErrorCode.aiResponseLimitExceeded, + ), + ); + }), + ); + return ('mock_id', stream); + } +} + +void main() { + group('AIWriterCubit:', () { + const text1 = '1. Select text to style using the toolbar menu.'; + const text2 = '2. Discover more styling options in Aa.'; + const text3 = + '3. AppFlowy empowers you to beautifully and effortlessly style your content.'; + + setUp(() { + TestWidgetsFlutterBinding.ensureInitialized(); + }); + + blocTest( + 'send request before the bloc is initialized', + build: () { + final document = Document( + root: pageNode( + children: [ + paragraphNode(text: text1), + paragraphNode(text: text2), + paragraphNode(text: text3), + ], + ), + ); + final selection = Selection( + start: Position(path: [0]), + end: Position(path: [2], offset: text3.length), + ); + final editorState = EditorState(document: document) + ..selection = selection; + return AiWriterCubit( + documentId: '', + editorState: editorState, + aiService: _MockAIRepository(), + ); + }, + act: (bloc) => bloc.register( + aiWriterNode( + command: AiWriterCommand.explain, + selection: Selection( + start: Position(path: [0]), + end: Position(path: [2], offset: text3.length), + ), + ), + ), + wait: Duration(seconds: 1), + expect: () => [ + isA() + .having((s) => s.markdownText, 'result', isEmpty), + isA() + .having((s) => s.markdownText, 'result', isNotEmpty) + .having((s) => s.markdownText, 'result', contains('UPDATED:')), + isA() + .having((s) => s.markdownText, 'result', isNotEmpty) + .having((s) => s.markdownText, 'result', contains('UPDATED:')), + isA() + .having((s) => s.markdownText, 'result', isNotEmpty) + .having((s) => s.markdownText, 'result', contains('UPDATED:')), + isA() + .having((s) => s.markdownText, 'result', isNotEmpty) + .having((s) => s.markdownText, 'result', contains('UPDATED:')), + ], + ); + + blocTest( + 'exceed the ai response limit', + build: () { + const text1 = '1. Select text to style using the toolbar menu.'; + const text2 = '2. Discover more styling options in Aa.'; + const text3 = + '3. AppFlowy empowers you to beautifully and effortlessly style your content.'; + final document = Document( + root: pageNode( + children: [ + paragraphNode(text: text1), + paragraphNode(text: text2), + paragraphNode(text: text3), + ], + ), + ); + final selection = Selection( + start: Position(path: [0]), + end: Position(path: [2], offset: text3.length), + ); + final editorState = EditorState(document: document) + ..selection = selection; + return AiWriterCubit( + documentId: '', + editorState: editorState, + aiService: _MockErrorRepository(), + ); + }, + act: (bloc) => bloc.register( + aiWriterNode( + command: AiWriterCommand.explain, + selection: Selection( + start: Position(path: [0]), + end: Position(path: [2], offset: text3.length), + ), + ), + ), + wait: Duration(seconds: 1), + expect: () => [ + isA() + .having((s) => s.markdownText, 'result', isEmpty), + isA().having( + (s) => s.error.code, + 'error code', + AIErrorCode.aiResponseLimitExceeded, + ), + ], + ); + + test('improve writing - the result contains the same number of paragraphs', + () async { + final selection = Selection( + start: Position(path: [0]), + end: Position(path: [2], offset: text3.length), + ); + final document = Document( + root: pageNode( + children: [ + paragraphNode(text: text1), + paragraphNode(text: text2), + paragraphNode(text: text3), + aiWriterNode( + command: AiWriterCommand.improveWriting, + selection: selection, + ), + ], + ), + ); + final editorState = EditorState(document: document) + ..selection = selection; + final aiNode = editorState.getNodeAtPath([3])!; + final bloc = AiWriterCubit( + documentId: '', + editorState: editorState, + aiService: _MockAIRepository(), + ); + bloc.register(aiNode); + await blocResponseFuture(); + bloc.runResponseAction(SuggestionAction.accept); + await blocResponseFuture(); + expect( + editorState.document.root.children.length, + 3, + ); + expect( + editorState.getNodeAtPath([0])!.delta!.toPlainText(), + '$_aiResponse $text1', + ); + expect( + editorState.getNodeAtPath([1])!.delta!.toPlainText(), + '$_aiResponse $text2', + ); + expect( + editorState.getNodeAtPath([2])!.delta!.toPlainText(), + '$_aiResponse $text3', + ); + }); + + test('improve writing - discard', () async { + final selection = Selection( + start: Position(path: [0]), + end: Position(path: [2], offset: text3.length), + ); + final document = Document( + root: pageNode( + children: [ + paragraphNode(text: text1), + paragraphNode(text: text2), + paragraphNode(text: text3), + aiWriterNode( + command: AiWriterCommand.improveWriting, + selection: selection, + ), + ], + ), + ); + final editorState = EditorState(document: document) + ..selection = selection; + final aiNode = editorState.getNodeAtPath([3])!; + final bloc = AiWriterCubit( + documentId: '', + editorState: editorState, + aiService: _MockAIRepository(), + ); + bloc.register(aiNode); + await blocResponseFuture(); + bloc.runResponseAction(SuggestionAction.discard); + await blocResponseFuture(); + expect( + editorState.document.root.children.length, + 3, + ); + expect(editorState.getNodeAtPath([0])!.delta!.toPlainText(), text1); + expect(editorState.getNodeAtPath([1])!.delta!.toPlainText(), text2); + expect(editorState.getNodeAtPath([2])!.delta!.toPlainText(), text3); + }); + + test('improve writing - the result less than the original text', () async { + final selection = Selection( + start: Position(path: [0]), + end: Position(path: [2], offset: text3.length), + ); + final document = Document( + root: pageNode( + children: [ + paragraphNode(text: text1), + paragraphNode(text: text2), + paragraphNode(text: text3), + aiWriterNode( + command: AiWriterCommand.improveWriting, + selection: selection, + ), + ], + ), + ); + final editorState = EditorState(document: document) + ..selection = selection; + final aiNode = editorState.getNodeAtPath([3])!; + final bloc = AiWriterCubit( + documentId: '', + editorState: editorState, + aiService: _MockAIRepositoryLess(), + ); + bloc.register(aiNode); + await blocResponseFuture(); + bloc.runResponseAction(SuggestionAction.accept); + await blocResponseFuture(); + expect(editorState.document.root.children.length, 2); + expect( + editorState.getNodeAtPath([0])!.delta!.toPlainText(), + 'Hello World', + ); + }); + + test('improve writing - the result more than the original text', () async { + final selection = Selection( + start: Position(path: [0]), + end: Position(path: [2], offset: text3.length), + ); + final document = Document( + root: pageNode( + children: [ + paragraphNode(text: text1), + paragraphNode(text: text2), + paragraphNode(text: text3), + aiWriterNode( + command: AiWriterCommand.improveWriting, + selection: selection, + ), + ], + ), + ); + final editorState = EditorState(document: document) + ..selection = selection; + final aiNode = editorState.getNodeAtPath([3])!; + final bloc = AiWriterCubit( + documentId: '', + editorState: editorState, + aiService: _MockAIRepositoryMore(), + ); + bloc.register(aiNode); + await blocResponseFuture(); + bloc.runResponseAction(SuggestionAction.accept); + await blocResponseFuture(); + expect(editorState.document.root.children.length, 10); + for (var i = 0; i < 10; i++) { + expect( + editorState.getNodeAtPath([i])!.delta!.toPlainText(), + 'Hello World', + ); + } + }); + }); +} diff --git a/frontend/appflowy_flutter/test/bloc_test/ask_ai_test/ask_ai_action_bloc_test.dart b/frontend/appflowy_flutter/test/bloc_test/ask_ai_test/ask_ai_action_bloc_test.dart deleted file mode 100644 index 8c6f798656..0000000000 --- a/frontend/appflowy_flutter/test/bloc_test/ask_ai_test/ask_ai_action_bloc_test.dart +++ /dev/null @@ -1,322 +0,0 @@ -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/ai_client.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/error.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/ask_ai_action.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/ask_ai_action_bloc.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; -import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; -import 'package:bloc_test/bloc_test.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mocktail/mocktail.dart'; - -import '../../util.dart'; - -const _aiResponse = 'UPDATED:'; - -class _MockAIRepository extends Mock implements AIRepository { - @override - Future streamCompletion({ - required String text, - required CompletionTypePB completionType, - required Future Function() onStart, - required Future Function(String text) onProcess, - required Future Function() onEnd, - required void Function(AIError error) onError, - }) async { - await onStart(); - final lines = text.split('\n\n'); - for (var i = 0; i < lines.length; i++) { - await onProcess('$_aiResponse ${lines[i]}\n\n'); - } - await onEnd(); - } -} - -class _MockAIRepositoryLess extends Mock implements AIRepository { - @override - Future streamCompletion({ - required String text, - required CompletionTypePB completionType, - required Future Function() onStart, - required Future Function(String text) onProcess, - required Future Function() onEnd, - required void Function(AIError error) onError, - }) async { - await onStart(); - // only return 1 line. - await onProcess('Hello World'); - await onEnd(); - } -} - -class _MockAIRepositoryMore extends Mock implements AIRepository { - @override - Future streamCompletion({ - required String text, - required CompletionTypePB completionType, - required Future Function() onStart, - required Future Function(String text) onProcess, - required Future Function() onEnd, - required void Function(AIError error) onError, - }) async { - await onStart(); - // return 10 lines - for (var i = 0; i < 10; i++) { - await onProcess('Hello World\n\n'); - } - await onEnd(); - } -} - -class _MockErrorRepository extends Mock implements AIRepository { - @override - Future streamCompletion({ - required String text, - required CompletionTypePB completionType, - required Future Function() onStart, - required Future Function(String text) onProcess, - required Future Function() onEnd, - required void Function(AIError error) onError, - }) async { - await onStart(); - onError( - const AIError( - message: 'Error', - code: AIErrorCode.aiResponseLimitExceeded, - ), - ); - } -} - -void main() { - group('AskAIActionBloc: ', () { - const text1 = '1. Select text to style using the toolbar menu.'; - const text2 = '2. Discover more styling options in Aa.'; - const text3 = - '3. AppFlowy empowers you to beautifully and effortlessly style your content.'; - - blocTest( - 'send request before the bloc is initialized', - build: () { - final document = Document( - root: pageNode( - children: [ - paragraphNode(text: text1), - paragraphNode(text: text2), - paragraphNode(text: text3), - ], - ), - ); - final editorState = EditorState(document: document); - editorState.selection = Selection( - start: Position(path: [0]), - end: Position(path: [2], offset: text3.length), - ); - - final node = askAINode( - action: AskAIAction.makeItLonger, - content: [text1, text2, text3].join('\n'), - ); - return AskAIActionBloc( - node: node, - editorState: editorState, - action: AskAIAction.makeItLonger, - enableLogging: false, - ); - }, - act: (bloc) { - bloc.add(AskAIEvent.initial(Future.value(_MockAIRepository()))); - bloc.add(const AskAIEvent.rewrite()); - }, - expect: () => [ - isA() - .having((s) => s.loading, 'loading', true) - .having((s) => s.result, 'result', isEmpty), - isA() - .having((s) => s.loading, 'loading', false) - .having((s) => s.result, 'result', isNotEmpty) - .having((s) => s.result, 'result', contains('UPDATED:')), - isA().having((s) => s.loading, 'loading', false), - ], - ); - - blocTest( - 'exceed the ai response limit', - build: () { - const text1 = '1. Select text to style using the toolbar menu.'; - const text2 = '2. Discover more styling options in Aa.'; - const text3 = - '3. AppFlowy empowers you to beautifully and effortlessly style your content.'; - final document = Document( - root: pageNode( - children: [ - paragraphNode(text: text1), - paragraphNode(text: text2), - paragraphNode(text: text3), - ], - ), - ); - final editorState = EditorState(document: document); - editorState.selection = Selection( - start: Position(path: [0]), - end: Position(path: [2], offset: text3.length), - ); - - final node = askAINode( - action: AskAIAction.makeItLonger, - content: [text1, text2, text3].join('\n'), - ); - return AskAIActionBloc( - node: node, - editorState: editorState, - action: AskAIAction.makeItLonger, - enableLogging: false, - ); - }, - act: (bloc) { - bloc.add(AskAIEvent.initial(Future.value(_MockErrorRepository()))); - bloc.add(const AskAIEvent.rewrite()); - }, - expect: () => [ - isA() - .having((s) => s.loading, 'loading', true) - .having((s) => s.result, 'result', isEmpty), - isA() - .having((s) => s.requestError, 'requestError', isNotNull) - .having( - (s) => s.requestError?.code, - 'requestError.code', - AIErrorCode.aiResponseLimitExceeded, - ), - ], - ); - - test('summary - the result contains the same number of paragraphs', - () async { - final document = Document( - root: pageNode( - children: [ - paragraphNode(text: text1), - paragraphNode(text: text2), - paragraphNode(text: text3), - ], - ), - ); - final editorState = EditorState(document: document); - editorState.selection = Selection( - start: Position(path: [0]), - end: Position(path: [2], offset: text3.length), - ); - - final node = askAINode( - action: AskAIAction.makeItLonger, - content: [text1, text2, text3].join('\n\n'), - ); - final bloc = AskAIActionBloc( - node: node, - editorState: editorState, - action: AskAIAction.summarize, - enableLogging: false, - ); - bloc.add(AskAIEvent.initial(Future.value(_MockAIRepository()))); - await blocResponseFuture(); - bloc.add(const AskAIEvent.started()); - await blocResponseFuture(); - bloc.add(const AskAIEvent.replace()); - await blocResponseFuture(); - expect(editorState.document.root.children.length, 3); - expect( - editorState.getNodeAtPath([0])!.delta!.toPlainText(), - '$_aiResponse $text1', - ); - expect( - editorState.getNodeAtPath([1])!.delta!.toPlainText(), - '$_aiResponse $text2', - ); - expect( - editorState.getNodeAtPath([2])!.delta!.toPlainText(), - '$_aiResponse $text3', - ); - }); - - test('summary - the result less than the original text', () async { - final document = Document( - root: pageNode( - children: [ - paragraphNode(text: text1), - paragraphNode(text: text2), - paragraphNode(text: text3), - ], - ), - ); - final editorState = EditorState(document: document); - editorState.selection = Selection( - start: Position(path: [0]), - end: Position(path: [2], offset: text3.length), - ); - - final node = askAINode( - action: AskAIAction.makeItLonger, - content: [text1, text2, text3].join('\n'), - ); - final bloc = AskAIActionBloc( - node: node, - editorState: editorState, - action: AskAIAction.summarize, - enableLogging: false, - ); - bloc.add(AskAIEvent.initial(Future.value(_MockAIRepositoryLess()))); - await blocResponseFuture(); - bloc.add(const AskAIEvent.started()); - await blocResponseFuture(); - bloc.add(const AskAIEvent.replace()); - await blocResponseFuture(); - expect(editorState.document.root.children.length, 1); - expect( - editorState.getNodeAtPath([0])!.delta!.toPlainText(), - 'Hello World', - ); - }); - - test('summary - the result more than the original text', () async { - final document = Document( - root: pageNode( - children: [ - paragraphNode(text: text1), - paragraphNode(text: text2), - paragraphNode(text: text3), - ], - ), - ); - final editorState = EditorState(document: document); - editorState.selection = Selection( - start: Position(path: [0]), - end: Position(path: [2], offset: text3.length), - ); - - final node = askAINode( - action: AskAIAction.makeItLonger, - content: [text1, text2, text3].join('\n'), - ); - final bloc = AskAIActionBloc( - node: node, - editorState: editorState, - action: AskAIAction.summarize, - enableLogging: false, - ); - bloc.add(AskAIEvent.initial(Future.value(_MockAIRepositoryMore()))); - await blocResponseFuture(); - bloc.add(const AskAIEvent.started()); - await blocResponseFuture(); - bloc.add(const AskAIEvent.replace()); - await blocResponseFuture(); - expect(editorState.document.root.children.length, 10); - for (var i = 0; i < 10; i++) { - expect( - editorState.getNodeAtPath([i])!.delta!.toPlainText(), - 'Hello World', - ); - } - }); - }); -} diff --git a/frontend/appflowy_flutter/test/bloc_test/board_test/group_by_date_test.dart b/frontend/appflowy_flutter/test/bloc_test/board_test/group_by_date_test.dart index 92998f9cc0..51bd537159 100644 --- a/frontend/appflowy_flutter/test/bloc_test/board_test/group_by_date_test.dart +++ b/frontend/appflowy_flutter/test/bloc_test/board_test/group_by_date_test.dart @@ -109,7 +109,8 @@ void main() { assert(boardBloc.groupControllers.values.length == 2); assert( - boardBloc.boardController.groupDatas.last.headerData.groupName == "2024", + boardBloc.boardController.groupDatas.last.headerData.groupName == + DateTime.now().year.toString(), ); }); } diff --git a/frontend/appflowy_flutter/test/bloc_test/chat_test/util.dart b/frontend/appflowy_flutter/test/bloc_test/chat_test/util.dart index 29d98416b5..ece0c5e027 100644 --- a/frontend/appflowy_flutter/test/bloc_test/chat_test/util.dart +++ b/frontend/appflowy_flutter/test/bloc_test/chat_test/util.dart @@ -34,11 +34,3 @@ class AppFlowyChatTest { }); } } - -Future boardResponseFuture() { - return Future.delayed(boardResponseDuration()); -} - -Duration boardResponseDuration({int milliseconds = 200}) { - return Duration(milliseconds: milliseconds); -} diff --git a/frontend/appflowy_flutter/test/bloc_test/home_test/view_bloc_test.dart b/frontend/appflowy_flutter/test/bloc_test/home_test/view_bloc_test.dart index d6d0351414..41865b7dd7 100644 --- a/frontend/appflowy_flutter/test/bloc_test/home_test/view_bloc_test.dart +++ b/frontend/appflowy_flutter/test/bloc_test/home_test/view_bloc_test.dart @@ -182,14 +182,14 @@ void main() { await blocResponseFuture(); assert(viewBloc.state.lastCreatedView!.name == gird); - var workspaceSetting = + var workspaceLatest = await FolderEventGetCurrentWorkspaceSetting().send().then( (result) => result.fold( (l) => l, (r) => throw Exception(), ), ); - workspaceSetting.latestView.id == viewBloc.state.lastCreatedView!.id; + workspaceLatest.latestView.id == viewBloc.state.lastCreatedView!.id; // ignore: unused_local_variable final documentBloc = DocumentBloc(documentId: document.id) @@ -198,14 +198,13 @@ void main() { ); await blocResponseFuture(); - workspaceSetting = - await FolderEventGetCurrentWorkspaceSetting().send().then( - (result) => result.fold( - (l) => l, - (r) => throw Exception(), - ), - ); - workspaceSetting.latestView.id == document.id; + workspaceLatest = await FolderEventGetCurrentWorkspaceSetting().send().then( + (result) => result.fold( + (l) => l, + (r) => throw Exception(), + ), + ); + workspaceLatest.latestView.id == document.id; }); test('create views', () async { diff --git a/frontend/appflowy_flutter/test/unit_test/document/document_diff/document_diff_test.dart b/frontend/appflowy_flutter/test/unit_test/document/document_diff/document_diff_test.dart new file mode 100644 index 0000000000..7124eb93fc --- /dev/null +++ b/frontend/appflowy_flutter/test/unit_test/document/document_diff/document_diff_test.dart @@ -0,0 +1,403 @@ +import 'package:appflowy/plugins/document/application/document_diff.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('document diff:', () { + setUpAll(() { + Log.shared.disableLog = true; + }); + + tearDownAll(() { + Log.shared.disableLog = false; + }); + + const diff = DocumentDiff(); + + Node createNodeWithId({required String id, required String text}) { + return Node( + id: id, + type: ParagraphBlockKeys.type, + attributes: { + ParagraphBlockKeys.delta: (Delta()..insert(text)).toJson(), + }, + ); + } + + Future applyOperationAndVerifyDocument( + Document before, + Document after, + List operations, + ) async { + final expected = after.toJson(); + final editorState = EditorState(document: before); + final transaction = editorState.transaction; + for (final operation in operations) { + transaction.add(operation); + } + await editorState.apply(transaction); + expect(editorState.document.toJson(), expected); + } + + test('no diff when the document is the same', () async { + // create two nodes with the same id and texts + final node1 = createNodeWithId(id: '1', text: 'Hello AppFlowy'); + final node2 = createNodeWithId(id: '1', text: 'Hello AppFlowy'); + + final previous = Document.blank()..insert([0], [node1]); + final next = Document.blank()..insert([0], [node2]); + final operations = diff.diffDocument(previous, next); + + expect(operations, isEmpty); + + await applyOperationAndVerifyDocument(previous, next, operations); + }); + + test('update text diff with the same id', () async { + final node1 = createNodeWithId(id: '1', text: 'Hello AppFlowy'); + final node2 = createNodeWithId(id: '1', text: 'Hello AppFlowy 2'); + + final previous = Document.blank()..insert([0], [node1]); + final next = Document.blank()..insert([0], [node2]); + final operations = diff.diffDocument(previous, next); + + expect(operations.length, 1); + expect(operations[0], isA()); + + await applyOperationAndVerifyDocument(previous, next, operations); + }); + + test('delete and insert text diff with different id', () async { + final node1 = createNodeWithId(id: '1', text: 'Hello AppFlowy'); + final node2 = createNodeWithId(id: '2', text: 'Hello AppFlowy 2'); + + final previous = Document.blank()..insert([0], [node1]); + final next = Document.blank()..insert([0], [node2]); + + final operations = diff.diffDocument(previous, next); + + expect(operations.length, 2); + expect(operations[0], isA()); + expect(operations[1], isA()); + + await applyOperationAndVerifyDocument(previous, next, operations); + }); + + test('insert single text diff', () async { + final node1 = createNodeWithId( + id: '1', + text: 'Hello AppFlowy - First line', + ); + final node21 = createNodeWithId( + id: '1', + text: 'Hello AppFlowy - First line', + ); + final node22 = createNodeWithId( + id: '2', + text: 'Hello AppFlowy - Second line', + ); + + final previous = Document.blank()..insert([0], [node1]); + final next = Document.blank()..insert([0], [node21, node22]); + + final operations = diff.diffDocument(previous, next); + + expect(operations.length, 1); + expect(operations[0], isA()); + + await applyOperationAndVerifyDocument(previous, next, operations); + }); + + test('delete single text diff', () async { + final node11 = createNodeWithId( + id: '1', + text: 'Hello AppFlowy - First line', + ); + final node12 = createNodeWithId( + id: '2', + text: 'Hello AppFlowy - Second line', + ); + final node21 = createNodeWithId( + id: '1', + text: 'Hello AppFlowy - First line', + ); + + final previous = Document.blank()..insert([0], [node11, node12]); + final next = Document.blank()..insert([0], [node21]); + + final operations = diff.diffDocument(previous, next); + + expect(operations.length, 1); + expect(operations[0], isA()); + + await applyOperationAndVerifyDocument(previous, next, operations); + }); + + test('insert multiple texts diff', () async { + final node11 = createNodeWithId( + id: '1', + text: 'Hello AppFlowy - First line', + ); + final node15 = createNodeWithId( + id: '5', + text: 'Hello AppFlowy - Fifth line', + ); + final node21 = createNodeWithId( + id: '1', + text: 'Hello AppFlowy - First line', + ); + final node22 = createNodeWithId( + id: '2', + text: 'Hello AppFlowy - Second line', + ); + final node23 = createNodeWithId( + id: '3', + text: 'Hello AppFlowy - Third line', + ); + final node24 = createNodeWithId( + id: '4', + text: 'Hello AppFlowy - Fourth line', + ); + final node25 = createNodeWithId( + id: '5', + text: 'Hello AppFlowy - Fifth line', + ); + + final previous = Document.blank() + ..insert( + [0], + [ + node11, + node15, + ], + ); + final next = Document.blank() + ..insert( + [0], + [ + node21, + node22, + node23, + node24, + node25, + ], + ); + + final operations = diff.diffDocument(previous, next); + + expect(operations.length, 1); + + final op = operations[0] as InsertOperation; + expect(op.path, [1]); + expect(op.nodes, [node22, node23, node24]); + + await applyOperationAndVerifyDocument(previous, next, operations); + }); + + test('delete multiple texts diff', () async { + final node11 = createNodeWithId( + id: '1', + text: 'Hello AppFlowy - First line', + ); + final node12 = createNodeWithId( + id: '2', + text: 'Hello AppFlowy - Second line', + ); + final node13 = createNodeWithId( + id: '3', + text: 'Hello AppFlowy - Third line', + ); + final node14 = createNodeWithId( + id: '4', + text: 'Hello AppFlowy - Fourth line', + ); + final node15 = createNodeWithId( + id: '5', + text: 'Hello AppFlowy - Fifth line', + ); + + final node21 = createNodeWithId( + id: '1', + text: 'Hello AppFlowy - First line', + ); + final node25 = createNodeWithId( + id: '5', + text: 'Hello AppFlowy - Fifth line', + ); + + final previous = Document.blank() + ..insert( + [0], + [ + node11, + node12, + node13, + node14, + node15, + ], + ); + final next = Document.blank() + ..insert( + [0], + [ + node21, + node25, + ], + ); + + final operations = diff.diffDocument(previous, next); + + expect(operations.length, 1); + + final op = operations[0] as DeleteOperation; + expect(op.path, [1]); + expect(op.nodes, [node12, node13, node14]); + + await applyOperationAndVerifyDocument(previous, next, operations); + }); + + test('multiple delete and update diff', () async { + final node11 = createNodeWithId( + id: '1', + text: 'Hello AppFlowy - First line', + ); + final node12 = createNodeWithId( + id: '2', + text: 'Hello AppFlowy - Second line', + ); + final node13 = createNodeWithId( + id: '3', + text: 'Hello AppFlowy - Third line', + ); + final node14 = createNodeWithId( + id: '4', + text: 'Hello AppFlowy - Fourth line', + ); + final node15 = createNodeWithId( + id: '5', + text: 'Hello AppFlowy - Fifth line', + ); + + final node21 = createNodeWithId( + id: '1', + text: 'Hello AppFlowy - First line', + ); + final node22 = createNodeWithId( + id: '2', + text: '', + ); + final node25 = createNodeWithId( + id: '5', + text: 'Hello AppFlowy - Fifth line', + ); + + final previous = Document.blank() + ..insert( + [0], + [ + node11, + node12, + node13, + node14, + node15, + ], + ); + final next = Document.blank() + ..insert( + [0], + [ + node21, + node22, + node25, + ], + ); + + final operations = diff.diffDocument(previous, next); + + expect(operations.length, 2); + final op1 = operations[0] as UpdateOperation; + expect(op1.path, [1]); + expect(op1.attributes, node22.attributes); + + final op2 = operations[1] as DeleteOperation; + expect(op2.path, [2]); + expect(op2.nodes, [node13, node14]); + + await applyOperationAndVerifyDocument(previous, next, operations); + }); + + test('multiple insert and update diff', () async { + final node11 = createNodeWithId( + id: '1', + text: 'Hello AppFlowy - First line', + ); + final node12 = createNodeWithId( + id: '2', + text: 'Hello AppFlowy - Second line', + ); + final node13 = createNodeWithId( + id: '3', + text: 'Hello AppFlowy - Third line', + ); + final node21 = createNodeWithId( + id: '1', + text: 'Hello AppFlowy - First line', + ); + final node22 = createNodeWithId( + id: '2', + text: 'Hello AppFlowy - Second line - Updated', + ); + final node23 = createNodeWithId( + id: '3', + text: 'Hello AppFlowy - Third line - Updated', + ); + final node24 = createNodeWithId( + id: '4', + text: 'Hello AppFlowy - Fourth line - Updated', + ); + final node25 = createNodeWithId( + id: '5', + text: 'Hello AppFlowy - Fifth line - Updated', + ); + + final previous = Document.blank() + ..insert( + [0], + [ + node11, + node12, + node13, + ], + ); + final next = Document.blank() + ..insert( + [0], + [ + node21, + node22, + node23, + node24, + node25, + ], + ); + + final operations = diff.diffDocument(previous, next); + + expect(operations.length, 3); + final op1 = operations[0] as InsertOperation; + expect(op1.path, [3]); + expect(op1.nodes, [node24, node25]); + + final op2 = operations[1] as UpdateOperation; + expect(op2.path, [1]); + expect(op2.attributes, node22.attributes); + + final op3 = operations[2] as UpdateOperation; + expect(op3.path, [2]); + expect(op3.attributes, node23.attributes); + + await applyOperationAndVerifyDocument(previous, next, operations); + }); + }); +} diff --git a/frontend/appflowy_flutter/test/unit_test/document/html/_html_samples.dart b/frontend/appflowy_flutter/test/unit_test/document/html/_html_samples.dart new file mode 100644 index 0000000000..41dea6e01c --- /dev/null +++ b/frontend/appflowy_flutter/test/unit_test/document/html/_html_samples.dart @@ -0,0 +1,541 @@ +// | Month | Savings | +// | -------- | ------- | +// | January | $250 | +// | February | $80 | +// | March | $420 | +const tableFromNotion = ''' + + + + + + + + + + + + + + + + + + + + +
MonthSavings
January\$250
February\$80
March\$420
+'''; + +// | Month | Savings | +// | -------- | ------- | +// | January | $250 | +// | February | $80 | +// | March | $420 | +const tableFromGoogleDocs = ''' + + +
+ + + + + + + + + + + + + + + + + + + + + + + +
+

+ + Month + +

+
+

+ + Savings + +

+
+

+ + January + +

+
+

+ + \$250 + +

+
+

+ + February + +

+
+

+ + \$80 + +

+
+

+ + March + +

+
+

+ + \$420 + +

+
+
+
+'''; + +// | Month | Savings | +// | -------- | ------- | +// | January | $250 | +// | February | $80 | +// | March | $420 | +const tableFromGoogleSheets = ''' + + + + + + + + + + + + + + + + + + + + + + + + + + +
MonthSavings
January\$250
February\$80
March\$420
+
+ +'''; + +// # The Benefits of a Balanced Diet +// A balanced diet is crucial for maintaining overall health and well-being. It provides the necessary nutrients your body needs to function effectively, supports growth and development, and helps prevent chronic diseases. In this guide, we will explore the key benefits of a balanced diet and how it can improve your life. +// --- +// ## Key Components of a Balanced Diet +// A balanced diet consists of various food groups, each providing essential nutrients. The main components include: +// 1. **Carbohydrates** – Provide energy for daily activities. +// 1. **Proteins** – Support growth, muscle repair, and immune function. +// 1. **Fats** – Aid in cell function and energy storage. +// 1. **Vitamins and Minerals** – Essential for immune function, bone health, and overall bodily processes. +// 1. **Fiber** – Promotes healthy digestion and reduces the risk of chronic diseases. +// 1. **Water** – Vital for hydration and proper bodily functions. +// --- +// ## Health Benefits of a Balanced Diet +// Maintaining a balanced diet can have profound effects on your health. Below are some of the most significant benefits: +// --- +// ### 1. **Improved Heart Health** +// A balanced diet rich in fruits, vegetables, and healthy fats helps lower cholesterol levels, reduce inflammation, and maintain a healthy blood pressure. +// ### 2. **Better Weight Management** +// By consuming nutrient-dense foods and avoiding overeating, you can achieve and maintain a healthy weight. +// ### 3. **Enhanced Mental Health** +// Proper nutrition supports brain function, which can improve mood, cognitive performance, and mental well-being. +// ### 4. **Stronger Immune System** +// A diet full of vitamins and minerals strengthens the immune system and helps the body fight off infections. +// --- +// ## Recommended Daily Nutrient Intake +// Below is a table that outlines the recommended daily intake for adults based on the different food groups: +// |Nutrient|Recommended Daily Intake|Example Foods| +// |---|---|---| +// |**Carbohydrates**|45-65% of total calories|Whole grains, fruits, vegetables| +// |**Proteins**|10-35% of total calories|Lean meats, beans, legumes, nuts, dairy| +// |**Fats**|20-35% of total calories|Olive oil, avocado, nuts, fatty fish| +// |**Fiber**|25-30 grams|Whole grains, fruits, vegetables, legumes| +// |**Vitamins & Minerals**|Varies (See below)|Fruits, vegetables, dairy, fortified cereals| +// |**Water**|2-3 liters/day|Water, herbal teas, soups| +// --- +// ## Conclusion +// Incorporating a variety of nutrient-rich foods into your diet is essential for maintaining your health. A balanced diet helps improve your physical and mental well-being, boosts energy levels, and reduces the risk of chronic conditions. By following the guidelines above, you can work toward achieving a healthier and happier life. +const tableFromChatGPT = ''' + +

The Benefits of a Balanced Diet

+

+ A balanced diet is crucial for maintaining overall health and well-being. It provides the necessary nutrients your body needs to function effectively, supports growth and development, and helps prevent chronic diseases. In this guide, + we will explore the key benefits of a balanced diet and how it can improve your life. +

+
+

Key Components of a Balanced Diet

+

A balanced diet consists of various food groups, each providing essential nutrients. The main components include:

+
    +
  1. Carbohydrates – Provide energy for daily activities.
  2. +
  3. Proteins – Support growth, muscle repair, and immune function.
  4. +
  5. Fats – Aid in cell function and energy storage.
  6. +
  7. Vitamins and Minerals – Essential for immune function, bone health, and overall bodily processes.
  8. +
  9. Fiber – Promotes healthy digestion and reduces the risk of chronic diseases.
  10. +
  11. Water – Vital for hydration and proper bodily functions.
  12. +
+
+

Health Benefits of a Balanced Diet

+

Maintaining a balanced diet can have profound effects on your health. Below are some of the most significant benefits:

+
+

1. Improved Heart Health

+

A balanced diet rich in fruits, vegetables, and healthy fats helps lower cholesterol levels, reduce inflammation, and maintain a healthy blood pressure.

+

2. Better Weight Management

+

By consuming nutrient-dense foods and avoiding overeating, you can achieve and maintain a healthy weight.

+

3. Enhanced Mental Health

+

Proper nutrition supports brain function, which can improve mood, cognitive performance, and mental well-being.

+

4. Stronger Immune System

+

A diet full of vitamins and minerals strengthens the immune system and helps the body fight off infections.

+
+

Recommended Daily Nutrient Intake

+

Below is a table that outlines the recommended daily intake for adults based on the different food groups:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NutrientRecommended Daily IntakeExample Foods
Carbohydrates45-65% of total caloriesWhole grains, fruits, vegetables
Proteins10-35% of total caloriesLean meats, beans, legumes, nuts, dairy
Fats20-35% of total caloriesOlive oil, avocado, nuts, fatty fish
Fiber25-30 gramsWhole grains, fruits, vegetables, legumes
Vitamins & MineralsVaries (See below)Fruits, vegetables, dairy, fortified cereals
Water2-3 liters/dayWater, herbal teas, soups
+
+

Conclusion

+

+ Incorporating a variety of nutrient-rich foods into your diet is essential for maintaining your health. A balanced diet helps improve your physical and mental well-being, boosts energy levels, and reduces the risk of chronic conditions. + By following the guidelines above, you can work toward achieving a healthier and happier life. +

+'''; + +// | Month | Savings | +// | -------- | ------- | +// | January | $250 | +// | February | $80 | +// | March | $420 | +const tableFromAppleNotes = ''' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

Month

+
+

Savings

+
+

January

+
+

\$250

+
+

February

+
+

\$80

+
+

March

+
+

\$420

+
+ + +'''; diff --git a/frontend/appflowy_flutter/test/unit_test/document/html/paste_from_html_test.dart b/frontend/appflowy_flutter/test/unit_test/document/html/paste_from_html_test.dart new file mode 100644 index 0000000000..a36474ef3f --- /dev/null +++ b/frontend/appflowy_flutter/test/unit_test/document/html/paste_from_html_test.dart @@ -0,0 +1,69 @@ +import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/paste_from_html.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '_html_samples.dart'; + +void main() { + group('paste from html:', () { + void checkTable(String html) { + final nodes = EditorState.blank().convertHtmlToNodes(html); + expect(nodes.length, 1); + final table = nodes.first; + expect(table.type, SimpleTableBlockKeys.type); + expect(table.getCellText(0, 0), 'Month'); + expect(table.getCellText(0, 1), 'Savings'); + expect(table.getCellText(1, 0), 'January'); + expect(table.getCellText(1, 1), '\$250'); + expect(table.getCellText(2, 0), 'February'); + expect(table.getCellText(2, 1), '\$80'); + expect(table.getCellText(3, 0), 'March'); + expect(table.getCellText(3, 1), '\$420'); + } + + test('sample 1 - paste table from Notion', () { + checkTable(tableFromNotion); + }); + + test('sample 2 - paste table from Google Docs', () { + checkTable(tableFromGoogleDocs); + }); + + test('sample 3 - paste table from Google Sheets', () { + checkTable(tableFromGoogleSheets); + }); + + test('sample 4 - paste table from ChatGPT', () { + final nodes = EditorState.blank().convertHtmlToNodes(tableFromChatGPT); + final table = + nodes.where((node) => node.type == SimpleTableBlockKeys.type).first; + + expect(table.columnLength, 3); + expect(table.rowLength, 7); + + final dividers = + nodes.where((node) => node.type == DividerBlockKeys.type); + expect(dividers.length, 5); + }); + + test('sample 5 - paste table from Apple Notes', () { + checkTable(tableFromAppleNotes); + }); + }); +} + +extension on Node { + String getCellText( + int row, + int column, { + int index = 0, + }) { + return children[row] + .children[column] + .children[index] + .delta + ?.toPlainText() ?? + ''; + } +} diff --git a/frontend/appflowy_flutter/test/unit_test/document/shortcuts/format_shortcut_test.dart b/frontend/appflowy_flutter/test/unit_test/document/shortcuts/format_shortcut_test.dart new file mode 100644 index 0000000000..21011df540 --- /dev/null +++ b/frontend/appflowy_flutter/test/unit_test/document/shortcuts/format_shortcut_test.dart @@ -0,0 +1,103 @@ +import 'package:appflowy/plugins/document/presentation/editor_plugins/base/format_arrow_character.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('format shortcut:', () { + setUpAll(() { + Log.shared.disableLog = true; + }); + + tearDownAll(() { + Log.shared.disableLog = false; + }); + + test('turn = + > into ⇒', () async { + final document = Document.blank() + ..insert([ + 0, + ], [ + paragraphNode(text: '='), + ]); + + final editorState = EditorState(document: document); + editorState.selection = Selection.collapsed( + Position(path: [0], offset: 1), + ); + + final result = await customFormatGreaterEqual.execute(editorState); + expect(result, true); + + expect(editorState.document.root.children.length, 1); + final node = editorState.document.root.children[0]; + expect(node.delta!.toPlainText(), '⇒'); + + // use undo to revert the change + undoCommand.execute(editorState); + expect(editorState.document.root.children.length, 1); + final nodeAfterUndo = editorState.document.root.children[0]; + expect(nodeAfterUndo.delta!.toPlainText(), '=>'); + + editorState.dispose(); + }); + + test('turn - + > into →', () async { + final document = Document.blank() + ..insert([ + 0, + ], [ + paragraphNode(text: '-'), + ]); + + final editorState = EditorState(document: document); + editorState.selection = Selection.collapsed( + Position(path: [0], offset: 1), + ); + + final result = await customFormatDashGreater.execute(editorState); + expect(result, true); + + expect(editorState.document.root.children.length, 1); + final node = editorState.document.root.children[0]; + expect(node.delta!.toPlainText(), '→'); + + // use undo to revert the change + undoCommand.execute(editorState); + expect(editorState.document.root.children.length, 1); + final nodeAfterUndo = editorState.document.root.children[0]; + expect(nodeAfterUndo.delta!.toPlainText(), '->'); + + editorState.dispose(); + }); + + test('turn -- into —', () async { + final document = Document.blank() + ..insert([ + 0, + ], [ + paragraphNode(text: '-'), + ]); + + final editorState = EditorState(document: document); + editorState.selection = Selection.collapsed( + Position(path: [0], offset: 1), + ); + + final result = await customFormatDoubleHyphenEmDash.execute(editorState); + expect(result, true); + + expect(editorState.document.root.children.length, 1); + final node = editorState.document.root.children[0]; + expect(node.delta!.toPlainText(), '—'); + + // use undo to revert the change + undoCommand.execute(editorState); + expect(editorState.document.root.children.length, 1); + final nodeAfterUndo = editorState.document.root.children[0]; + expect(nodeAfterUndo.delta!.toPlainText(), '--'); + + editorState.dispose(); + }); + }); +} diff --git a/frontend/appflowy_flutter/test/unit_test/document/text_robot/markdown_text_robot_test.dart b/frontend/appflowy_flutter/test/unit_test/document/text_robot/markdown_text_robot_test.dart index ed60e53ae7..8b1b710f4e 100644 --- a/frontend/appflowy_flutter/test/unit_test/document/text_robot/markdown_text_robot_test.dart +++ b/frontend/appflowy_flutter/test/unit_test/document/text_robot/markdown_text_robot_test.dart @@ -29,7 +29,7 @@ void main() { // mock the delay of the text robot await Future.delayed(const Duration(milliseconds: 10)); } - await markdownTextRobot.stop(); + await markdownTextRobot.persist(); expect(editorState); } @@ -43,11 +43,10 @@ void main() { markdownTextRobot.start(); await markdownTextRobot.appendMarkdownText(_sample1); - await markdownTextRobot.stop(); + await markdownTextRobot.persist(); final nodes = editorState.document.root.children; - // 4 from the sample, 1 from the original empty paragraph node - expect(nodes.length, 5); + expect(nodes.length, 4); final n1 = nodes[0]; expect(n1.delta!.toPlainText(), 'The Curious Cat'); @@ -116,7 +115,7 @@ void main() { _liveRefreshSample2, expect: (editorState) { final nodes = editorState.document.root.children; - expect(nodes.length, 5); + expect(nodes.length, 4); final n1 = nodes[0]; expect(n1.type, HeadingBlockKeys.type); @@ -164,7 +163,7 @@ void main() { _liveRefreshSample3, expect: (editorState) { final nodes = editorState.document.root.children; - expect(nodes.length, 6); + expect(nodes.length, 5); final n1 = nodes[0]; expect(n1.type, HeadingBlockKeys.type); @@ -275,7 +274,7 @@ void main() { _liveRefreshSample4, expect: (editorState) { final nodes = editorState.document.root.children; - expect(nodes.length, 3); + expect(nodes.length, 2); final n1 = nodes[0]; expect(n1.type, ParagraphBlockKeys.type); @@ -295,6 +294,578 @@ void main() { ); }); }); + + group('markdown text robot - replace in same line:', () { + final text1 = + '''The introduction of the World Wide Web in the early 1990s marked a turning point. '''; + final text2 = + '''Tim Berners-Lee's invention made the internet accessible to non-technical users, opening the floodgates for mass adoption. '''; + final text3 = + '''Email became widespread, and instant messaging services like ICQ and AOL Instant Messenger gained popularity, allowing for real-time text communication.'''; + + Document buildTestDocument() { + return Document( + root: pageNode( + children: [ + paragraphNode(delta: Delta()..insert(text1 + text2 + text3)), + ], + ), + ); + } + + // 1. create a document with a paragraph node + // 2. use the text robot to replace the selected content in the same line + // 3. check the document + test('the selection is in the middle of the text', () async { + final document = buildTestDocument(); + final editorState = EditorState(document: document); + + editorState.selection = Selection( + start: Position( + path: [0], + offset: text1.length, + ), + end: Position( + path: [0], + offset: text1.length + text2.length, + ), + ); + + final markdownText = + '''Tim Berners-Lee's invention of the **World Wide Web** transformed the internet, making it accessible to _non-technical users_ and opening the floodgates for global mass adoption.'''; + final markdownTextRobot = MarkdownTextRobot( + editorState: editorState, + ); + await markdownTextRobot.replace( + selection: editorState.selection!, + markdownText: markdownText, + ); + + final afterDelta = editorState.document.root.children[0].delta!.toList(); + expect(afterDelta.length, 5); + + final d1 = afterDelta[0] as TextInsert; + expect(d1.text, '${text1}Tim Berners-Lee\'s invention of the '); + expect(d1.attributes, null); + + final d2 = afterDelta[1] as TextInsert; + expect(d2.text, 'World Wide Web'); + expect(d2.attributes, {AppFlowyRichTextKeys.bold: true}); + + final d3 = afterDelta[2] as TextInsert; + expect(d3.text, ' transformed the internet, making it accessible to '); + expect(d3.attributes, null); + + final d4 = afterDelta[3] as TextInsert; + expect(d4.text, 'non-technical users'); + expect(d4.attributes, {AppFlowyRichTextKeys.italic: true}); + + final d5 = afterDelta[4] as TextInsert; + expect( + d5.text, + ' and opening the floodgates for global mass adoption.$text3', + ); + expect(d5.attributes, null); + }); + + test('replace markdown text with selection from start to middle', () async { + final document = buildTestDocument(); + final editorState = EditorState(document: document); + + editorState.selection = Selection( + start: Position( + path: [0], + ), + end: Position( + path: [0], + offset: text1.length, + ), + ); + + final markdownText = + '''The **invention** of the _World Wide Web_ by Tim Berners-Lee transformed how we access information.'''; + final markdownTextRobot = MarkdownTextRobot( + editorState: editorState, + ); + await markdownTextRobot.replace( + selection: editorState.selection!, + markdownText: markdownText, + ); + + final afterDelta = editorState.document.root.children[0].delta!.toList(); + expect(afterDelta.length, 5); + + final d1 = afterDelta[0] as TextInsert; + expect(d1.text, 'The '); + expect(d1.attributes, null); + + final d2 = afterDelta[1] as TextInsert; + expect(d2.text, 'invention'); + expect(d2.attributes, {AppFlowyRichTextKeys.bold: true}); + + final d3 = afterDelta[2] as TextInsert; + expect(d3.text, ' of the '); + expect(d3.attributes, null); + + final d4 = afterDelta[3] as TextInsert; + expect(d4.text, 'World Wide Web'); + expect(d4.attributes, {AppFlowyRichTextKeys.italic: true}); + + final d5 = afterDelta[4] as TextInsert; + expect( + d5.text, + ' by Tim Berners-Lee transformed how we access information.$text2$text3', + ); + expect(d5.attributes, null); + }); + + test('replace markdown text with selection from middle to end', () async { + final document = buildTestDocument(); + final editorState = EditorState(document: document); + + editorState.selection = Selection( + start: Position( + path: [0], + offset: text1.length + text2.length, + ), + end: Position( + path: [0], + offset: text1.length + text2.length + text3.length, + ), + ); + + final markdownText = + '''**Email** became widespread, and instant messaging services like *ICQ* and **AOL Instant Messenger** gained tremendous popularity, allowing for seamless real-time text communication across the globe.'''; + final markdownTextRobot = MarkdownTextRobot( + editorState: editorState, + ); + await markdownTextRobot.replace( + selection: editorState.selection!, + markdownText: markdownText, + ); + + final afterDelta = editorState.document.root.children[0].delta!.toList(); + expect(afterDelta.length, 7); + + final d1 = afterDelta[0] as TextInsert; + expect( + d1.text, + text1 + text2, + ); + expect(d1.attributes, null); + + final d2 = afterDelta[1] as TextInsert; + expect(d2.text, 'Email'); + expect(d2.attributes, {AppFlowyRichTextKeys.bold: true}); + + final d3 = afterDelta[2] as TextInsert; + expect( + d3.text, + ' became widespread, and instant messaging services like ', + ); + expect(d3.attributes, null); + + final d4 = afterDelta[3] as TextInsert; + expect(d4.text, 'ICQ'); + expect(d4.attributes, {AppFlowyRichTextKeys.italic: true}); + + final d5 = afterDelta[4] as TextInsert; + expect(d5.text, ' and '); + expect(d5.attributes, null); + + final d6 = afterDelta[5] as TextInsert; + expect( + d6.text, + 'AOL Instant Messenger', + ); + expect(d6.attributes, {AppFlowyRichTextKeys.bold: true}); + + final d7 = afterDelta[6] as TextInsert; + expect( + d7.text, + ' gained tremendous popularity, allowing for seamless real-time text communication across the globe.', + ); + expect(d7.attributes, null); + }); + + test('replace markdown text with selection from start to end', () async { + final text1 = + '''The introduction of the World Wide Web in the early 1990s marked a turning point.'''; + final text2 = + '''Tim Berners-Lee's invention made the internet accessible to non-technical users, opening the floodgates for mass adoption.'''; + final text3 = + '''Email became widespread, and instant messaging services like ICQ and AOL Instant Messenger gained popularity, allowing for real-time text communication.'''; + + final document = Document( + root: pageNode( + children: [ + paragraphNode(delta: Delta()..insert(text1)), + paragraphNode(delta: Delta()..insert(text2)), + paragraphNode(delta: Delta()..insert(text3)), + ], + ), + ); + final editorState = EditorState(document: document); + + editorState.selection = Selection( + start: Position(path: [0]), + end: Position(path: [0], offset: text1.length), + ); + + final markdownText = '''1. $text1 + +2. $text1 + +3. $text1'''; + final markdownTextRobot = MarkdownTextRobot( + editorState: editorState, + ); + await markdownTextRobot.replace( + selection: editorState.selection!, + markdownText: markdownText, + ); + + final nodes = editorState.document.root.children; + expect(nodes.length, 5); + + final d1 = nodes[0].delta!.toList()[0] as TextInsert; + expect(d1.text, text1); + expect(d1.attributes, null); + expect(nodes[0].type, NumberedListBlockKeys.type); + + final d2 = nodes[1].delta!.toList()[0] as TextInsert; + expect(d2.text, text1); + expect(d2.attributes, null); + expect(nodes[1].type, NumberedListBlockKeys.type); + + final d3 = nodes[2].delta!.toList()[0] as TextInsert; + expect(d3.text, text1); + expect(d3.attributes, null); + expect(nodes[2].type, NumberedListBlockKeys.type); + + final d4 = nodes[3].delta!.toList()[0] as TextInsert; + expect(d4.text, text2); + expect(d4.attributes, null); + + final d5 = nodes[4].delta!.toList()[0] as TextInsert; + expect(d5.text, text3); + expect(d5.attributes, null); + }); + }); + + group('markdown text robot - replace in multiple lines:', () { + final text1 = + '''The introduction of the World Wide Web in the early 1990s marked a turning point. '''; + final text2 = + '''Tim Berners-Lee's invention made the internet accessible to non-technical users, opening the floodgates for mass adoption. '''; + final text3 = + '''Email became widespread, and instant messaging services like ICQ and AOL Instant Messenger gained popularity, allowing for real-time text communication.'''; + + Document buildTestDocument() { + return Document( + root: pageNode( + children: [ + paragraphNode(delta: Delta()..insert(text1)), + paragraphNode(delta: Delta()..insert(text2)), + paragraphNode(delta: Delta()..insert(text3)), + ], + ), + ); + } + + // 1. create a document with 3 paragraph nodes + // 2. use the text robot to replace the selected content in the multiple lines + // 3. check the document + test( + 'the selection starts with the first paragraph and ends with the middle of second paragraph', + () async { + final document = buildTestDocument(); + final editorState = EditorState(document: document); + + editorState.selection = Selection( + start: Position( + path: [0], + ), + end: Position( + path: [1], + offset: text2.length - + ', opening the floodgates for mass adoption. '.length, + ), + ); + + final markdownText = + '''The **introduction** of the World Wide Web in the *early 1990s* marked a significant turning point. + +Tim Berners-Lee's **revolutionary invention** made the internet accessible to non-technical users'''; + final markdownTextRobot = MarkdownTextRobot( + editorState: editorState, + ); + await markdownTextRobot.replace( + selection: editorState.selection!, + markdownText: markdownText, + ); + + final afterNodes = editorState.document.root.children; + expect(afterNodes.length, 3); + + { + // first paragraph + final delta1 = afterNodes[0].delta!.toList(); + expect(delta1.length, 5); + + final d1 = delta1[0] as TextInsert; + expect(d1.text, 'The '); + expect(d1.attributes, null); + + final d2 = delta1[1] as TextInsert; + expect(d2.text, 'introduction'); + expect(d2.attributes, {AppFlowyRichTextKeys.bold: true}); + + final d3 = delta1[2] as TextInsert; + expect(d3.text, ' of the World Wide Web in the '); + expect(d3.attributes, null); + + final d4 = delta1[3] as TextInsert; + expect(d4.text, 'early 1990s'); + expect(d4.attributes, {AppFlowyRichTextKeys.italic: true}); + + final d5 = delta1[4] as TextInsert; + expect(d5.text, ' marked a significant turning point.'); + expect(d5.attributes, null); + } + + { + // second paragraph + final delta2 = afterNodes[1].delta!.toList(); + expect(delta2.length, 3); + + final d1 = delta2[0] as TextInsert; + expect(d1.text, "Tim Berners-Lee's "); + expect(d1.attributes, null); + + final d2 = delta2[1] as TextInsert; + expect(d2.text, "revolutionary invention"); + expect(d2.attributes, {AppFlowyRichTextKeys.bold: true}); + + final d3 = delta2[2] as TextInsert; + expect( + d3.text, + " made the internet accessible to non-technical users, opening the floodgates for mass adoption. ", + ); + expect(d3.attributes, null); + } + + { + // third paragraph + final delta3 = afterNodes[2].delta!.toList(); + expect(delta3.length, 1); + + final d1 = delta3[0] as TextInsert; + expect(d1.text, text3); + expect(d1.attributes, null); + } + }); + + test( + 'the selection starts with the middle of the first paragraph and ends with the middle of last paragraph', + () async { + final document = buildTestDocument(); + final editorState = EditorState(document: document); + + editorState.selection = Selection( + start: Position( + path: [0], + offset: 'The introduction of the World Wide Web'.length, + ), + end: Position( + path: [2], + offset: + 'Email became widespread, and instant messaging services like ICQ and AOL Instant Messenger gained popularity' + .length, + ), + ); + + final markdownText = + ''' in the **early 1990s** marked a *significant turning point* in technological history. + +Tim Berners-Lee's **revolutionary invention** made the internet accessible to non-technical users, opening the floodgates for *unprecedented mass adoption*. + +Email became **widely prevalent**, and instant messaging services like *ICQ* and *AOL Instant Messenger* gained tremendous popularity + '''; + final markdownTextRobot = MarkdownTextRobot( + editorState: editorState, + ); + await markdownTextRobot.replace( + selection: editorState.selection!, + markdownText: markdownText, + ); + + final afterNodes = editorState.document.root.children; + expect(afterNodes.length, 3); + + { + // first paragraph + final delta1 = afterNodes[0].delta!.toList(); + expect(delta1.length, 5); + + final d1 = delta1[0] as TextInsert; + expect(d1.text, 'The introduction of the World Wide Web in the '); + expect(d1.attributes, null); + + final d2 = delta1[1] as TextInsert; + expect(d2.text, 'early 1990s'); + expect(d2.attributes, {AppFlowyRichTextKeys.bold: true}); + + final d3 = delta1[2] as TextInsert; + expect(d3.text, ' marked a '); + expect(d3.attributes, null); + + final d4 = delta1[3] as TextInsert; + expect(d4.text, 'significant turning point'); + expect(d4.attributes, {AppFlowyRichTextKeys.italic: true}); + + final d5 = delta1[4] as TextInsert; + expect(d5.text, ' in technological history.'); + expect(d5.attributes, null); + } + + { + // second paragraph + final delta2 = afterNodes[1].delta!.toList(); + expect(delta2.length, 5); + + final d1 = delta2[0] as TextInsert; + expect(d1.text, "Tim Berners-Lee's "); + expect(d1.attributes, null); + + final d2 = delta2[1] as TextInsert; + expect(d2.text, "revolutionary invention"); + expect(d2.attributes, {AppFlowyRichTextKeys.bold: true}); + + final d3 = delta2[2] as TextInsert; + expect( + d3.text, + " made the internet accessible to non-technical users, opening the floodgates for ", + ); + expect(d3.attributes, null); + + final d4 = delta2[3] as TextInsert; + expect(d4.text, "unprecedented mass adoption"); + expect(d4.attributes, {AppFlowyRichTextKeys.italic: true}); + + final d5 = delta2[4] as TextInsert; + expect(d5.text, "."); + expect(d5.attributes, null); + } + + { + // third paragraph + // third paragraph + final delta3 = afterNodes[2].delta!.toList(); + expect(delta3.length, 7); + + final d1 = delta3[0] as TextInsert; + expect(d1.text, "Email became "); + expect(d1.attributes, null); + + final d2 = delta3[1] as TextInsert; + expect(d2.text, "widely prevalent"); + expect(d2.attributes, {AppFlowyRichTextKeys.bold: true}); + + final d3 = delta3[2] as TextInsert; + expect(d3.text, ", and instant messaging services like "); + expect(d3.attributes, null); + + final d4 = delta3[3] as TextInsert; + expect(d4.text, "ICQ"); + expect(d4.attributes, {AppFlowyRichTextKeys.italic: true}); + + final d5 = delta3[4] as TextInsert; + expect(d5.text, " and "); + expect(d5.attributes, null); + + final d6 = delta3[5] as TextInsert; + expect(d6.text, "AOL Instant Messenger"); + expect(d6.attributes, {AppFlowyRichTextKeys.italic: true}); + + final d7 = delta3[6] as TextInsert; + expect( + d7.text, + " gained tremendous popularity, allowing for real-time text communication.", + ); + expect(d7.attributes, null); + } + }); + + test( + 'the length of the returned response less than the length of the selected text', + () async { + final document = buildTestDocument(); + final editorState = EditorState(document: document); + + editorState.selection = Selection( + start: Position( + path: [0], + offset: 'The introduction of the World Wide Web'.length, + ), + end: Position( + path: [2], + offset: + 'Email became widespread, and instant messaging services like ICQ and AOL Instant Messenger gained popularity' + .length, + ), + ); + + final markdownText = + ''' in the **early 1990s** marked a *significant turning point* in technological history.'''; + final markdownTextRobot = MarkdownTextRobot( + editorState: editorState, + ); + await markdownTextRobot.replace( + selection: editorState.selection!, + markdownText: markdownText, + ); + + final afterNodes = editorState.document.root.children; + expect(afterNodes.length, 2); + + { + // first paragraph + final delta1 = afterNodes[0].delta!.toList(); + expect(delta1.length, 5); + + final d1 = delta1[0] as TextInsert; + expect(d1.text, "The introduction of the World Wide Web in the "); + expect(d1.attributes, null); + + final d2 = delta1[1] as TextInsert; + expect(d2.text, "early 1990s"); + expect(d2.attributes, {AppFlowyRichTextKeys.bold: true}); + + final d3 = delta1[2] as TextInsert; + expect(d3.text, " marked a "); + expect(d3.attributes, null); + + final d4 = delta1[3] as TextInsert; + expect(d4.text, "significant turning point"); + expect(d4.attributes, {AppFlowyRichTextKeys.italic: true}); + + final d5 = delta1[4] as TextInsert; + expect(d5.text, " in technological history."); + expect(d5.attributes, null); + } + + { + // second paragraph + final delta2 = afterNodes[1].delta!.toList(); + expect(delta2.length, 1); + + final d1 = delta2[0] as TextInsert; + expect(d1.text, ", allowing for real-time text communication."); + expect(d1.attributes, null); + } + }); + }); } const _sample1 = '''# The Curious Cat diff --git a/frontend/appflowy_flutter/test/unit_test/document/turn_into/turn_into_test.dart b/frontend/appflowy_flutter/test/unit_test/document/turn_into/turn_into_test.dart index 387375c286..d2432557eb 100644 --- a/frontend/appflowy_flutter/test/unit_test/document/turn_into/turn_into_test.dart +++ b/frontend/appflowy_flutter/test/unit_test/document/turn_into/turn_into_test.dart @@ -1,7 +1,8 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_option_cubit.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy_backend/log.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor/appflowy_editor.dart' + hide quoteNode, QuoteBlockKeys; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -23,11 +24,6 @@ void main() { void Function(EditorState editorState, Node node)? afterTurnInto, }) async { final editorState = EditorState(document: document); - final cubit = BlockActionOptionCubit( - editorState: editorState, - blockComponentBuilder: {}, - ); - final types = toType == null ? EditorOptionActionType.turnInto.supportTypes : [toType]; @@ -42,7 +38,12 @@ void main() { final node = editorState.getNodeAtPath([0])!; expect(node.type, originalType); - final result = await cubit.turnIntoBlock(type, node, level: level); + final result = await BlockActionOptionCubit.turnIntoBlock( + type, + node, + editorState, + level: level, + ); expect(result, true); final newNode = editorState.getNodeAtPath([0])!; expect(newNode.type, type); @@ -58,9 +59,10 @@ void main() { Selection.collapsed( Position(path: [0]), ); - await cubit.turnIntoBlock( + await BlockActionOptionCubit.turnIntoBlock( originalType, newNode, + editorState, ); expect(result, true); } @@ -163,8 +165,6 @@ void main() { for (final type in [ HeadingBlockKeys.type, - QuoteBlockKeys.type, - CalloutBlockKeys.type, ]) { test('from nested bulleted list to $type', () async { const text = 'bulleted list'; @@ -229,8 +229,6 @@ void main() { for (final type in [ HeadingBlockKeys.type, - QuoteBlockKeys.type, - CalloutBlockKeys.type, ]) { test('from nested numbered list to $type', () async { const text = 'numbered list'; @@ -295,8 +293,6 @@ void main() { for (final type in [ HeadingBlockKeys.type, - QuoteBlockKeys.type, - CalloutBlockKeys.type, ]) { // numbered list, bulleted list, todo list // before @@ -391,6 +387,8 @@ void main() { BulletedListBlockKeys.type, NumberedListBlockKeys.type, TodoListBlockKeys.type, + QuoteBlockKeys.type, + CalloutBlockKeys.type, ]) { // numbered list, bulleted list, todo list // before diff --git a/frontend/appflowy_flutter/test/unit_test/editor/transaction_adapter_test.dart b/frontend/appflowy_flutter/test/unit_test/editor/transaction_adapter_test.dart index 4a7457a43b..9e60c13ed7 100644 --- a/frontend/appflowy_flutter/test/unit_test/editor/transaction_adapter_test.dart +++ b/frontend/appflowy_flutter/test/unit_test/editor/transaction_adapter_test.dart @@ -290,5 +290,107 @@ void main() { await editorState.apply(transaction); await completer.future; }); + + test('text retain with attributes that are false', () async { + final node = paragraphNode( + delta: Delta() + ..insert( + 'Hello AppFlowy', + attributes: { + 'bold': true, + }, + ), + ); + final document = Document( + root: pageNode( + children: [ + node, + ], + ), + ); + final transactionAdapter = TransactionAdapter( + documentId: '', + documentService: DocumentService(), + ); + + final editorState = EditorState( + document: document, + ); + + int counter = 0; + final completer = Completer(); + editorState.transactionStream.listen((event) { + final time = event.$1; + if (time == TransactionTime.before) { + final actions = transactionAdapter.transactionToBlockActions( + event.$2, + editorState, + ); + final textActions = + transactionAdapter.filterTextDeltaActions(actions); + final blockActions = transactionAdapter.filterBlockActions(actions); + expect(textActions.length, 1); + expect(blockActions.length, 1); + if (counter == 1) { + // check text operation + final textAction = textActions.first; + final textId = textAction.textDeltaPayloadPB?.textId; + { + expect(textAction.textDeltaType, TextDeltaType.create); + + expect(textId, isNotEmpty); + final delta = textAction.textDeltaPayloadPB?.delta; + expect( + delta, + equals( + '[{"insert":"Hello","attributes":{"bold":null}},{"insert":" AppFlowy","attributes":{"bold":true}}]', + ), + ); + } + } else if (counter == 3) { + final textAction = textActions.first; + final textId = textAction.textDeltaPayloadPB?.textId; + { + expect(textAction.textDeltaType, TextDeltaType.update); + + expect(textId, isNotEmpty); + final delta = textAction.textDeltaPayloadPB?.delta; + expect( + delta, + equals( + '[{"retain":5,"attributes":{"bold":null}}]', + ), + ); + } + } + } else if (time == TransactionTime.after && counter == 3) { + completer.complete(); + } + }); + + counter = 1; + final insertTransaction = editorState.transaction; + insertTransaction.formatText(node, 0, 5, { + 'bold': false, + }); + + await editorState.apply(insertTransaction); + + counter = 2; + final updateTransaction = editorState.transaction; + updateTransaction.formatText(node, 0, 5, { + 'bold': true, + }); + await editorState.apply(updateTransaction); + + counter = 3; + final formatTransaction = editorState.transaction; + formatTransaction.formatText(node, 0, 5, { + 'bold': false, + }); + await editorState.apply(formatTransaction); + + await completer.future; + }); }); } diff --git a/frontend/appflowy_flutter/test/unit_test/image/appflowy_network_image_test.dart b/frontend/appflowy_flutter/test/unit_test/image/appflowy_network_image_test.dart new file mode 100644 index 0000000000..3c075126db --- /dev/null +++ b/frontend/appflowy_flutter/test/unit_test/image/appflowy_network_image_test.dart @@ -0,0 +1,35 @@ +import 'package:appflowy/shared/appflowy_network_image.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('AppFlowy Network Image:', () { + setUpAll(() { + Log.shared.disableLog = true; + }); + + tearDownAll(() { + Log.shared.disableLog = false; + }); + + test( + 'retry count should be clear if the value exceeds max retries', + () async { + const maxRetries = 5; + const fakeUrl = 'https://plus.unsplash.com/premium_photo-1731948132439'; + final retryCounter = FlowyNetworkRetryCounter(); + final tag = retryCounter.add(fakeUrl); + for (var i = 0; i < maxRetries; i++) { + retryCounter.increment(fakeUrl); + expect(retryCounter.getRetryCount(fakeUrl), i + 1); + } + retryCounter.clear( + tag: tag, + url: fakeUrl, + maxRetries: maxRetries, + ); + expect(retryCounter.getRetryCount(fakeUrl), 0); + }, + ); + }); +} diff --git a/frontend/appflowy_flutter/test/unit_test/link_preview/link_preview_test.dart b/frontend/appflowy_flutter/test/unit_test/link_preview/link_preview_test.dart new file mode 100644 index 0000000000..5b6f88801a --- /dev/null +++ b/frontend/appflowy_flutter/test/unit_test/link_preview/link_preview_test.dart @@ -0,0 +1,47 @@ +import 'package:appflowy/plugins/document/presentation/editor_plugins/link_preview/link_parsers/default_parser.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() async { + test( + 'description', + () async { + final links = [ + 'https://www.baidu.com/', + 'https://appflowy.io/', + 'https://github.com/AppFlowy-IO/AppFlowy', + 'https://github.com/', + 'https://www.figma.com/design/3K0ai4FhDOJ3Lts8G3KOVP/Page?node-id=7282-4007&p=f&t=rpfvEvh9K9J9WkIo-0', + 'https://www.figma.com/files/drafts', + 'https://www.youtube.com/watch?v=LyY5Rh9qBvA', + 'https://www.youtube.com/', + 'https://www.youtube.com/watch?v=a6GDT7', + 'http://www.test.com/', + 'https://www.baidu.com/s?wd=test&rsv_spt=1&rsv_iqid=0xb6a7840b00e5324a&issp=1&f=8&rsv_bp=1&rsv_idx=2&ie=utf-8&tn=22073068_7_oem_dg&rsv_dl=tb&rsv_enter=1&rsv_sug3=5&rsv_sug1=4&rsv_sug7=100&rsv_sug2=0&rsv_btype=i&prefixsug=test&rsp=9&inputT=478&rsv_sug4=547', + 'https://www.google.com/', + 'https://www.google.com.hk/search?q=test&oq=test&gs_lcrp=EgZjaHJvbWUyCQgAEEUYORiABDIHCAEQABiABDIHCAIQABiABDIHCAMQABiABDIHCAQQABiABDIHCAUQABiABDIHCAYQABiABDIHCAcQABiABDIHCAgQLhiABDIHCAkQABiABNIBCTE4MDJqMGoxNagCCLACAfEFAQs7K9PprSfxBQELOyvT6a0n&sourceid=chrome&ie=UTF-8', + 'www.baidu.com', + 'baidu.com', + 'com', + 'https://www.baidu.com', + 'https://github.com/AppFlowy-IO/AppFlowy', + 'https://appflowy.com/app/c29fafc4-b7c0-4549-8702-71339b0fd9ea/59f36be8-9b2f-4d3e-b6a1-816c6c2043e5?blockId=GCY_T4', + ]; + + final parser = DefaultParser(); + int i = 1; + for (final link in links) { + final formatLink = LinkInfoParser.formatUrl(link); + final siteInfo = await parser + .parse(Uri.tryParse(formatLink) ?? Uri.parse(formatLink)); + if (siteInfo?.isEmpty() ?? true) { + debugPrint('$i : $formatLink ---- empty \n'); + } else { + debugPrint('$i : $formatLink ---- \n$siteInfo \n'); + } + i++; + } + }, + timeout: const Timeout(Duration(seconds: 120)), + ); +} diff --git a/frontend/appflowy_flutter/test/unit_test/markdown/markdown_parser_test.dart b/frontend/appflowy_flutter/test/unit_test/markdown/markdown_parser_test.dart index 70775612e2..707cc23d4f 100644 --- a/frontend/appflowy_flutter/test/unit_test/markdown/markdown_parser_test.dart +++ b/frontend/appflowy_flutter/test/unit_test/markdown/markdown_parser_test.dart @@ -1,7 +1,10 @@ +import 'package:appflowy/plugins/document/presentation/editor_plugins/image/common.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy/shared/markdown_to_document.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { @@ -17,21 +20,78 @@ void main() { ), ], ); - final markdown = customDocumentToMarkdown(document); + final markdown = await customDocumentToMarkdown(document); expect(markdown, '[file.txt](https://file.com)\n'); }); - test('link preview', () { + test('link preview', () async { final document = Document.blank() ..insert( [0], [linkPreviewNode(url: 'https://www.link_preview.com')], ); - final markdown = customDocumentToMarkdown(document); + final markdown = await customDocumentToMarkdown(document); expect( markdown, '[https://www.link_preview.com](https://www.link_preview.com)\n', ); }); + + test('multiple images', () async { + const png1 = 'https://www.appflowy.png', + png2 = 'https://www.appflowy2.png'; + final document = Document.blank() + ..insert( + [0], + [ + multiImageNode( + images: [ + ImageBlockData( + url: png1, + type: CustomImageType.external, + ), + ImageBlockData( + url: png2, + type: CustomImageType.external, + ), + ], + ), + ], + ); + final markdown = await customDocumentToMarkdown(document); + expect( + markdown, + '![]($png1)\n![]($png2)', + ); + }); + + test('subpage block', () async { + const testSubpageId = 'testSubpageId'; + final subpageNode = pageMentionNode(testSubpageId); + final document = Document.blank() + ..insert( + [0], + [subpageNode], + ); + final markdown = await customDocumentToMarkdown(document); + expect( + markdown, + '[]($testSubpageId)\n', + ); + }); + + test('date or reminder', () async { + final dateTime = DateTime.now(); + final document = Document.blank() + ..insert( + [0], + [dateMentionNode()], + ); + final markdown = await customDocumentToMarkdown(document); + expect( + markdown, + '${DateFormat.yMMMd().format(dateTime)}\n', + ); + }); }); } diff --git a/frontend/appflowy_flutter/test/unit_test/simple_table/simple_table_delete_operation_test.dart b/frontend/appflowy_flutter/test/unit_test/simple_table/simple_table_delete_operation_test.dart index c9c1be8379..cb28f955da 100644 --- a/frontend/appflowy_flutter/test/unit_test/simple_table/simple_table_delete_operation_test.dart +++ b/frontend/appflowy_flutter/test/unit_test/simple_table/simple_table_delete_operation_test.dart @@ -170,5 +170,67 @@ void main() { '0': TableAlign.center.key, }); }); + + test('delete a column with text color & bold style (1)', () async { + final (editorState, tableNode) = createEditorStateAndTable( + rowCount: 3, + columnCount: 4, + ); + // delete the column 1 + final tableCellNode = + tableNode.getTableCellNode(rowIndex: 0, columnIndex: 1); + await editorState.updateColumnTextColor( + tableCellNode: tableCellNode!, + color: '0xFF0000FF', + ); + await editorState.toggleColumnBoldAttribute( + tableCellNode: tableCellNode, + isBold: true, + ); + expect(tableNode.columnTextColors, { + '1': '0xFF0000FF', + }); + expect(tableNode.columnBoldAttributes, { + '1': true, + }); + await editorState.deleteColumnInTable(tableNode, 0); + expect(tableNode.columnTextColors, { + '0': '0xFF0000FF', + }); + expect(tableNode.columnBoldAttributes, { + '0': true, + }); + expect(tableNode.rowLength, 3); + expect(tableNode.columnLength, 3); + }); + + test('delete a column with text color & bold style (2)', () async { + final (editorState, tableNode) = createEditorStateAndTable( + rowCount: 3, + columnCount: 4, + ); + // delete the column 1 + final tableCellNode = + tableNode.getTableCellNode(rowIndex: 0, columnIndex: 1); + await editorState.updateColumnTextColor( + tableCellNode: tableCellNode!, + color: '0xFF0000FF', + ); + await editorState.toggleColumnBoldAttribute( + tableCellNode: tableCellNode, + isBold: true, + ); + expect(tableNode.columnTextColors, { + '1': '0xFF0000FF', + }); + expect(tableNode.columnBoldAttributes, { + '1': true, + }); + await editorState.deleteColumnInTable(tableNode, 1); + expect(tableNode.columnTextColors, {}); + expect(tableNode.columnBoldAttributes, {}); + expect(tableNode.rowLength, 3); + expect(tableNode.columnLength, 3); + }); }); } diff --git a/frontend/appflowy_flutter/test/unit_test/simple_table/simple_table_duplicate_operation_test.dart b/frontend/appflowy_flutter/test/unit_test/simple_table/simple_table_duplicate_operation_test.dart index 123310538a..85a1c252c7 100644 --- a/frontend/appflowy_flutter/test/unit_test/simple_table/simple_table_duplicate_operation_test.dart +++ b/frontend/appflowy_flutter/test/unit_test/simple_table/simple_table_duplicate_operation_test.dart @@ -161,5 +161,69 @@ void main() { '1': TableAlign.center.key, }); }); + + test('duplicate a column with text color & bold style (1)', () async { + final (editorState, tableNode) = createEditorStateAndTable( + rowCount: 3, + columnCount: 4, + ); + // duplicate the column 1 + final tableCellNode = + tableNode.getTableCellNode(rowIndex: 0, columnIndex: 1); + await editorState.updateColumnTextColor( + tableCellNode: tableCellNode!, + color: '0xFF0000FF', + ); + await editorState.toggleColumnBoldAttribute( + tableCellNode: tableCellNode, + isBold: true, + ); + expect(tableNode.columnTextColors, { + '1': '0xFF0000FF', + }); + expect(tableNode.columnBoldAttributes, { + '1': true, + }); + await editorState.duplicateColumnInTable(tableNode, 1); + expect(tableNode.columnTextColors, { + '1': '0xFF0000FF', + '2': '0xFF0000FF', + }); + expect(tableNode.columnBoldAttributes, { + '1': true, + '2': true, + }); + }); + + test('duplicate a column with text color & bold style (2)', () async { + final (editorState, tableNode) = createEditorStateAndTable( + rowCount: 3, + columnCount: 4, + ); + // duplicate the column 1 + final tableCellNode = + tableNode.getTableCellNode(rowIndex: 0, columnIndex: 1); + await editorState.updateColumnTextColor( + tableCellNode: tableCellNode!, + color: '0xFF0000FF', + ); + await editorState.toggleColumnBoldAttribute( + tableCellNode: tableCellNode, + isBold: true, + ); + expect(tableNode.columnTextColors, { + '1': '0xFF0000FF', + }); + expect(tableNode.columnBoldAttributes, { + '1': true, + }); + await editorState.duplicateColumnInTable(tableNode, 0); + expect(tableNode.columnTextColors, { + '2': '0xFF0000FF', + }); + expect(tableNode.columnBoldAttributes, { + '2': true, + }); + }); }); } diff --git a/frontend/appflowy_flutter/test/unit_test/simple_table/simple_table_insert_operation_test.dart b/frontend/appflowy_flutter/test/unit_test/simple_table/simple_table_insert_operation_test.dart index c84615ac42..86c4236a03 100644 --- a/frontend/appflowy_flutter/test/unit_test/simple_table/simple_table_insert_operation_test.dart +++ b/frontend/appflowy_flutter/test/unit_test/simple_table/simple_table_insert_operation_test.dart @@ -190,5 +190,67 @@ void main() { '0': TableAlign.center.key, }); }); + + test('insert a column with text color & bold style (1)', () async { + final (editorState, tableNode) = createEditorStateAndTable( + rowCount: 2, + columnCount: 3, + ); + // insert the column at the first position + final tableCellNode = + tableNode.getTableCellNode(rowIndex: 0, columnIndex: 0); + await editorState.updateColumnTextColor( + tableCellNode: tableCellNode!, + color: '0xFF0000FF', + ); + await editorState.toggleColumnBoldAttribute( + tableCellNode: tableCellNode, + isBold: true, + ); + expect(tableNode.columnTextColors, { + '0': '0xFF0000FF', + }); + expect(tableNode.columnBoldAttributes, { + '0': true, + }); + await editorState.insertColumnInTable(tableNode, 0); + expect(tableNode.columnTextColors, { + '1': '0xFF0000FF', + }); + expect(tableNode.columnBoldAttributes, { + '1': true, + }); + }); + + test('insert a column with text color & bold style (2)', () async { + final (editorState, tableNode) = createEditorStateAndTable( + rowCount: 2, + columnCount: 3, + ); + // insert the column at the first position + final tableCellNode = + tableNode.getTableCellNode(rowIndex: 0, columnIndex: 0); + await editorState.updateColumnTextColor( + tableCellNode: tableCellNode!, + color: '0xFF0000FF', + ); + await editorState.toggleColumnBoldAttribute( + tableCellNode: tableCellNode, + isBold: true, + ); + expect(tableNode.columnTextColors, { + '0': '0xFF0000FF', + }); + expect(tableNode.columnBoldAttributes, { + '0': true, + }); + await editorState.insertColumnInTable(tableNode, 1); + expect(tableNode.columnTextColors, { + '0': '0xFF0000FF', + }); + expect(tableNode.columnBoldAttributes, { + '0': true, + }); + }); }); } diff --git a/frontend/appflowy_flutter/test/unit_test/simple_table/simple_table_markdown_test.dart b/frontend/appflowy_flutter/test/unit_test/simple_table/simple_table_markdown_test.dart index 4037958873..354e6bfa5e 100644 --- a/frontend/appflowy_flutter/test/unit_test/simple_table/simple_table_markdown_test.dart +++ b/frontend/appflowy_flutter/test/unit_test/simple_table/simple_table_markdown_test.dart @@ -49,6 +49,21 @@ void main() { expect(tableNode.rowLength, equals(4)); expect(tableNode.columnLength, equals(4)); }); + + test('convert markdown to simple table (2)', () async { + final document = customMarkdownToDocument( + _sampleMarkdown1, + tableWidth: 200, + ); + expect(document, isNotNull); + final tableNode = document.nodeAtPath([0])!; + expect(tableNode, isNotNull); + expect(tableNode.type, equals(SimpleTableBlockKeys.type)); + expect(tableNode.columnWidths.length, 4); + for (final entry in tableNode.columnWidths.entries) { + expect(entry.value, equals(200)); + } + }); }); } diff --git a/frontend/appflowy_flutter/test/unit_test/simple_table/simple_table_style_operation_test.dart b/frontend/appflowy_flutter/test/unit_test/simple_table/simple_table_style_operation_test.dart index 940f03711a..dd127d3d0b 100644 --- a/frontend/appflowy_flutter/test/unit_test/simple_table/simple_table_style_operation_test.dart +++ b/frontend/appflowy_flutter/test/unit_test/simple_table/simple_table_style_operation_test.dart @@ -1,5 +1,6 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table.dart'; import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flutter_test/flutter_test.dart'; import 'simple_table_test_helper.dart'; @@ -193,5 +194,45 @@ void main() { expect(tableNode.tableAlign, align); } }); + + test('clear the existing align of the column before updating', () async { + final (editorState, tableNode) = createEditorStateAndTable( + rowCount: 2, + columnCount: 3, + ); + + final firstCellNode = tableNode.getTableCellNode( + rowIndex: 0, + columnIndex: 0, + ); + + Node firstParagraphNode = firstCellNode!.children.first; + + // format the first paragraph to center align + final transaction = editorState.transaction; + transaction.updateNode( + firstParagraphNode, + { + blockComponentAlign: TableAlign.right.key, + }, + ); + await editorState.apply(transaction); + + firstParagraphNode = editorState.getNodeAtPath([0, 0, 0, 0])!; + expect( + firstParagraphNode.attributes[blockComponentAlign], + TableAlign.right.key, + ); + + await editorState.updateColumnAlign( + tableCellNode: firstCellNode, + align: TableAlign.center, + ); + + expect( + firstParagraphNode.attributes[blockComponentAlign], + null, + ); + }); }); } diff --git a/frontend/appflowy_flutter/test/unit_test/util/recent_icons_test.dart b/frontend/appflowy_flutter/test/unit_test/util/recent_icons_test.dart index 9212467d84..a2326a3e33 100644 --- a/frontend/appflowy_flutter/test/unit_test/util/recent_icons_test.dart +++ b/frontend/appflowy_flutter/test/unit_test/util/recent_icons_test.dart @@ -16,6 +16,12 @@ void main() { Log.shared.disableLog = true; }); + bool equalIcon(RecentIcon a, RecentIcon b) => + a.groupName == b.groupName && + a.name == b.name && + a.keywords.equals(b.keywords) && + a.content == b.content; + test('putEmoji', () async { List emojiIds = await RecentIcons.getEmojiIds(); assert(emojiIds.isEmpty); @@ -46,20 +52,15 @@ void main() { }); test('putIcons', () async { - List icons = await RecentIcons.getIcons(); + List icons = await RecentIcons.getIcons(); assert(icons.isEmpty); await loadIconGroups(); final groups = kIconGroups!; - final List localIcons = []; + final List localIcons = []; for (final e in groups) { - localIcons.addAll(e.icons); + localIcons.addAll(e.icons.map((e) => RecentIcon(e, e.name)).toList()); } - bool equalIcon(Icon a, Icon b) => - a.name == b.name && - a.keywords.equals(b.keywords) && - a.content == b.content; - await RecentIcons.putIcon(localIcons.first); icons = await RecentIcons.getIcons(); assert(icons.length == 1); @@ -90,4 +91,26 @@ void main() { ); } }); + + test('put without group name', () async { + RecentIcons.clear(); + List icons = await RecentIcons.getIcons(); + assert(icons.isEmpty); + await loadIconGroups(); + final groups = kIconGroups!; + final List localIcons = []; + for (final e in groups) { + localIcons.addAll(e.icons.map((e) => RecentIcon(e, e.name)).toList()); + } + + await RecentIcons.putIcon(RecentIcon(localIcons.first.icon, '')); + icons = await RecentIcons.getIcons(); + assert(icons.isEmpty); + + await RecentIcons.putIcon( + RecentIcon(localIcons.first.icon, 'Test group name'), + ); + icons = await RecentIcons.getIcons(); + assert(icons.isNotEmpty); + }); } diff --git a/frontend/appflowy_flutter/test/util.dart b/frontend/appflowy_flutter/test/util.dart index c4f2a21c64..3bb774411b 100644 --- a/frontend/appflowy_flutter/test/util.dart +++ b/frontend/appflowy_flutter/test/util.dart @@ -69,7 +69,10 @@ class AppFlowyUnitTest { } Future _initialServices() async { - workspaceService = WorkspaceService(workspaceId: currentWorkspace.id); + workspaceService = WorkspaceService( + workspaceId: currentWorkspace.id, + userId: userProfile.id, + ); } Future createWorkspace() async { diff --git a/frontend/appflowy_flutter/test/widget_test/confirm_dialog_test.dart b/frontend/appflowy_flutter/test/widget_test/confirm_dialog_test.dart index 4458d588cc..8a370f74d5 100644 --- a/frontend/appflowy_flutter/test/widget_test/confirm_dialog_test.dart +++ b/frontend/appflowy_flutter/test/widget_test/confirm_dialog_test.dart @@ -1,4 +1,5 @@ import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart'; +import 'package:appflowy_ui/appflowy_ui.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -29,14 +30,17 @@ void main() { showDialog( context: context, builder: (_) { - return Dialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12.0), - ), - child: ConfirmPopup( - description: "desc", - title: "title", - onConfirm: onConfirm, + return AppFlowyTheme( + data: AppFlowyDefaultTheme().light(), + child: Dialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0), + ), + child: ConfirmPopup( + description: "desc", + title: "title", + onConfirm: onConfirm, + ), ), ); }, diff --git a/frontend/appflowy_flutter/test/widget_test/direction_setting_test.dart b/frontend/appflowy_flutter/test/widget_test/direction_setting_test.dart index d83706f068..4d954d1724 100644 --- a/frontend/appflowy_flutter/test/widget_test/direction_setting_test.dart +++ b/frontend/appflowy_flutter/test/widget_test/direction_setting_test.dart @@ -33,6 +33,7 @@ void main() { appearanceSettings = await UserSettingsBackendService().getAppearanceSetting(); dateTimeSettings = await UserSettingsBackendService().getDateTimeSettings(); + registerFallbackValue(AppFlowyTextDirection.ltr); }); testWidgets('TextDirectionSelect update default text direction setting', @@ -129,7 +130,7 @@ void main() { when( () => mockAppearanceSettingsBloc.setTextDirection( - any(), + any(), ), ).thenAnswer((_) async => {}); when( @@ -146,7 +147,7 @@ void main() { verify( () => mockAppearanceSettingsBloc.setTextDirection( - any(), + any(), ), ).called(1); verify( diff --git a/frontend/appflowy_flutter/test/widget_test/test_material_app.dart b/frontend/appflowy_flutter/test/widget_test/test_material_app.dart index ecb06b97e2..2ebc61a8bc 100644 --- a/frontend/appflowy_flutter/test/widget_test/test_material_app.dart +++ b/frontend/appflowy_flutter/test/widget_test/test_material_app.dart @@ -62,6 +62,7 @@ class WidgetTestApp extends StatelessWidget { scrollbarColor: Colors.transparent, scrollbarHoverColor: Colors.transparent, lightIconColor: Colors.transparent, + toolbarHoverColor: Colors.transparent, ), ], ), diff --git a/frontend/appflowy_flutter/windows/runner/Runner.rc b/frontend/appflowy_flutter/windows/runner/Runner.rc index 77795cde10..3477dab755 100644 --- a/frontend/appflowy_flutter/windows/runner/Runner.rc +++ b/frontend/appflowy_flutter/windows/runner/Runner.rc @@ -119,3 +119,11 @@ END ///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED + +///////////////////////////////////////////////////////////////////////////// +// +// WinSparkle +// + +// And verify signature using DSA public key: +DSAPub DSAPEM "../../dsa_pub.pem" \ No newline at end of file diff --git a/frontend/appflowy_tauri/.eslintignore b/frontend/appflowy_tauri/.eslintignore deleted file mode 100644 index e0ff674834..0000000000 --- a/frontend/appflowy_tauri/.eslintignore +++ /dev/null @@ -1,7 +0,0 @@ -src/services -src/styles -node_modules/ -dist/ -src-tauri/ -.eslintrc.cjs -tsconfig.json \ No newline at end of file diff --git a/frontend/appflowy_tauri/.eslintrc.cjs b/frontend/appflowy_tauri/.eslintrc.cjs deleted file mode 100644 index a1160f0bd3..0000000000 --- a/frontend/appflowy_tauri/.eslintrc.cjs +++ /dev/null @@ -1,73 +0,0 @@ -module.exports = { - // https://eslint.org/docs/latest/use/configure/configuration-files - env: { - browser: true, - es6: true, - node: true, - }, - extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], - parser: '@typescript-eslint/parser', - parserOptions: { - project: 'tsconfig.json', - sourceType: 'module', - tsconfigRootDir: __dirname, - extraFileExtensions: ['.json'], - }, - plugins: ['@typescript-eslint', "react-hooks"], - rules: { - "react-hooks/rules-of-hooks": "error", - "react-hooks/exhaustive-deps": "error", - '@typescript-eslint/adjacent-overload-signatures': 'error', - '@typescript-eslint/no-empty-function': 'error', - '@typescript-eslint/no-empty-interface': 'error', - '@typescript-eslint/no-floating-promises': 'error', - '@typescript-eslint/await-thenable': 'error', - '@typescript-eslint/no-namespace': 'error', - '@typescript-eslint/no-unnecessary-type-assertion': 'error', - '@typescript-eslint/no-redeclare': 'error', - '@typescript-eslint/prefer-for-of': 'error', - '@typescript-eslint/triple-slash-reference': 'error', - '@typescript-eslint/unified-signatures': 'error', - 'no-shadow': 'off', - '@typescript-eslint/no-shadow': 'off', - 'constructor-super': 'error', - eqeqeq: ['error', 'always'], - 'no-cond-assign': 'error', - 'no-duplicate-case': 'error', - 'no-duplicate-imports': 'error', - 'no-empty': [ - 'error', - { - allowEmptyCatch: true, - }, - ], - 'no-invalid-this': 'error', - 'no-new-wrappers': 'error', - 'no-param-reassign': 'error', - 'no-sequences': 'error', - 'no-throw-literal': 'error', - 'no-unsafe-finally': 'error', - 'no-unused-labels': 'error', - 'no-var': 'error', - 'no-void': 'off', - 'prefer-const': 'error', - 'prefer-spread': 'off', - '@typescript-eslint/no-unused-vars': [ - 'error', - { - argsIgnorePattern: '^_', - } - ], - 'padding-line-between-statements': [ - "error", - { blankLine: "always", prev: ["const", "let", "var"], next: "*"}, - { blankLine: "any", prev: ["const", "let", "var"], next: ["const", "let", "var"]}, - { blankLine: "always", prev: "import", next: "*" }, - { blankLine: "any", prev: "import", next: "import" }, - { blankLine: "always", prev: "block-like", next: "*" }, - { blankLine: "always", prev: "block", next: "*" }, - - ] - }, - ignorePatterns: ['src/**/*.test.ts', '**/__tests__/**/*.json', 'package.json'] -}; diff --git a/frontend/appflowy_tauri/.gitignore b/frontend/appflowy_tauri/.gitignore deleted file mode 100644 index 32a3d59bc2..0000000000 --- a/frontend/appflowy_tauri/.gitignore +++ /dev/null @@ -1,33 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? - -**/src/services/backend/models/ -**/src/services/backend/events/ -**/src/appflowy_app/i18n/translations/ - -coverage -**/AppFlowy-Collab - -.env \ No newline at end of file diff --git a/frontend/appflowy_tauri/.prettierignore b/frontend/appflowy_tauri/.prettierignore deleted file mode 100644 index d515c1c2f2..0000000000 --- a/frontend/appflowy_tauri/.prettierignore +++ /dev/null @@ -1,19 +0,0 @@ -.DS_Store -node_modules -/build -/public -/.svelte-kit -/package -/.vscode -.env -.env.* -!.env.example - -# rust and generated ts code -/src-tauri -/src/services - -# Ignore files for PNPM, NPM and YARN -pnpm-lock.yaml -package-lock.json -yarn.lock diff --git a/frontend/appflowy_tauri/.prettierrc.cjs b/frontend/appflowy_tauri/.prettierrc.cjs deleted file mode 100644 index f283db53a2..0000000000 --- a/frontend/appflowy_tauri/.prettierrc.cjs +++ /dev/null @@ -1,20 +0,0 @@ -module.exports = { - arrowParens: 'always', - bracketSpacing: true, - endOfLine: 'lf', - htmlWhitespaceSensitivity: 'css', - insertPragma: false, - jsxBracketSameLine: false, - jsxSingleQuote: true, - printWidth: 121, - plugins: [require('prettier-plugin-tailwindcss')], - proseWrap: 'preserve', - quoteProps: 'as-needed', - requirePragma: false, - semi: true, - singleQuote: true, - tabWidth: 2, - trailingComma: 'es5', - useTabs: false, - vueIndentScriptAndStyle: false, -}; diff --git a/frontend/appflowy_tauri/.vscode/extensions.json b/frontend/appflowy_tauri/.vscode/extensions.json deleted file mode 100644 index 24d7cc6de8..0000000000 --- a/frontend/appflowy_tauri/.vscode/extensions.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"] -} diff --git a/frontend/appflowy_tauri/README.md b/frontend/appflowy_tauri/README.md deleted file mode 100644 index 102e366893..0000000000 --- a/frontend/appflowy_tauri/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Tauri + React + Typescript - -This template should help get you started developing with Tauri, React and Typescript in Vite. - -## Recommended IDE Setup - -- [VS Code](https://code.visualstudio.com/) + [Tauri](https://marketplace.visualstudio.com/items?itemName=tauri-apps.tauri-vscode) + [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer) diff --git a/frontend/appflowy_tauri/index.html b/frontend/appflowy_tauri/index.html deleted file mode 100644 index 4983fb648b..0000000000 --- a/frontend/appflowy_tauri/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - AppFlowy: The Open Source Alternative To Notion - - - -
- - - diff --git a/frontend/appflowy_tauri/jest.config.cjs b/frontend/appflowy_tauri/jest.config.cjs deleted file mode 100644 index 4939478165..0000000000 --- a/frontend/appflowy_tauri/jest.config.cjs +++ /dev/null @@ -1,21 +0,0 @@ -const { compilerOptions } = require('./tsconfig.json'); -const { pathsToModuleNameMapper } = require("ts-jest"); -const esModules = ["lodash-es", "nanoid"].join("|"); - -/** @type {import('ts-jest').JestConfigWithTsJest} */ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - roots: [''], - modulePaths: [compilerOptions.baseUrl], - moduleNameMapper: { - ...pathsToModuleNameMapper(compilerOptions.paths), - "^lodash-es(/(.*)|$)": "lodash$1", - "^nanoid(/(.*)|$)": "nanoid$1", - }, - "transform": { - "(.*)/node_modules/nanoid/.+\\.(j|t)sx?$": "ts-jest" - }, - "transformIgnorePatterns": [`/node_modules/(?!${esModules})`], - "testRegex": "(/__tests__/.*\.(test|spec))\\.(jsx?|tsx?)$", -}; \ No newline at end of file diff --git a/frontend/appflowy_tauri/package.json b/frontend/appflowy_tauri/package.json deleted file mode 100644 index 30c7978771..0000000000 --- a/frontend/appflowy_tauri/package.json +++ /dev/null @@ -1,126 +0,0 @@ -{ - "name": "appflowy_tauri", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "pnpm sync:i18n && tsc && vite build", - "preview": "vite preview", - "format": "prettier --write .", - "test:code": "eslint --max-warnings=0 --ext .js,.ts,.tsx .", - "test:errors": "pnpm sync:i18n && tsc --noEmit && eslint --ext .js,.ts,.tsx .", - "test:prettier": "pnpm prettier --list-different src", - "tauri:clean": "cargo make --cwd .. tauri_clean", - "tauri:dev": "pnpm sync:i18n && tauri dev", - "sync:i18n": "node scripts/i18n/index.cjs", - "css:variables": "node style-dictionary/config.cjs", - "test": "jest" - }, - "dependencies": { - "@emoji-mart/data": "^1.1.2", - "@emoji-mart/react": "^1.1.1", - "@emotion/react": "^11.10.6", - "@emotion/styled": "^11.10.6", - "@mui/icons-material": "^5.11.11", - "@mui/material": "^5.11.12", - "@mui/system": "^5.14.4", - "@mui/x-date-pickers-pro": "^6.18.2", - "@reduxjs/toolkit": "2.0.0", - "@slate-yjs/core": "^1.0.2", - "@tauri-apps/api": "^1.2.0", - "@types/react-swipeable-views": "^0.13.4", - "dayjs": "^1.11.9", - "emoji-mart": "^5.5.2", - "emoji-regex": "^10.2.1", - "events": "^3.3.0", - "google-protobuf": "^3.15.12", - "i18next": "^22.4.10", - "i18next-browser-languagedetector": "^7.0.1", - "i18next-resources-to-backend": "^1.1.4", - "is-hotkey": "^0.2.0", - "jest": "^29.5.0", - "js-base64": "^3.7.5", - "katex": "^0.16.7", - "lodash-es": "^4.17.21", - "nanoid": "^4.0.0", - "prismjs": "^1.29.0", - "protoc-gen-ts": "0.8.7", - "quill": "^1.3.7", - "quill-delta": "^5.1.0", - "react": "^18.2.0", - "react-beautiful-dnd": "^13.1.1", - "react-big-calendar": "^1.8.5", - "react-color": "^2.19.3", - "react-custom-scrollbars": "^4.2.1", - "react-datepicker": "^4.23.0", - "react-dom": "^18.2.0", - "react-error-boundary": "^3.1.4", - "react-hot-toast": "^2.4.1", - "react-i18next": "^12.2.0", - "react-katex": "^3.0.1", - "react-redux": "^8.0.5", - "react-router-dom": "^6.8.0", - "react-swipeable-views": "^0.14.0", - "react-transition-group": "^4.4.5", - "react-virtualized-auto-sizer": "^1.0.20", - "react-vtree": "^2.0.4", - "react-window": "^1.8.10", - "react18-input-otp": "^1.1.2", - "redux": "^4.2.1", - "rxjs": "^7.8.0", - "sass": "^1.70.0", - "slate": "^0.101.4", - "slate-history": "^0.100.0", - "slate-react": "^0.101.3", - "ts-results": "^3.3.0", - "unsplash-js": "^7.0.19", - "utf8": "^3.0.0", - "valtio": "^1.12.1", - "yjs": "^13.5.51" - }, - "devDependencies": { - "@svgr/plugin-svgo": "^8.0.1", - "@tauri-apps/cli": "^1.5.6", - "@types/google-protobuf": "^3.15.12", - "@types/is-hotkey": "^0.1.7", - "@types/jest": "^29.5.3", - "@types/katex": "^0.16.0", - "@types/lodash-es": "^4.17.11", - "@types/node": "^18.7.10", - "@types/prismjs": "^1.26.0", - "@types/quill": "^2.0.10", - "@types/react": "^18.0.15", - "@types/react-beautiful-dnd": "^13.1.3", - "@types/react-color": "^3.0.6", - "@types/react-custom-scrollbars": "^4.0.13", - "@types/react-datepicker": "^4.19.3", - "@types/react-dom": "^18.0.6", - "@types/react-katex": "^3.0.0", - "@types/react-transition-group": "^4.4.6", - "@types/react-window": "^1.8.8", - "@types/utf8": "^3.0.1", - "@types/uuid": "^9.0.1", - "@typescript-eslint/eslint-plugin": "^5.51.0", - "@typescript-eslint/parser": "^5.51.0", - "@vitejs/plugin-react": "^3.0.0", - "autoprefixer": "^10.4.13", - "babel-jest": "^29.6.2", - "eslint": "^8.34.0", - "eslint-plugin-react": "^7.32.2", - "eslint-plugin-react-hooks": "^4.6.0", - "jest-environment-jsdom": "^29.6.2", - "postcss": "^8.4.21", - "prettier": "2.8.4", - "prettier-plugin-tailwindcss": "^0.2.2", - "style-dictionary": "^3.8.0", - "tailwindcss": "^3.2.7", - "ts-jest": "^29.1.1", - "ts-node-dev": "^2.0.0", - "tsconfig-paths-jest": "^0.0.1", - "typescript": "^4.6.4", - "uuid": "^9.0.0", - "vite": "^4.0.0", - "vite-plugin-svgr": "^3.2.0" - } -} diff --git a/frontend/appflowy_tauri/pnpm-lock.yaml b/frontend/appflowy_tauri/pnpm-lock.yaml deleted file mode 100644 index d670b8b312..0000000000 --- a/frontend/appflowy_tauri/pnpm-lock.yaml +++ /dev/null @@ -1,7264 +0,0 @@ -lockfileVersion: '6.0' - -dependencies: - '@emoji-mart/data': - specifier: ^1.1.2 - version: 1.1.2 - '@emoji-mart/react': - specifier: ^1.1.1 - version: 1.1.1(emoji-mart@5.5.2)(react@18.2.0) - '@emotion/react': - specifier: ^11.10.6 - version: 11.11.0(@types/react@18.2.6)(react@18.2.0) - '@emotion/styled': - specifier: ^11.10.6 - version: 11.11.0(@emotion/react@11.11.0)(@types/react@18.2.6)(react@18.2.0) - '@mui/icons-material': - specifier: ^5.11.11 - version: 5.11.16(@mui/material@5.13.0)(@types/react@18.2.6)(react@18.2.0) - '@mui/material': - specifier: ^5.11.12 - version: 5.13.0(@emotion/react@11.11.0)(@emotion/styled@11.11.0)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) - '@mui/system': - specifier: ^5.14.4 - version: 5.14.4(@emotion/react@11.11.0)(@emotion/styled@11.11.0)(@types/react@18.2.6)(react@18.2.0) - '@mui/x-date-pickers-pro': - specifier: ^6.18.2 - version: 6.18.2(@emotion/react@11.11.0)(@emotion/styled@11.11.0)(@mui/material@5.13.0)(@mui/system@5.14.4)(@types/react@18.2.6)(dayjs@1.11.9)(react-dom@18.2.0)(react@18.2.0) - '@reduxjs/toolkit': - specifier: 2.0.0 - version: 2.0.0(react-redux@8.0.5)(react@18.2.0) - '@slate-yjs/core': - specifier: ^1.0.2 - version: 1.0.2(slate@0.101.4)(yjs@13.6.1) - '@tauri-apps/api': - specifier: ^1.2.0 - version: 1.3.0 - '@types/react-swipeable-views': - specifier: ^0.13.4 - version: 0.13.4 - dayjs: - specifier: ^1.11.9 - version: 1.11.9 - emoji-mart: - specifier: ^5.5.2 - version: 5.5.2 - emoji-regex: - specifier: ^10.2.1 - version: 10.2.1 - events: - specifier: ^3.3.0 - version: 3.3.0 - google-protobuf: - specifier: ^3.15.12 - version: 3.21.2 - i18next: - specifier: ^22.4.10 - version: 22.4.15 - i18next-browser-languagedetector: - specifier: ^7.0.1 - version: 7.0.1 - i18next-resources-to-backend: - specifier: ^1.1.4 - version: 1.1.4 - is-hotkey: - specifier: ^0.2.0 - version: 0.2.0 - jest: - specifier: ^29.5.0 - version: 29.5.0(@types/node@18.16.9) - js-base64: - specifier: ^3.7.5 - version: 3.7.5 - katex: - specifier: ^0.16.7 - version: 0.16.7 - lodash-es: - specifier: ^4.17.21 - version: 4.17.21 - nanoid: - specifier: ^4.0.0 - version: 4.0.2 - prismjs: - specifier: ^1.29.0 - version: 1.29.0 - protoc-gen-ts: - specifier: 0.8.7 - version: 0.8.7 - quill: - specifier: ^1.3.7 - version: 1.3.7 - quill-delta: - specifier: ^5.1.0 - version: 5.1.0 - react: - specifier: ^18.2.0 - version: 18.2.0 - react-beautiful-dnd: - specifier: ^13.1.1 - version: 13.1.1(react-dom@18.2.0)(react@18.2.0) - react-big-calendar: - specifier: ^1.8.5 - version: 1.8.5(react-dom@18.2.0)(react@18.2.0) - react-color: - specifier: ^2.19.3 - version: 2.19.3(react@18.2.0) - react-custom-scrollbars: - specifier: ^4.2.1 - version: 4.2.1(react-dom@18.2.0)(react@18.2.0) - react-datepicker: - specifier: ^4.23.0 - version: 4.23.0(react-dom@18.2.0)(react@18.2.0) - react-dom: - specifier: ^18.2.0 - version: 18.2.0(react@18.2.0) - react-error-boundary: - specifier: ^3.1.4 - version: 3.1.4(react@18.2.0) - react-hot-toast: - specifier: ^2.4.1 - version: 2.4.1(csstype@3.1.2)(react-dom@18.2.0)(react@18.2.0) - react-i18next: - specifier: ^12.2.0 - version: 12.2.2(i18next@22.4.15)(react-dom@18.2.0)(react@18.2.0) - react-katex: - specifier: ^3.0.1 - version: 3.0.1(prop-types@15.8.1)(react@18.2.0) - react-redux: - specifier: ^8.0.5 - version: 8.0.5(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1) - react-router-dom: - specifier: ^6.8.0 - version: 6.11.1(react-dom@18.2.0)(react@18.2.0) - react-swipeable-views: - specifier: ^0.14.0 - version: 0.14.0(react@18.2.0) - react-transition-group: - specifier: ^4.4.5 - version: 4.4.5(react-dom@18.2.0)(react@18.2.0) - react-virtualized-auto-sizer: - specifier: ^1.0.20 - version: 1.0.20(react-dom@18.2.0)(react@18.2.0) - react-vtree: - specifier: ^2.0.4 - version: 2.0.4(@types/react-window@1.8.8)(react-dom@18.2.0)(react-window@1.8.10)(react@18.2.0) - react-window: - specifier: ^1.8.10 - version: 1.8.10(react-dom@18.2.0)(react@18.2.0) - react18-input-otp: - specifier: ^1.1.2 - version: 1.1.3(react-dom@18.2.0)(react@18.2.0) - redux: - specifier: ^4.2.1 - version: 4.2.1 - rxjs: - specifier: ^7.8.0 - version: 7.8.1 - sass: - specifier: ^1.70.0 - version: 1.70.0 - slate: - specifier: ^0.101.4 - version: 0.101.4 - slate-history: - specifier: ^0.100.0 - version: 0.100.0(slate@0.101.4) - slate-react: - specifier: ^0.101.3 - version: 0.101.3(react-dom@18.2.0)(react@18.2.0)(slate@0.101.4) - ts-results: - specifier: ^3.3.0 - version: 3.3.0 - unsplash-js: - specifier: ^7.0.19 - version: 7.0.19 - utf8: - specifier: ^3.0.0 - version: 3.0.0 - valtio: - specifier: ^1.12.1 - version: 1.12.1(@types/react@18.2.6)(react@18.2.0) - yjs: - specifier: ^13.5.51 - version: 13.6.1 - -devDependencies: - '@svgr/plugin-svgo': - specifier: ^8.0.1 - version: 8.0.1(@svgr/core@7.0.0) - '@tauri-apps/cli': - specifier: ^1.5.6 - version: 1.5.6 - '@types/google-protobuf': - specifier: ^3.15.12 - version: 3.15.12 - '@types/is-hotkey': - specifier: ^0.1.7 - version: 0.1.7 - '@types/jest': - specifier: ^29.5.3 - version: 29.5.3 - '@types/katex': - specifier: ^0.16.0 - version: 0.16.0 - '@types/lodash-es': - specifier: ^4.17.11 - version: 4.17.11 - '@types/node': - specifier: ^18.7.10 - version: 18.16.9 - '@types/prismjs': - specifier: ^1.26.0 - version: 1.26.0 - '@types/quill': - specifier: ^2.0.10 - version: 2.0.10 - '@types/react': - specifier: ^18.0.15 - version: 18.2.6 - '@types/react-beautiful-dnd': - specifier: ^13.1.3 - version: 13.1.4 - '@types/react-color': - specifier: ^3.0.6 - version: 3.0.6 - '@types/react-custom-scrollbars': - specifier: ^4.0.13 - version: 4.0.13 - '@types/react-datepicker': - specifier: ^4.19.3 - version: 4.19.3(react-dom@18.2.0)(react@18.2.0) - '@types/react-dom': - specifier: ^18.0.6 - version: 18.2.4 - '@types/react-katex': - specifier: ^3.0.0 - version: 3.0.0 - '@types/react-transition-group': - specifier: ^4.4.6 - version: 4.4.6 - '@types/react-window': - specifier: ^1.8.8 - version: 1.8.8 - '@types/utf8': - specifier: ^3.0.1 - version: 3.0.1 - '@types/uuid': - specifier: ^9.0.1 - version: 9.0.1 - '@typescript-eslint/eslint-plugin': - specifier: ^5.51.0 - version: 5.59.5(@typescript-eslint/parser@5.59.5)(eslint@8.40.0)(typescript@4.9.5) - '@typescript-eslint/parser': - specifier: ^5.51.0 - version: 5.59.5(eslint@8.40.0)(typescript@4.9.5) - '@vitejs/plugin-react': - specifier: ^3.0.0 - version: 3.1.0(vite@4.3.5) - autoprefixer: - specifier: ^10.4.13 - version: 10.4.14(postcss@8.4.23) - babel-jest: - specifier: ^29.6.2 - version: 29.6.2(@babel/core@7.21.8) - eslint: - specifier: ^8.34.0 - version: 8.40.0 - eslint-plugin-react: - specifier: ^7.32.2 - version: 7.32.2(eslint@8.40.0) - eslint-plugin-react-hooks: - specifier: ^4.6.0 - version: 4.6.0(eslint@8.40.0) - jest-environment-jsdom: - specifier: ^29.6.2 - version: 29.6.2 - postcss: - specifier: ^8.4.21 - version: 8.4.23 - prettier: - specifier: 2.8.4 - version: 2.8.4 - prettier-plugin-tailwindcss: - specifier: ^0.2.2 - version: 0.2.8(prettier@2.8.4) - style-dictionary: - specifier: ^3.8.0 - version: 3.8.0 - tailwindcss: - specifier: ^3.2.7 - version: 3.3.2 - ts-jest: - specifier: ^29.1.1 - version: 29.1.1(@babel/core@7.21.8)(babel-jest@29.6.2)(jest@29.5.0)(typescript@4.9.5) - ts-node-dev: - specifier: ^2.0.0 - version: 2.0.0(@types/node@18.16.9)(typescript@4.9.5) - tsconfig-paths-jest: - specifier: ^0.0.1 - version: 0.0.1 - typescript: - specifier: ^4.6.4 - version: 4.9.5 - uuid: - specifier: ^9.0.0 - version: 9.0.0 - vite: - specifier: ^4.0.0 - version: 4.3.5(@types/node@18.16.9)(sass@1.70.0) - vite-plugin-svgr: - specifier: ^3.2.0 - version: 3.2.0(vite@4.3.5) - -packages: - - /@alloc/quick-lru@5.2.0: - resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} - engines: {node: '>=10'} - dev: true - - /@ampproject/remapping@2.2.1: - resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} - engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.18 - - /@babel/code-frame@7.21.4: - resolution: {integrity: sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/highlight': 7.18.6 - - /@babel/code-frame@7.23.5: - resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/highlight': 7.23.4 - chalk: 2.4.2 - - /@babel/compat-data@7.21.7: - resolution: {integrity: sha512-KYMqFYTaenzMK4yUtf4EW9wc4N9ef80FsbMtkwool5zpwl4YrT1SdWYSTRcT94KO4hannogdS+LxY7L+arP3gA==} - engines: {node: '>=6.9.0'} - - /@babel/core@7.21.8: - resolution: {integrity: sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@ampproject/remapping': 2.2.1 - '@babel/code-frame': 7.21.4 - '@babel/generator': 7.21.5 - '@babel/helper-compilation-targets': 7.21.5(@babel/core@7.21.8) - '@babel/helper-module-transforms': 7.21.5 - '@babel/helpers': 7.21.5 - '@babel/parser': 7.21.8 - '@babel/template': 7.20.7 - '@babel/traverse': 7.23.7 - '@babel/types': 7.21.5 - convert-source-map: 1.9.0 - debug: 4.3.4 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.0 - transitivePeerDependencies: - - supports-color - - /@babel/generator@7.21.5: - resolution: {integrity: sha512-SrKK/sRv8GesIW1bDagf9cCG38IOMYZusoe1dfg0D8aiUe3Amvoj1QtjTPAWcfrZFvIwlleLb0gxzQidL9w14w==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.21.5 - '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.18 - jsesc: 2.5.2 - - /@babel/generator@7.23.6: - resolution: {integrity: sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.23.6 - '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.18 - jsesc: 2.5.2 - - /@babel/helper-compilation-targets@7.21.5(@babel/core@7.21.8): - resolution: {integrity: sha512-1RkbFGUKex4lvsB9yhIfWltJM5cZKUftB2eNajaDv3dCMEp49iBG0K14uH8NnX9IPux2+mK7JGEOB0jn48/J6w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/compat-data': 7.21.7 - '@babel/core': 7.21.8 - '@babel/helper-validator-option': 7.21.0 - browserslist: 4.21.5 - lru-cache: 5.1.1 - semver: 6.3.0 - - /@babel/helper-environment-visitor@7.21.5: - resolution: {integrity: sha512-IYl4gZ3ETsWocUWgsFZLM5i1BYx9SoemminVEXadgLBa9TdeorzgLKm8wWLA6J1N/kT3Kch8XIk1laNzYoHKvQ==} - engines: {node: '>=6.9.0'} - - /@babel/helper-environment-visitor@7.22.20: - resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} - engines: {node: '>=6.9.0'} - - /@babel/helper-function-name@7.23.0: - resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.22.15 - '@babel/types': 7.23.6 - - /@babel/helper-hoist-variables@7.22.5: - resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.23.6 - - /@babel/helper-module-imports@7.21.4: - resolution: {integrity: sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.21.5 - - /@babel/helper-module-transforms@7.21.5: - resolution: {integrity: sha512-bI2Z9zBGY2q5yMHoBvJ2a9iX3ZOAzJPm7Q8Yz6YeoUjU/Cvhmi2G4QyTNyPBqqXSgTjUxRg3L0xV45HvkNWWBw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-environment-visitor': 7.21.5 - '@babel/helper-module-imports': 7.21.4 - '@babel/helper-simple-access': 7.21.5 - '@babel/helper-split-export-declaration': 7.18.6 - '@babel/helper-validator-identifier': 7.19.1 - '@babel/template': 7.20.7 - '@babel/traverse': 7.23.7 - '@babel/types': 7.21.5 - transitivePeerDependencies: - - supports-color - - /@babel/helper-plugin-utils@7.21.5: - resolution: {integrity: sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg==} - engines: {node: '>=6.9.0'} - - /@babel/helper-simple-access@7.21.5: - resolution: {integrity: sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.21.5 - - /@babel/helper-split-export-declaration@7.18.6: - resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.21.5 - - /@babel/helper-split-export-declaration@7.22.6: - resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.23.6 - - /@babel/helper-string-parser@7.21.5: - resolution: {integrity: sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==} - engines: {node: '>=6.9.0'} - - /@babel/helper-string-parser@7.23.4: - resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==} - engines: {node: '>=6.9.0'} - - /@babel/helper-validator-identifier@7.19.1: - resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} - engines: {node: '>=6.9.0'} - - /@babel/helper-validator-identifier@7.22.20: - resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} - engines: {node: '>=6.9.0'} - - /@babel/helper-validator-option@7.21.0: - resolution: {integrity: sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==} - engines: {node: '>=6.9.0'} - - /@babel/helpers@7.21.5: - resolution: {integrity: sha512-BSY+JSlHxOmGsPTydUkPf1MdMQ3M81x5xGCOVgWM3G8XH77sJ292Y2oqcp0CbbgxhqBuI46iUz1tT7hqP7EfgA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.20.7 - '@babel/traverse': 7.23.7 - '@babel/types': 7.21.5 - transitivePeerDependencies: - - supports-color - - /@babel/highlight@7.18.6: - resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-validator-identifier': 7.19.1 - chalk: 2.4.2 - js-tokens: 4.0.0 - - /@babel/highlight@7.23.4: - resolution: {integrity: sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-validator-identifier': 7.22.20 - chalk: 2.4.2 - js-tokens: 4.0.0 - - /@babel/parser@7.21.8: - resolution: {integrity: sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==} - engines: {node: '>=6.0.0'} - hasBin: true - dependencies: - '@babel/types': 7.21.5 - - /@babel/parser@7.23.6: - resolution: {integrity: sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==} - engines: {node: '>=6.0.0'} - hasBin: true - dependencies: - '@babel/types': 7.23.6 - - /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.21.8): - resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.21.8 - '@babel/helper-plugin-utils': 7.21.5 - - /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.21.8): - resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.21.8 - '@babel/helper-plugin-utils': 7.21.5 - - /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.21.8): - resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.21.8 - '@babel/helper-plugin-utils': 7.21.5 - - /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.21.8): - resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.21.8 - '@babel/helper-plugin-utils': 7.21.5 - - /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.21.8): - resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.21.8 - '@babel/helper-plugin-utils': 7.21.5 - - /@babel/plugin-syntax-jsx@7.21.4(@babel/core@7.21.8): - resolution: {integrity: sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.21.8 - '@babel/helper-plugin-utils': 7.21.5 - - /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.21.8): - resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.21.8 - '@babel/helper-plugin-utils': 7.21.5 - - /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.21.8): - resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.21.8 - '@babel/helper-plugin-utils': 7.21.5 - - /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.21.8): - resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.21.8 - '@babel/helper-plugin-utils': 7.21.5 - - /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.21.8): - resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.21.8 - '@babel/helper-plugin-utils': 7.21.5 - - /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.21.8): - resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.21.8 - '@babel/helper-plugin-utils': 7.21.5 - - /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.21.8): - resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.21.8 - '@babel/helper-plugin-utils': 7.21.5 - - /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.21.8): - resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.21.8 - '@babel/helper-plugin-utils': 7.21.5 - - /@babel/plugin-syntax-typescript@7.21.4(@babel/core@7.21.8): - resolution: {integrity: sha512-xz0D39NvhQn4t4RNsHmDnnsaQizIlUkdtYvLs8La1BlfjQ6JEwxkJGeqJMW2tAXx+q6H+WFuUTXNdYVpEya0YA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.21.8 - '@babel/helper-plugin-utils': 7.21.5 - - /@babel/plugin-transform-react-jsx-self@7.21.0(@babel/core@7.21.8): - resolution: {integrity: sha512-f/Eq+79JEu+KUANFks9UZCcvydOOGMgF7jBrcwjHa5jTZD8JivnhCJYvmlhR/WTXBWonDExPoW0eO/CR4QJirA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.21.8 - '@babel/helper-plugin-utils': 7.21.5 - dev: true - - /@babel/plugin-transform-react-jsx-source@7.19.6(@babel/core@7.21.8): - resolution: {integrity: sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.21.8 - '@babel/helper-plugin-utils': 7.21.5 - dev: true - - /@babel/runtime@7.0.0: - resolution: {integrity: sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==} - dependencies: - regenerator-runtime: 0.12.1 - dev: false - - /@babel/runtime@7.21.5: - resolution: {integrity: sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q==} - engines: {node: '>=6.9.0'} - dependencies: - regenerator-runtime: 0.13.11 - dev: false - - /@babel/runtime@7.22.10: - resolution: {integrity: sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==} - engines: {node: '>=6.9.0'} - dependencies: - regenerator-runtime: 0.14.0 - dev: false - - /@babel/runtime@7.23.4: - resolution: {integrity: sha512-2Yv65nlWnWlSpe3fXEyX5i7fx5kIKo4Qbcj+hMO0odwaneFjfXw5fdum+4yL20O0QiaHpia0cYQ9xpNMqrBwHg==} - engines: {node: '>=6.9.0'} - dependencies: - regenerator-runtime: 0.14.0 - - /@babel/template@7.20.7: - resolution: {integrity: sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.21.4 - '@babel/parser': 7.21.8 - '@babel/types': 7.21.5 - - /@babel/template@7.22.15: - resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.23.5 - '@babel/parser': 7.23.6 - '@babel/types': 7.23.6 - - /@babel/traverse@7.23.7: - resolution: {integrity: sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.23.5 - '@babel/generator': 7.23.6 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-function-name': 7.23.0 - '@babel/helper-hoist-variables': 7.22.5 - '@babel/helper-split-export-declaration': 7.22.6 - '@babel/parser': 7.23.6 - '@babel/types': 7.23.6 - debug: 4.3.4 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - - /@babel/types@7.21.5: - resolution: {integrity: sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-string-parser': 7.21.5 - '@babel/helper-validator-identifier': 7.19.1 - to-fast-properties: 2.0.0 - - /@babel/types@7.23.6: - resolution: {integrity: sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-string-parser': 7.23.4 - '@babel/helper-validator-identifier': 7.22.20 - to-fast-properties: 2.0.0 - - /@bcoe/v8-coverage@0.2.3: - resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - - /@cspotcode/source-map-support@0.8.1: - resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} - engines: {node: '>=12'} - dependencies: - '@jridgewell/trace-mapping': 0.3.9 - dev: true - - /@emoji-mart/data@1.1.2: - resolution: {integrity: sha512-1HP8BxD2azjqWJvxIaWAMyTySeZY0Osr83ukYjltPVkNXeJvTz7yDrPLBtnrD5uqJ3tg4CcLuuBW09wahqL/fg==} - dev: false - - /@emoji-mart/react@1.1.1(emoji-mart@5.5.2)(react@18.2.0): - resolution: {integrity: sha512-NMlFNeWgv1//uPsvLxvGQoIerPuVdXwK/EUek8OOkJ6wVOWPUizRBJU0hDqWZCOROVpfBgCemaC3m6jDOXi03g==} - peerDependencies: - emoji-mart: ^5.2 - react: ^16.8 || ^17 || ^18 - dependencies: - emoji-mart: 5.5.2 - react: 18.2.0 - dev: false - - /@emotion/babel-plugin@11.11.0: - resolution: {integrity: sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==} - dependencies: - '@babel/helper-module-imports': 7.21.4 - '@babel/runtime': 7.21.5 - '@emotion/hash': 0.9.1 - '@emotion/memoize': 0.8.1 - '@emotion/serialize': 1.1.2 - babel-plugin-macros: 3.1.0 - convert-source-map: 1.9.0 - escape-string-regexp: 4.0.0 - find-root: 1.1.0 - source-map: 0.5.7 - stylis: 4.2.0 - dev: false - - /@emotion/cache@11.11.0: - resolution: {integrity: sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==} - dependencies: - '@emotion/memoize': 0.8.1 - '@emotion/sheet': 1.2.2 - '@emotion/utils': 1.2.1 - '@emotion/weak-memoize': 0.3.1 - stylis: 4.2.0 - dev: false - - /@emotion/hash@0.9.1: - resolution: {integrity: sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==} - dev: false - - /@emotion/is-prop-valid@1.2.1: - resolution: {integrity: sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==} - dependencies: - '@emotion/memoize': 0.8.1 - dev: false - - /@emotion/memoize@0.8.1: - resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==} - dev: false - - /@emotion/react@11.11.0(@types/react@18.2.6)(react@18.2.0): - resolution: {integrity: sha512-ZSK3ZJsNkwfjT3JpDAWJZlrGD81Z3ytNDsxw1LKq1o+xkmO5pnWfr6gmCC8gHEFf3nSSX/09YrG67jybNPxSUw==} - peerDependencies: - '@types/react': '*' - react: '>=16.8.0' - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.21.5 - '@emotion/babel-plugin': 11.11.0 - '@emotion/cache': 11.11.0 - '@emotion/serialize': 1.1.2 - '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0) - '@emotion/utils': 1.2.1 - '@emotion/weak-memoize': 0.3.1 - '@types/react': 18.2.6 - hoist-non-react-statics: 3.3.2 - react: 18.2.0 - dev: false - - /@emotion/serialize@1.1.2: - resolution: {integrity: sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==} - dependencies: - '@emotion/hash': 0.9.1 - '@emotion/memoize': 0.8.1 - '@emotion/unitless': 0.8.1 - '@emotion/utils': 1.2.1 - csstype: 3.1.2 - dev: false - - /@emotion/sheet@1.2.2: - resolution: {integrity: sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==} - dev: false - - /@emotion/styled@11.11.0(@emotion/react@11.11.0)(@types/react@18.2.6)(react@18.2.0): - resolution: {integrity: sha512-hM5Nnvu9P3midq5aaXj4I+lnSfNi7Pmd4EWk1fOZ3pxookaQTNew6bp4JaCBYM4HVFZF9g7UjJmsUmC2JlxOng==} - peerDependencies: - '@emotion/react': ^11.0.0-rc.0 - '@types/react': '*' - react: '>=16.8.0' - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.21.5 - '@emotion/babel-plugin': 11.11.0 - '@emotion/is-prop-valid': 1.2.1 - '@emotion/react': 11.11.0(@types/react@18.2.6)(react@18.2.0) - '@emotion/serialize': 1.1.2 - '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0) - '@emotion/utils': 1.2.1 - '@types/react': 18.2.6 - react: 18.2.0 - dev: false - - /@emotion/unitless@0.8.1: - resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==} - dev: false - - /@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@18.2.0): - resolution: {integrity: sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==} - peerDependencies: - react: '>=16.8.0' - dependencies: - react: 18.2.0 - dev: false - - /@emotion/utils@1.2.1: - resolution: {integrity: sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==} - dev: false - - /@emotion/weak-memoize@0.3.1: - resolution: {integrity: sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==} - dev: false - - /@esbuild/android-arm64@0.17.19: - resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: true - optional: true - - /@esbuild/android-arm@0.17.19: - resolution: {integrity: sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - requiresBuild: true - dev: true - optional: true - - /@esbuild/android-x64@0.17.19: - resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - requiresBuild: true - dev: true - optional: true - - /@esbuild/darwin-arm64@0.17.19: - resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /@esbuild/darwin-x64@0.17.19: - resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /@esbuild/freebsd-arm64@0.17.19: - resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/freebsd-x64@0.17.19: - resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-arm64@0.17.19: - resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-arm@0.17.19: - resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-ia32@0.17.19: - resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-loong64@0.17.19: - resolution: {integrity: sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-mips64el@0.17.19: - resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-ppc64@0.17.19: - resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-riscv64@0.17.19: - resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-s390x@0.17.19: - resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/linux-x64@0.17.19: - resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@esbuild/netbsd-x64@0.17.19: - resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/openbsd-x64@0.17.19: - resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - requiresBuild: true - dev: true - optional: true - - /@esbuild/sunos-x64@0.17.19: - resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - requiresBuild: true - dev: true - optional: true - - /@esbuild/win32-arm64@0.17.19: - resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@esbuild/win32-ia32@0.17.19: - resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@esbuild/win32-x64@0.17.19: - resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@eslint-community/eslint-utils@4.4.0(eslint@8.40.0): - resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - dependencies: - eslint: 8.40.0 - eslint-visitor-keys: 3.4.1 - dev: true - - /@eslint-community/regexpp@4.5.1: - resolution: {integrity: sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - dev: true - - /@eslint/eslintrc@2.0.3: - resolution: {integrity: sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - ajv: 6.12.6 - debug: 4.3.4 - espree: 9.5.2 - globals: 13.20.0 - ignore: 5.2.4 - import-fresh: 3.3.0 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - dev: true - - /@eslint/js@8.40.0: - resolution: {integrity: sha512-ElyB54bJIhXQYVKjDSvCkPO1iU1tSAeVQJbllWJq1XQSmmA4dgFk8CbiBGpiOPxleE48vDogxCtmMYku4HSVLA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true - - /@floating-ui/core@1.5.0: - resolution: {integrity: sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==} - dependencies: - '@floating-ui/utils': 0.1.6 - dev: false - - /@floating-ui/dom@1.5.3: - resolution: {integrity: sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==} - dependencies: - '@floating-ui/core': 1.5.0 - '@floating-ui/utils': 0.1.6 - dev: false - - /@floating-ui/react-dom@2.0.4(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-CF8k2rgKeh/49UrnIBs4BdxPUV6vize/Db1d/YbCLyp9GiVZ0BEwf5AiDSxJRCr6yOkGqTFHtmrULxkEfYZ7dQ==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - dependencies: - '@floating-ui/dom': 1.5.3 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@floating-ui/utils@0.1.6: - resolution: {integrity: sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==} - dev: false - - /@humanwhocodes/config-array@0.11.8: - resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==} - engines: {node: '>=10.10.0'} - dependencies: - '@humanwhocodes/object-schema': 1.2.1 - debug: 4.3.4 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - dev: true - - /@humanwhocodes/module-importer@1.0.1: - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - dev: true - - /@humanwhocodes/object-schema@1.2.1: - resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} - dev: true - - /@icons/material@0.2.4(react@18.2.0): - resolution: {integrity: sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==} - peerDependencies: - react: '*' - dependencies: - react: 18.2.0 - dev: false - - /@istanbuljs/load-nyc-config@1.1.0: - resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} - engines: {node: '>=8'} - dependencies: - camelcase: 5.3.1 - find-up: 4.1.0 - get-package-type: 0.1.0 - js-yaml: 3.14.1 - resolve-from: 5.0.0 - - /@istanbuljs/schema@0.1.3: - resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} - engines: {node: '>=8'} - - /@jest/console@29.5.0: - resolution: {integrity: sha512-NEpkObxPwyw/XxZVLPmAGKE89IQRp4puc6IQRPru6JKd1M3fW9v1xM1AnzIJE65hbCkzQAdnL8P47e9hzhiYLQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.5.0 - '@types/node': 18.16.9 - chalk: 4.1.2 - jest-message-util: 29.5.0 - jest-util: 29.5.0 - slash: 3.0.0 - - /@jest/core@29.5.0: - resolution: {integrity: sha512-28UzQc7ulUrOQw1IsN/kv1QES3q2kkbl/wGslyhAclqZ/8cMdB5M68BffkIdSJgKBUt50d3hbwJ92XESlE7LiQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - dependencies: - '@jest/console': 29.5.0 - '@jest/reporters': 29.5.0 - '@jest/test-result': 29.5.0 - '@jest/transform': 29.5.0 - '@jest/types': 29.5.0 - '@types/node': 18.16.9 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - ci-info: 3.8.0 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-changed-files: 29.5.0 - jest-config: 29.5.0(@types/node@18.16.9) - jest-haste-map: 29.5.0 - jest-message-util: 29.5.0 - jest-regex-util: 29.4.3 - jest-resolve: 29.5.0 - jest-resolve-dependencies: 29.5.0 - jest-runner: 29.5.0 - jest-runtime: 29.5.0 - jest-snapshot: 29.5.0 - jest-util: 29.5.0 - jest-validate: 29.5.0 - jest-watcher: 29.5.0 - micromatch: 4.0.5 - pretty-format: 29.5.0 - slash: 3.0.0 - strip-ansi: 6.0.1 - transitivePeerDependencies: - - supports-color - - ts-node - - /@jest/environment@29.5.0: - resolution: {integrity: sha512-5FXw2+wD29YU1d4I2htpRX7jYnAyTRjP2CsXQdo9SAM8g3ifxWPSV0HnClSn71xwctr0U3oZIIH+dtbfmnbXVQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/fake-timers': 29.5.0 - '@jest/types': 29.5.0 - '@types/node': 18.16.9 - jest-mock: 29.5.0 - - /@jest/environment@29.6.4: - resolution: {integrity: sha512-sQ0SULEjA1XUTHmkBRl7A1dyITM9yb1yb3ZNKPX3KlTd6IG7mWUe3e2yfExtC2Zz1Q+mMckOLHmL/qLiuQJrBQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/fake-timers': 29.6.4 - '@jest/types': 29.6.3 - '@types/node': 18.16.9 - jest-mock: 29.6.3 - - /@jest/expect-utils@29.5.0: - resolution: {integrity: sha512-fmKzsidoXQT2KwnrwE0SQq3uj8Z763vzR8LnLBwC2qYWEFpjX8daRsk6rHUM1QvNlEW/UJXNXm59ztmJJWs2Mg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - jest-get-type: 29.4.3 - - /@jest/expect@29.5.0: - resolution: {integrity: sha512-PueDR2HGihN3ciUNGr4uelropW7rqUfTiOn+8u0leg/42UhblPxHkfoh0Ruu3I9Y1962P3u2DY4+h7GVTSVU6g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - expect: 29.5.0 - jest-snapshot: 29.5.0 - transitivePeerDependencies: - - supports-color - - /@jest/fake-timers@29.5.0: - resolution: {integrity: sha512-9ARvuAAQcBwDAqOnglWq2zwNIRUDtk/SCkp/ToGEhFv5r86K21l+VEs0qNTaXtyiY0lEePl3kylijSYJQqdbDg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.5.0 - '@sinonjs/fake-timers': 10.1.0 - '@types/node': 18.16.9 - jest-message-util: 29.5.0 - jest-mock: 29.5.0 - jest-util: 29.5.0 - - /@jest/fake-timers@29.6.4: - resolution: {integrity: sha512-6UkCwzoBK60edXIIWb0/KWkuj7R7Qq91vVInOe3De6DSpaEiqjKcJw4F7XUet24Wupahj9J6PlR09JqJ5ySDHw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.6.3 - '@sinonjs/fake-timers': 10.1.0 - '@types/node': 18.16.9 - jest-message-util: 29.6.3 - jest-mock: 29.6.3 - jest-util: 29.6.3 - - /@jest/globals@29.5.0: - resolution: {integrity: sha512-S02y0qMWGihdzNbUiqSAiKSpSozSuHX5UYc7QbnHP+D9Lyw8DgGGCinrN9uSuHPeKgSSzvPom2q1nAtBvUsvPQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/environment': 29.5.0 - '@jest/expect': 29.5.0 - '@jest/types': 29.5.0 - jest-mock: 29.5.0 - transitivePeerDependencies: - - supports-color - - /@jest/reporters@29.5.0: - resolution: {integrity: sha512-D05STXqj/M8bP9hQNSICtPqz97u7ffGzZu+9XLucXhkOFBqKcXe04JLZOgIekOxdb73MAoBUFnqvf7MCpKk5OA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - dependencies: - '@bcoe/v8-coverage': 0.2.3 - '@jest/console': 29.5.0 - '@jest/test-result': 29.5.0 - '@jest/transform': 29.5.0 - '@jest/types': 29.5.0 - '@jridgewell/trace-mapping': 0.3.18 - '@types/node': 18.16.9 - chalk: 4.1.2 - collect-v8-coverage: 1.0.1 - exit: 0.1.2 - glob: 7.2.3 - graceful-fs: 4.2.11 - istanbul-lib-coverage: 3.2.0 - istanbul-lib-instrument: 5.2.1 - istanbul-lib-report: 3.0.0 - istanbul-lib-source-maps: 4.0.1 - istanbul-reports: 3.1.5 - jest-message-util: 29.5.0 - jest-util: 29.5.0 - jest-worker: 29.5.0 - slash: 3.0.0 - string-length: 4.0.2 - strip-ansi: 6.0.1 - v8-to-istanbul: 9.1.0 - transitivePeerDependencies: - - supports-color - - /@jest/schemas@29.4.3: - resolution: {integrity: sha512-VLYKXQmtmuEz6IxJsrZwzG9NvtkQsWNnWMsKxqWNu3+CnfzJQhp0WDDKWLVV9hLKr0l3SLLFRqcYHjhtyuDVxg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@sinclair/typebox': 0.25.24 - - /@jest/schemas@29.6.3: - resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@sinclair/typebox': 0.27.8 - - /@jest/source-map@29.4.3: - resolution: {integrity: sha512-qyt/mb6rLyd9j1jUts4EQncvS6Yy3PM9HghnNv86QBlV+zdL2inCdK1tuVlL+J+lpiw2BI67qXOrX3UurBqQ1w==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jridgewell/trace-mapping': 0.3.18 - callsites: 3.1.0 - graceful-fs: 4.2.11 - - /@jest/test-result@29.5.0: - resolution: {integrity: sha512-fGl4rfitnbfLsrfx1uUpDEESS7zM8JdgZgOCQuxQvL1Sn/I6ijeAVQWGfXI9zb1i9Mzo495cIpVZhA0yr60PkQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/console': 29.5.0 - '@jest/types': 29.5.0 - '@types/istanbul-lib-coverage': 2.0.4 - collect-v8-coverage: 1.0.1 - - /@jest/test-sequencer@29.5.0: - resolution: {integrity: sha512-yPafQEcKjkSfDXyvtgiV4pevSeyuA6MQr6ZIdVkWJly9vkqjnFfcfhRQqpD5whjoU8EORki752xQmjaqoFjzMQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/test-result': 29.5.0 - graceful-fs: 4.2.11 - jest-haste-map: 29.6.4 - slash: 3.0.0 - - /@jest/transform@29.5.0: - resolution: {integrity: sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@babel/core': 7.21.8 - '@jest/types': 29.5.0 - '@jridgewell/trace-mapping': 0.3.18 - babel-plugin-istanbul: 6.1.1 - chalk: 4.1.2 - convert-source-map: 2.0.0 - fast-json-stable-stringify: 2.1.0 - graceful-fs: 4.2.11 - jest-haste-map: 29.5.0 - jest-regex-util: 29.4.3 - jest-util: 29.5.0 - micromatch: 4.0.5 - pirates: 4.0.5 - slash: 3.0.0 - write-file-atomic: 4.0.2 - transitivePeerDependencies: - - supports-color - - /@jest/transform@29.6.4: - resolution: {integrity: sha512-8thgRSiXUqtr/pPGY/OsyHuMjGyhVnWrFAwoxmIemlBuiMyU1WFs0tXoNxzcr4A4uErs/ABre76SGmrr5ab/AA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@babel/core': 7.21.8 - '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.18 - babel-plugin-istanbul: 6.1.1 - chalk: 4.1.2 - convert-source-map: 2.0.0 - fast-json-stable-stringify: 2.1.0 - graceful-fs: 4.2.11 - jest-haste-map: 29.6.4 - jest-regex-util: 29.6.3 - jest-util: 29.6.3 - micromatch: 4.0.5 - pirates: 4.0.5 - slash: 3.0.0 - write-file-atomic: 4.0.2 - transitivePeerDependencies: - - supports-color - - /@jest/types@29.5.0: - resolution: {integrity: sha512-qbu7kN6czmVRc3xWFQcAN03RAUamgppVUdXrvl1Wr3jlNF93o9mJbGcDWrwGB6ht44u7efB1qCFgVQmca24Uog==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/schemas': 29.4.3 - '@types/istanbul-lib-coverage': 2.0.4 - '@types/istanbul-reports': 3.0.1 - '@types/node': 18.16.9 - '@types/yargs': 17.0.24 - chalk: 4.1.2 - - /@jest/types@29.6.3: - resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/schemas': 29.6.3 - '@types/istanbul-lib-coverage': 2.0.4 - '@types/istanbul-reports': 3.0.1 - '@types/node': 18.16.9 - '@types/yargs': 17.0.24 - chalk: 4.1.2 - - /@jridgewell/gen-mapping@0.3.3: - resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} - engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.15 - '@jridgewell/trace-mapping': 0.3.18 - - /@jridgewell/resolve-uri@3.1.0: - resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} - engines: {node: '>=6.0.0'} - - /@jridgewell/set-array@1.1.2: - resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} - engines: {node: '>=6.0.0'} - - /@jridgewell/sourcemap-codec@1.4.14: - resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} - - /@jridgewell/sourcemap-codec@1.4.15: - resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - - /@jridgewell/trace-mapping@0.3.18: - resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==} - dependencies: - '@jridgewell/resolve-uri': 3.1.0 - '@jridgewell/sourcemap-codec': 1.4.14 - - /@jridgewell/trace-mapping@0.3.9: - resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - dependencies: - '@jridgewell/resolve-uri': 3.1.0 - '@jridgewell/sourcemap-codec': 1.4.15 - dev: true - - /@juggle/resize-observer@3.4.0: - resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==} - dev: false - - /@mui/base@5.0.0-beta.0(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-ap+juKvt8R8n3cBqd/pGtZydQ4v2I/hgJKnvJRGjpSh3RvsvnDHO4rXov8MHQlH6VqpOekwgilFLGxMZjNTucA==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.21.5 - '@emotion/is-prop-valid': 1.2.1 - '@mui/types': 7.2.4(@types/react@18.2.6) - '@mui/utils': 5.12.3(react@18.2.0) - '@popperjs/core': 2.11.7 - '@types/react': 18.2.6 - clsx: 1.2.1 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-is: 18.2.0 - dev: false - - /@mui/base@5.0.0-beta.24(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-bKt2pUADHGQtqWDZ8nvL2Lvg2GNJyd/ZUgZAJoYzRgmnxBL9j36MSlS3+exEdYkikcnvVafcBtD904RypFKb0w==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.23.4 - '@floating-ui/react-dom': 2.0.4(react-dom@18.2.0)(react@18.2.0) - '@mui/types': 7.2.9(@types/react@18.2.6) - '@mui/utils': 5.14.18(@types/react@18.2.6)(react@18.2.0) - '@popperjs/core': 2.11.8 - '@types/react': 18.2.6 - clsx: 2.0.0 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@mui/core-downloads-tracker@5.13.0: - resolution: {integrity: sha512-5nXz2k8Rv2ZjtQY6kXirJVyn2+ODaQuAJmXSJtLDUQDKWp3PFUj6j3bILqR0JGOs9R5ejgwz3crLKsl6GwjwkQ==} - dev: false - - /@mui/icons-material@5.11.16(@mui/material@5.13.0)(@types/react@18.2.6)(react@18.2.0): - resolution: {integrity: sha512-oKkx9z9Kwg40NtcIajF9uOXhxiyTZrrm9nmIJ4UjkU2IdHpd4QVLbCc/5hZN/y0C6qzi2Zlxyr9TGddQx2vx2A==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@mui/material': ^5.0.0 - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.21.5 - '@mui/material': 5.13.0(@emotion/react@11.11.0)(@emotion/styled@11.11.0)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) - '@types/react': 18.2.6 - react: 18.2.0 - dev: false - - /@mui/material@5.13.0(@emotion/react@11.11.0)(@emotion/styled@11.11.0)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-ckS+9tCpAzpdJdaTF+btF0b6mF9wbXg/EVKtnoAWYi0UKXoXBAVvEUMNpLGA5xdpCdf+A6fPbVUEHs9TsfU+Yw==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@emotion/react': ^11.5.0 - '@emotion/styled': ^11.3.0 - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.21.5 - '@emotion/react': 11.11.0(@types/react@18.2.6)(react@18.2.0) - '@emotion/styled': 11.11.0(@emotion/react@11.11.0)(@types/react@18.2.6)(react@18.2.0) - '@mui/base': 5.0.0-beta.0(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) - '@mui/core-downloads-tracker': 5.13.0 - '@mui/system': 5.14.4(@emotion/react@11.11.0)(@emotion/styled@11.11.0)(@types/react@18.2.6)(react@18.2.0) - '@mui/types': 7.2.4(@types/react@18.2.6) - '@mui/utils': 5.12.3(react@18.2.0) - '@types/react': 18.2.6 - '@types/react-transition-group': 4.4.6 - clsx: 1.2.1 - csstype: 3.1.2 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-is: 18.2.0 - react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0) - dev: false - - /@mui/private-theming@5.14.4(@types/react@18.2.6)(react@18.2.0): - resolution: {integrity: sha512-ISXsHDiQ3z1XA4IuKn+iXDWvDjcz/UcQBiFZqtdoIsEBt8CB7wgdQf3LwcwqO81dl5ofg/vNQBEnXuKfZHrnYA==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.22.10 - '@mui/utils': 5.14.4(react@18.2.0) - '@types/react': 18.2.6 - prop-types: 15.8.1 - react: 18.2.0 - dev: false - - /@mui/styled-engine@5.13.2(@emotion/react@11.11.0)(@emotion/styled@11.11.0)(react@18.2.0): - resolution: {integrity: sha512-VCYCU6xVtXOrIN8lcbuPmoG+u7FYuOERG++fpY74hPpEWkyFQG97F+/XfTQVYzlR2m7nPjnwVUgATcTCMEaMvw==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@emotion/react': ^11.4.1 - '@emotion/styled': ^11.3.0 - react: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - dependencies: - '@babel/runtime': 7.22.10 - '@emotion/cache': 11.11.0 - '@emotion/react': 11.11.0(@types/react@18.2.6)(react@18.2.0) - '@emotion/styled': 11.11.0(@emotion/react@11.11.0)(@types/react@18.2.6)(react@18.2.0) - csstype: 3.1.2 - prop-types: 15.8.1 - react: 18.2.0 - dev: false - - /@mui/system@5.14.4(@emotion/react@11.11.0)(@emotion/styled@11.11.0)(@types/react@18.2.6)(react@18.2.0): - resolution: {integrity: sha512-oPgfWS97QNfHcDBapdkZIs4G5i85BJt69Hp6wbXF6s7vi3Evcmhdk8AbCRW6n0sX4vTj8oe0mh0RIm1G2A1KDA==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@emotion/react': ^11.5.0 - '@emotion/styled': ^11.3.0 - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.22.10 - '@emotion/react': 11.11.0(@types/react@18.2.6)(react@18.2.0) - '@emotion/styled': 11.11.0(@emotion/react@11.11.0)(@types/react@18.2.6)(react@18.2.0) - '@mui/private-theming': 5.14.4(@types/react@18.2.6)(react@18.2.0) - '@mui/styled-engine': 5.13.2(@emotion/react@11.11.0)(@emotion/styled@11.11.0)(react@18.2.0) - '@mui/types': 7.2.4(@types/react@18.2.6) - '@mui/utils': 5.14.4(react@18.2.0) - '@types/react': 18.2.6 - clsx: 2.0.0 - csstype: 3.1.2 - prop-types: 15.8.1 - react: 18.2.0 - dev: false - - /@mui/types@7.2.4(@types/react@18.2.6): - resolution: {integrity: sha512-LBcwa8rN84bKF+f5sDyku42w1NTxaPgPyYKODsh01U1fVstTClbUoSA96oyRBnSNyEiAVjKm6Gwx9vjR+xyqHA==} - peerDependencies: - '@types/react': '*' - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@types/react': 18.2.6 - dev: false - - /@mui/types@7.2.9(@types/react@18.2.6): - resolution: {integrity: sha512-k1lN/PolaRZfNsRdAqXtcR71sTnv3z/VCCGPxU8HfdftDkzi335MdJ6scZxvofMAd/K/9EbzCZTFBmlNpQVdCg==} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@types/react': 18.2.6 - dev: false - - /@mui/utils@5.12.3(react@18.2.0): - resolution: {integrity: sha512-D/Z4Ub3MRl7HiUccid7sQYclTr24TqUAQFFlxHQF8FR177BrCTQ0JJZom7EqYjZCdXhwnSkOj2ph685MSKNtIA==} - engines: {node: '>=12.0.0'} - peerDependencies: - react: ^17.0.0 || ^18.0.0 - dependencies: - '@babel/runtime': 7.21.5 - '@types/prop-types': 15.7.5 - '@types/react-is': 17.0.4 - prop-types: 15.8.1 - react: 18.2.0 - react-is: 18.2.0 - dev: false - - /@mui/utils@5.14.18(@types/react@18.2.6)(react@18.2.0): - resolution: {integrity: sha512-HZDRsJtEZ7WMSnrHV9uwScGze4wM/Y+u6pDVo+grUjt5yXzn+wI8QX/JwTHh9YSw/WpnUL80mJJjgCnWj2VrzQ==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.23.4 - '@types/prop-types': 15.7.11 - '@types/react': 18.2.6 - prop-types: 15.8.1 - react: 18.2.0 - react-is: 18.2.0 - dev: false - - /@mui/utils@5.14.4(react@18.2.0): - resolution: {integrity: sha512-4ANV0txPD3x0IcTCSEHKDWnsutg1K3m6Vz5IckkbLXVYu17oOZCVUdOKsb/txUmaCd0v0PmSRe5PW+Mlvns5dQ==} - engines: {node: '>=12.0.0'} - peerDependencies: - react: ^17.0.0 || ^18.0.0 - dependencies: - '@babel/runtime': 7.22.10 - '@types/prop-types': 15.7.5 - '@types/react-is': 18.2.1 - prop-types: 15.8.1 - react: 18.2.0 - react-is: 18.2.0 - dev: false - - /@mui/x-date-pickers-pro@6.18.2(@emotion/react@11.11.0)(@emotion/styled@11.11.0)(@mui/material@5.13.0)(@mui/system@5.14.4)(@types/react@18.2.6)(dayjs@1.11.9)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-8lEVEOtCQssKWel4Ey1pRulGPXUQ73TnkHKzHWsjdv03FjiUs3eYB+Ej0Uk5yWPmsqlShWhOzOlOGDpzsYJsUg==} - engines: {node: '>=14.0.0'} - peerDependencies: - '@emotion/react': ^11.9.0 - '@emotion/styled': ^11.8.1 - '@mui/material': ^5.8.6 - '@mui/system': ^5.8.0 - date-fns: ^2.25.0 - date-fns-jalali: ^2.13.0-0 - dayjs: ^1.10.7 - luxon: ^3.0.2 - moment: ^2.29.4 - moment-hijri: ^2.1.2 - moment-jalaali: ^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0 - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - date-fns: - optional: true - date-fns-jalali: - optional: true - dayjs: - optional: true - luxon: - optional: true - moment: - optional: true - moment-hijri: - optional: true - moment-jalaali: - optional: true - dependencies: - '@babel/runtime': 7.23.4 - '@emotion/react': 11.11.0(@types/react@18.2.6)(react@18.2.0) - '@emotion/styled': 11.11.0(@emotion/react@11.11.0)(@types/react@18.2.6)(react@18.2.0) - '@mui/base': 5.0.0-beta.24(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) - '@mui/material': 5.13.0(@emotion/react@11.11.0)(@emotion/styled@11.11.0)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) - '@mui/system': 5.14.4(@emotion/react@11.11.0)(@emotion/styled@11.11.0)(@types/react@18.2.6)(react@18.2.0) - '@mui/utils': 5.14.18(@types/react@18.2.6)(react@18.2.0) - '@mui/x-date-pickers': 6.18.2(@emotion/react@11.11.0)(@emotion/styled@11.11.0)(@mui/material@5.13.0)(@mui/system@5.14.4)(@types/react@18.2.6)(dayjs@1.11.9)(react-dom@18.2.0)(react@18.2.0) - '@mui/x-license-pro': 6.10.2(@types/react@18.2.6)(react@18.2.0) - clsx: 2.0.0 - dayjs: 1.11.9 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0) - transitivePeerDependencies: - - '@types/react' - dev: false - - /@mui/x-date-pickers@6.18.2(@emotion/react@11.11.0)(@emotion/styled@11.11.0)(@mui/material@5.13.0)(@mui/system@5.14.4)(@types/react@18.2.6)(dayjs@1.11.9)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-HJq4uoFQSu5isa/mesWw2BKh8KBRYUQb+KaSlVlWfJNgP3YhPvWZ6yqCNYyxOAiPMxb0n3nBjS9ErO27OHjFMA==} - engines: {node: '>=14.0.0'} - peerDependencies: - '@emotion/react': ^11.9.0 - '@emotion/styled': ^11.8.1 - '@mui/material': ^5.8.6 - '@mui/system': ^5.8.0 - date-fns: ^2.25.0 - date-fns-jalali: ^2.13.0-0 - dayjs: ^1.10.7 - luxon: ^3.0.2 - moment: ^2.29.4 - moment-hijri: ^2.1.2 - moment-jalaali: ^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0 - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - date-fns: - optional: true - date-fns-jalali: - optional: true - dayjs: - optional: true - luxon: - optional: true - moment: - optional: true - moment-hijri: - optional: true - moment-jalaali: - optional: true - dependencies: - '@babel/runtime': 7.23.4 - '@emotion/react': 11.11.0(@types/react@18.2.6)(react@18.2.0) - '@emotion/styled': 11.11.0(@emotion/react@11.11.0)(@types/react@18.2.6)(react@18.2.0) - '@mui/base': 5.0.0-beta.24(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) - '@mui/material': 5.13.0(@emotion/react@11.11.0)(@emotion/styled@11.11.0)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) - '@mui/system': 5.14.4(@emotion/react@11.11.0)(@emotion/styled@11.11.0)(@types/react@18.2.6)(react@18.2.0) - '@mui/utils': 5.14.18(@types/react@18.2.6)(react@18.2.0) - '@types/react-transition-group': 4.4.9 - clsx: 2.0.0 - dayjs: 1.11.9 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0) - transitivePeerDependencies: - - '@types/react' - dev: false - - /@mui/x-license-pro@6.10.2(@types/react@18.2.6)(react@18.2.0): - resolution: {integrity: sha512-Baw3shilU+eHgU+QYKNPFUKvfS5rSyNJ98pQx02E0gKA22hWp/XAt88K1qUfUMPlkPpvg/uci6gviQSSLZkuKw==} - engines: {node: '>=14.0.0'} - peerDependencies: - react: ^17.0.0 || ^18.0.0 - dependencies: - '@babel/runtime': 7.23.4 - '@mui/utils': 5.14.18(@types/react@18.2.6)(react@18.2.0) - react: 18.2.0 - transitivePeerDependencies: - - '@types/react' - dev: false - - /@nodelib/fs.scandir@2.1.5: - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - dev: true - - /@nodelib/fs.stat@2.0.5: - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - dev: true - - /@nodelib/fs.walk@1.2.8: - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.15.0 - dev: true - - /@popperjs/core@2.11.7: - resolution: {integrity: sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==} - dev: false - - /@popperjs/core@2.11.8: - resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} - - /@reduxjs/toolkit@2.0.0(react-redux@8.0.5)(react@18.2.0): - resolution: {integrity: sha512-Kq/a+aO28adYdPoNEu9p800MYPKoUc0tlkYfv035Ief9J7MPq8JvmT7UdpYhvXsoMtOdt567KwZjc9H3Rf8yjg==} - peerDependencies: - react: ^16.9.0 || ^17.0.0 || ^18 - react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 - peerDependenciesMeta: - react: - optional: true - react-redux: - optional: true - dependencies: - immer: 10.0.3 - react: 18.2.0 - react-redux: 8.0.5(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1) - redux: 5.0.0 - redux-thunk: 3.1.0(redux@5.0.0) - reselect: 5.0.1 - dev: false - - /@remix-run/router@1.6.1: - resolution: {integrity: sha512-YUkWj+xs0oOzBe74OgErsuR3wVn+efrFhXBWrit50kOiED+pvQe2r6MWY0iJMQU/mSVKxvNzL4ZaYvjdX+G7ZA==} - engines: {node: '>=14'} - dev: false - - /@restart/hooks@0.4.15(react@18.2.0): - resolution: {integrity: sha512-cZFXYTxbpzYcieq/mBwSyXgqnGMHoBVh3J7MU0CCoIB4NRZxV9/TuwTBAaLMqpNhC3zTPMCgkQ5Ey07L02Xmcw==} - peerDependencies: - react: '>=16.8.0' - dependencies: - dequal: 2.0.3 - react: 18.2.0 - dev: false - - /@rollup/pluginutils@5.0.2: - resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0 - peerDependenciesMeta: - rollup: - optional: true - dependencies: - '@types/estree': 1.0.1 - estree-walker: 2.0.2 - picomatch: 2.3.1 - dev: true - - /@sinclair/typebox@0.25.24: - resolution: {integrity: sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==} - - /@sinclair/typebox@0.27.8: - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - - /@sinonjs/commons@3.0.0: - resolution: {integrity: sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==} - dependencies: - type-detect: 4.0.8 - - /@sinonjs/fake-timers@10.1.0: - resolution: {integrity: sha512-w1qd368vtrwttm1PRJWPW1QHlbmHrVDGs1eBH/jZvRPUFS4MNXV9Q33EQdjOdeAxZ7O8+3wM7zxztm2nfUSyKw==} - dependencies: - '@sinonjs/commons': 3.0.0 - - /@slate-yjs/core@1.0.2(slate@0.101.4)(yjs@13.6.1): - resolution: {integrity: sha512-X0hLFJbQu9c1ItWBaNuEn0pqcXYK76KCp8C4Gvy/VaTQVMo1VgAb2WiiJ0Je/AyuIYEPPSTNVOcyrGHwgA7e6Q==} - peerDependencies: - slate: '>=0.70.0' - yjs: ^13.5.29 - dependencies: - slate: 0.101.4 - y-protocols: 1.0.6(yjs@13.6.1) - yjs: 13.6.1 - dev: false - - /@svgr/babel-plugin-add-jsx-attribute@7.0.0(@babel/core@7.21.8): - resolution: {integrity: sha512-khWbXesWIP9v8HuKCl2NU2HNAyqpSQ/vkIl36Nbn4HIwEYSRWL0H7Gs6idJdha2DkpFDWlsqMELvoCE8lfFY6Q==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.21.8 - dev: true - - /@svgr/babel-plugin-remove-jsx-attribute@7.0.0(@babel/core@7.21.8): - resolution: {integrity: sha512-iiZaIvb3H/c7d3TH2HBeK91uI2rMhZNwnsIrvd7ZwGLkFw6mmunOCoVnjdYua662MqGFxlN9xTq4fv9hgR4VXQ==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.21.8 - dev: true - - /@svgr/babel-plugin-remove-jsx-empty-expression@7.0.0(@babel/core@7.21.8): - resolution: {integrity: sha512-sQQmyo+qegBx8DfFc04PFmIO1FP1MHI1/QEpzcIcclo5OAISsOJPW76ZIs0bDyO/DBSJEa/tDa1W26pVtt0FRw==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.21.8 - dev: true - - /@svgr/babel-plugin-replace-jsx-attribute-value@7.0.0(@babel/core@7.21.8): - resolution: {integrity: sha512-i6MaAqIZXDOJeikJuzocByBf8zO+meLwfQ/qMHIjCcvpnfvWf82PFvredEZElErB5glQFJa2KVKk8N2xV6tRRA==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.21.8 - dev: true - - /@svgr/babel-plugin-svg-dynamic-title@7.0.0(@babel/core@7.21.8): - resolution: {integrity: sha512-BoVSh6ge3SLLpKC0pmmN9DFlqgFy4NxNgdZNLPNJWBUU7TQpDWeBuyVuDW88iXydb5Cv0ReC+ffa5h3VrKfk1w==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.21.8 - dev: true - - /@svgr/babel-plugin-svg-em-dimensions@7.0.0(@babel/core@7.21.8): - resolution: {integrity: sha512-tNDcBa+hYn0gO+GkP/AuNKdVtMufVhU9fdzu+vUQsR18RIJ9RWe7h/pSBY338RO08wArntwbDk5WhQBmhf2PaA==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.21.8 - dev: true - - /@svgr/babel-plugin-transform-react-native-svg@7.0.0(@babel/core@7.21.8): - resolution: {integrity: sha512-qw54u8ljCJYL2KtBOjI5z7Nzg8LnSvQOP5hPKj77H4VQL4+HdKbAT5pnkkZLmHKYwzsIHSYKXxHouD8zZamCFQ==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.21.8 - dev: true - - /@svgr/babel-plugin-transform-svg-component@7.0.0(@babel/core@7.21.8): - resolution: {integrity: sha512-CcFECkDj98daOg9jE3Bh3uyD9kzevCAnZ+UtzG6+BQG/jOQ2OA3jHnX6iG4G1MCJkUQFnUvEv33NvQfqrb/F3A==} - engines: {node: '>=12'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.21.8 - dev: true - - /@svgr/babel-preset@7.0.0(@babel/core@7.21.8): - resolution: {integrity: sha512-EX/NHeFa30j5UjldQGVQikuuQNHUdGmbh9kEpBKofGUtF0GUPJ4T4rhoYiqDAOmBOxojyot36JIFiDUHUK1ilQ==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.21.8 - '@svgr/babel-plugin-add-jsx-attribute': 7.0.0(@babel/core@7.21.8) - '@svgr/babel-plugin-remove-jsx-attribute': 7.0.0(@babel/core@7.21.8) - '@svgr/babel-plugin-remove-jsx-empty-expression': 7.0.0(@babel/core@7.21.8) - '@svgr/babel-plugin-replace-jsx-attribute-value': 7.0.0(@babel/core@7.21.8) - '@svgr/babel-plugin-svg-dynamic-title': 7.0.0(@babel/core@7.21.8) - '@svgr/babel-plugin-svg-em-dimensions': 7.0.0(@babel/core@7.21.8) - '@svgr/babel-plugin-transform-react-native-svg': 7.0.0(@babel/core@7.21.8) - '@svgr/babel-plugin-transform-svg-component': 7.0.0(@babel/core@7.21.8) - dev: true - - /@svgr/core@7.0.0: - resolution: {integrity: sha512-ztAoxkaKhRVloa3XydohgQQCb0/8x9T63yXovpmHzKMkHO6pkjdsIAWKOS4bE95P/2quVh1NtjSKlMRNzSBffw==} - engines: {node: '>=14'} - dependencies: - '@babel/core': 7.21.8 - '@svgr/babel-preset': 7.0.0(@babel/core@7.21.8) - camelcase: 6.3.0 - cosmiconfig: 8.2.0 - transitivePeerDependencies: - - supports-color - dev: true - - /@svgr/hast-util-to-babel-ast@7.0.0: - resolution: {integrity: sha512-42Ej9sDDEmsJKjrfQ1PHmiDiHagh/u9AHO9QWbeNx4KmD9yS5d1XHmXUNINfUcykAU+4431Cn+k6Vn5mWBYimQ==} - engines: {node: '>=14'} - dependencies: - '@babel/types': 7.21.5 - entities: 4.5.0 - dev: true - - /@svgr/plugin-jsx@7.0.0: - resolution: {integrity: sha512-SWlTpPQmBUtLKxXWgpv8syzqIU8XgFRvyhfkam2So8b3BE0OS0HPe5UfmlJ2KIC+a7dpuuYovPR2WAQuSyMoPw==} - engines: {node: '>=14'} - dependencies: - '@babel/core': 7.21.8 - '@svgr/babel-preset': 7.0.0(@babel/core@7.21.8) - '@svgr/hast-util-to-babel-ast': 7.0.0 - svg-parser: 2.0.4 - transitivePeerDependencies: - - supports-color - dev: true - - /@svgr/plugin-svgo@8.0.1(@svgr/core@7.0.0): - resolution: {integrity: sha512-29OJ1QmJgnohQHDAgAuY2h21xWD6TZiXji+hnx+W635RiXTAlHTbjrZDktfqzkN0bOeQEtNe+xgq73/XeWFfSg==} - engines: {node: '>=14'} - peerDependencies: - '@svgr/core': '*' - dependencies: - '@svgr/core': 7.0.0 - cosmiconfig: 8.2.0 - deepmerge: 4.3.1 - svgo: 3.0.2 - dev: true - - /@tauri-apps/api@1.3.0: - resolution: {integrity: sha512-AH+3FonkKZNtfRtGrObY38PrzEj4d+1emCbwNGu0V2ENbXjlLHMZQlUh+Bhu/CRmjaIwZMGJ3yFvWaZZgTHoog==} - engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'} - dev: false - - /@tauri-apps/cli-darwin-arm64@1.5.6: - resolution: {integrity: sha512-NNvG3XLtciCMsBahbDNUEvq184VZmOveTGOuy0So2R33b/6FDkuWaSgWZsR1mISpOuP034htQYW0VITCLelfqg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /@tauri-apps/cli-darwin-x64@1.5.6: - resolution: {integrity: sha512-nkiqmtUQw3N1j4WoVjv81q6zWuZFhBLya/RNGUL94oafORloOZoSY0uTZJAoeieb3Y1YK0rCHSDl02MyV2Fi4A==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /@tauri-apps/cli-linux-arm-gnueabihf@1.5.6: - resolution: {integrity: sha512-z6SPx+axZexmWXTIVPNs4Tg7FtvdJl9EKxYN6JPjOmDZcqA13iyqWBQal2DA/GMZ1Xqo3vyJf6EoEaKaliymPQ==} - engines: {node: '>= 10'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@tauri-apps/cli-linux-arm64-gnu@1.5.6: - resolution: {integrity: sha512-QuQjMQmpsCbzBrmtQiG4uhnfAbdFx3nzm+9LtqjuZlurc12+Mj5MTgqQ3AOwQedH3f7C+KlvbqD2AdXpwTg7VA==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@tauri-apps/cli-linux-arm64-musl@1.5.6: - resolution: {integrity: sha512-8j5dH3odweFeom7bRGlfzDApWVOT4jIq8/214Wl+JeiNVehouIBo9lZGeghZBH3XKFRwEvU23i7sRVjuh2s8mg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@tauri-apps/cli-linux-x64-gnu@1.5.6: - resolution: {integrity: sha512-gbFHYHfdEGW0ffk8SigDsoXks6USpilF6wR0nqB/JbWzbzFR/sBuLVNQlJl1RKNakyJHu+lsFxGy0fcTdoX8xA==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@tauri-apps/cli-linux-x64-musl@1.5.6: - resolution: {integrity: sha512-9v688ogoLkeFYQNgqiSErfhTreLUd8B3prIBSYUt+x4+5Kcw91zWvIh+VSxL1n3KCGGsM7cuXhkGPaxwlEh1ug==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@tauri-apps/cli-win32-arm64-msvc@1.5.6: - resolution: {integrity: sha512-DRNDXFNZb6y5IZrw+lhTTA9l4wbzO4TNRBAlHAiXUrH+pRFZ/ZJtv5WEuAj9ocVSahVw2NaK5Yaold4NPAxHog==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@tauri-apps/cli-win32-ia32-msvc@1.5.6: - resolution: {integrity: sha512-oUYKNR/IZjF4fsOzRpw0xesl2lOjhsQEyWlgbpT25T83EU113Xgck9UjtI7xemNI/OPCv1tPiaM1e7/ABdg5iA==} - engines: {node: '>= 10'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@tauri-apps/cli-win32-x64-msvc@1.5.6: - resolution: {integrity: sha512-RmEf1os9C8//uq2hbjXi7Vgz9ne7798ZxqemAZdUwo1pv3oLVZSz1/IvZmUHPdy2e6zSeySqWu1D0Y3QRNN+dg==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@tauri-apps/cli@1.5.6: - resolution: {integrity: sha512-k4Y19oVCnt7WZb2TnDzLqfs7o98Jq0tUoVMv+JQSzuRDJqaVu2xMBZ8dYplEn+EccdR5SOMyzaLBJWu38TVK1A==} - engines: {node: '>= 10'} - hasBin: true - optionalDependencies: - '@tauri-apps/cli-darwin-arm64': 1.5.6 - '@tauri-apps/cli-darwin-x64': 1.5.6 - '@tauri-apps/cli-linux-arm-gnueabihf': 1.5.6 - '@tauri-apps/cli-linux-arm64-gnu': 1.5.6 - '@tauri-apps/cli-linux-arm64-musl': 1.5.6 - '@tauri-apps/cli-linux-x64-gnu': 1.5.6 - '@tauri-apps/cli-linux-x64-musl': 1.5.6 - '@tauri-apps/cli-win32-arm64-msvc': 1.5.6 - '@tauri-apps/cli-win32-ia32-msvc': 1.5.6 - '@tauri-apps/cli-win32-x64-msvc': 1.5.6 - dev: true - - /@tootallnate/once@2.0.0: - resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} - engines: {node: '>= 10'} - dev: true - - /@trysound/sax@0.2.0: - resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} - engines: {node: '>=10.13.0'} - dev: true - - /@tsconfig/node10@1.0.9: - resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} - dev: true - - /@tsconfig/node12@1.0.11: - resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} - dev: true - - /@tsconfig/node14@1.0.3: - resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} - dev: true - - /@tsconfig/node16@1.0.4: - resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - dev: true - - /@types/babel__core@7.20.0: - resolution: {integrity: sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==} - dependencies: - '@babel/parser': 7.21.8 - '@babel/types': 7.21.5 - '@types/babel__generator': 7.6.4 - '@types/babel__template': 7.4.1 - '@types/babel__traverse': 7.18.5 - - /@types/babel__generator@7.6.4: - resolution: {integrity: sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==} - dependencies: - '@babel/types': 7.21.5 - - /@types/babel__template@7.4.1: - resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==} - dependencies: - '@babel/parser': 7.21.8 - '@babel/types': 7.21.5 - - /@types/babel__traverse@7.18.5: - resolution: {integrity: sha512-enCvTL8m/EHS/zIvJno9nE+ndYPh1/oNFzRYRmtUqJICG2VnCSBzMLW5VN2KCQU91f23tsNKR8v7VJJQMatl7Q==} - dependencies: - '@babel/types': 7.21.5 - - /@types/estree@1.0.1: - resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==} - dev: true - - /@types/google-protobuf@3.15.12: - resolution: {integrity: sha512-40um9QqwHjRS92qnOaDpL7RmDK15NuZYo9HihiJRbYkMQZlWnuH8AdvbMy8/o6lgLmKbDUKa+OALCltHdbOTpQ==} - dev: true - - /@types/graceful-fs@4.1.6: - resolution: {integrity: sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==} - dependencies: - '@types/node': 18.16.9 - - /@types/hoist-non-react-statics@3.3.1: - resolution: {integrity: sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==} - dependencies: - '@types/react': 18.2.6 - hoist-non-react-statics: 3.3.2 - dev: false - - /@types/is-hotkey@0.1.10: - resolution: {integrity: sha512-RvC8KMw5BCac1NvRRyaHgMMEtBaZ6wh0pyPTBu7izn4Sj/AX9Y4aXU5c7rX8PnM/knsuUpC1IeoBkANtxBypsQ==} - dev: false - - /@types/is-hotkey@0.1.7: - resolution: {integrity: sha512-yB5C7zcOM7idwYZZ1wKQ3pTfjA9BbvFqRWvKB46GFddxnJtHwi/b9y84ykQtxQPg5qhdpg4Q/kWU3EGoCTmLzQ==} - dev: true - - /@types/istanbul-lib-coverage@2.0.4: - resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==} - - /@types/istanbul-lib-report@3.0.0: - resolution: {integrity: sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==} - dependencies: - '@types/istanbul-lib-coverage': 2.0.4 - - /@types/istanbul-reports@3.0.1: - resolution: {integrity: sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==} - dependencies: - '@types/istanbul-lib-report': 3.0.0 - - /@types/jest@29.5.3: - resolution: {integrity: sha512-1Nq7YrO/vJE/FYnqYyw0FS8LdrjExSgIiHyKg7xPpn+yi8Q4huZryKnkJatN1ZRH89Kw2v33/8ZMB7DuZeSLlA==} - dependencies: - expect: 29.5.0 - pretty-format: 29.5.0 - dev: true - - /@types/jsdom@20.0.1: - resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==} - dependencies: - '@types/node': 18.16.9 - '@types/tough-cookie': 4.0.2 - parse5: 7.1.2 - dev: true - - /@types/json-schema@7.0.11: - resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} - dev: true - - /@types/katex@0.16.0: - resolution: {integrity: sha512-hz+S3nV6Mym5xPbT9fnO8dDhBFQguMYpY0Ipxv06JMi1ORgnEM4M1ymWDUhUNer3ElLmT583opRo4RzxKmh9jw==} - dev: true - - /@types/lodash-es@4.17.11: - resolution: {integrity: sha512-eCw8FYAWHt2DDl77s+AMLLzPn310LKohruumpucZI4oOFJkIgnlaJcy23OKMJxx4r9PeTF13Gv6w+jqjWQaYUg==} - dependencies: - '@types/lodash': 4.14.194 - dev: true - - /@types/lodash@4.14.194: - resolution: {integrity: sha512-r22s9tAS7imvBt2lyHC9B8AGwWnXaYb1tY09oyLkXDs4vArpYJzw09nj8MLx5VfciBPGIb+ZwG0ssYnEPJxn/g==} - dev: true - - /@types/lodash@4.14.202: - resolution: {integrity: sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==} - dev: false - - /@types/node@18.16.9: - resolution: {integrity: sha512-IeB32oIV4oGArLrd7znD2rkHQ6EDCM+2Sr76dJnrHwv9OHBTTM6nuDLK9bmikXzPa0ZlWMWtRGo/Uw4mrzQedA==} - - /@types/parse-json@4.0.0: - resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==} - dev: false - - /@types/prettier@2.7.2: - resolution: {integrity: sha512-KufADq8uQqo1pYKVIYzfKbJfBAc0sOeXqGbFaSpv8MRmC/zXgowNZmFcbngndGk922QDmOASEXUZCaY48gs4cg==} - - /@types/prismjs@1.26.0: - resolution: {integrity: sha512-ZTaqn/qSqUuAq1YwvOFQfVW1AR/oQJlLSZVustdjwI+GZ8kr0MSHBj0tsXPW1EqHubx50gtBEjbPGsdZwQwCjQ==} - dev: true - - /@types/prop-types@15.7.11: - resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==} - dev: false - - /@types/prop-types@15.7.5: - resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} - - /@types/quill@2.0.10: - resolution: {integrity: sha512-L6OHONEj2v4NRbWQOsn7j1N0SyzhRR3M4g1M6j/uuIwIsIW2ShWHhwbqNvH8hSmVktzqu0lITfdnqVOQ4qkrhA==} - dependencies: - parchment: 1.1.4 - quill-delta: 4.2.2 - dev: true - - /@types/react-beautiful-dnd@13.1.4: - resolution: {integrity: sha512-4bIBdzOr0aavN+88q3C7Pgz+xkb7tz3whORYrmSj77wfVEMfiWiooIwVWFR7KM2e+uGTe5BVrXqSfb0aHeflJA==} - dependencies: - '@types/react': 18.2.6 - dev: true - - /@types/react-color@3.0.6: - resolution: {integrity: sha512-OzPIO5AyRmLA7PlOyISlgabpYUa3En74LP8mTMa0veCA719SvYQov4WLMsHvCgXP+L+KI9yGhYnqZafVGG0P4w==} - dependencies: - '@types/react': 18.2.6 - '@types/reactcss': 1.2.6 - dev: true - - /@types/react-custom-scrollbars@4.0.13: - resolution: {integrity: sha512-t+15reWgAE1jXlrhaZoxjuH/SQf+EG0rzAzSCzTIkSiP5CDT7KhoExNPwIa6uUxtPkjc3gdW/ry7GetLEwCfGA==} - dependencies: - '@types/react': 18.2.6 - dev: true - - /@types/react-datepicker@4.19.3(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-85F9eKWu9fGiD9r4KVVMPYAdkJJswR3Wci9PvqplmB6T+D+VbUqPeKtifg96NZ4nEhufjehW+SX4JLrEWVplWw==} - dependencies: - '@popperjs/core': 2.11.8 - '@types/react': 18.2.6 - date-fns: 2.30.0 - react-popper: 2.3.0(@popperjs/core@2.11.8)(react-dom@18.2.0)(react@18.2.0) - transitivePeerDependencies: - - react - - react-dom - dev: true - - /@types/react-dom@18.2.4: - resolution: {integrity: sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw==} - dependencies: - '@types/react': 18.2.6 - - /@types/react-is@17.0.4: - resolution: {integrity: sha512-FLzd0K9pnaEvKz4D1vYxK9JmgQPiGk1lu23o1kqGsLeT0iPbRSF7b76+S5T9fD8aRa0B8bY7I/3DebEj+1ysBA==} - dependencies: - '@types/react': 17.0.59 - dev: false - - /@types/react-is@18.2.1: - resolution: {integrity: sha512-wyUkmaaSZEzFZivD8F2ftSyAfk6L+DfFliVj/mYdOXbVjRcS87fQJLTnhk6dRZPuJjI+9g6RZJO4PNCngUrmyw==} - dependencies: - '@types/react': 18.2.6 - dev: false - - /@types/react-katex@3.0.0: - resolution: {integrity: sha512-AiHHXh71a2M7Z6z1wj6iA23SkiRF9r0neHUdu8zjU/cT3MyLxDefYHbcceKhV/gjDEZgF3YaiNHyPNtoGUjPvg==} - dependencies: - '@types/react': 18.2.6 - dev: true - - /@types/react-redux@7.1.25: - resolution: {integrity: sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg==} - dependencies: - '@types/hoist-non-react-statics': 3.3.1 - '@types/react': 18.2.6 - hoist-non-react-statics: 3.3.2 - redux: 4.2.1 - dev: false - - /@types/react-swipeable-views@0.13.4: - resolution: {integrity: sha512-hQV9Oq6oa+9HKdnGd43xkckElwf5dThOiegtQxqE7qX761oHhxnZO07fz6IsKSnUy9J3tzlRQBu3sNyvC8+kYw==} - dependencies: - '@types/react': 18.2.6 - dev: false - - /@types/react-transition-group@4.4.6: - resolution: {integrity: sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==} - dependencies: - '@types/react': 18.2.6 - - /@types/react-transition-group@4.4.9: - resolution: {integrity: sha512-ZVNmWumUIh5NhH8aMD9CR2hdW0fNuYInlocZHaZ+dgk/1K49j1w/HoAuK1ki+pgscQrOFRTlXeoURtuzEkV3dg==} - dependencies: - '@types/react': 18.2.6 - dev: false - - /@types/react-window@1.8.8: - resolution: {integrity: sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q==} - dependencies: - '@types/react': 18.2.6 - - /@types/react@17.0.59: - resolution: {integrity: sha512-gSON5zWYIGyoBcycCE75E9+r6dCC2dHdsrVkOEiIYNU5+Q28HcBAuqvDuxHcCbMfHBHdeT5Tva/AFn3rnMKE4g==} - dependencies: - '@types/prop-types': 15.7.5 - '@types/scheduler': 0.16.3 - csstype: 3.1.2 - dev: false - - /@types/react@18.2.6: - resolution: {integrity: sha512-wRZClXn//zxCFW+ye/D2qY65UsYP1Fpex2YXorHc8awoNamkMZSvBxwxdYVInsHOZZd2Ppq8isnSzJL5Mpf8OA==} - dependencies: - '@types/prop-types': 15.7.5 - '@types/scheduler': 0.16.3 - csstype: 3.1.2 - - /@types/reactcss@1.2.6: - resolution: {integrity: sha512-qaIzpCuXNWomGR1Xq8SCFTtF4v8V27Y6f+b9+bzHiv087MylI/nTCqqdChNeWS7tslgROmYB7yeiruWX7WnqNg==} - dependencies: - '@types/react': 18.2.6 - dev: true - - /@types/scheduler@0.16.3: - resolution: {integrity: sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==} - - /@types/semver@7.5.0: - resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==} - dev: true - - /@types/stack-utils@2.0.1: - resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==} - - /@types/strip-bom@3.0.0: - resolution: {integrity: sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==} - dev: true - - /@types/strip-json-comments@0.0.30: - resolution: {integrity: sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==} - dev: true - - /@types/tough-cookie@4.0.2: - resolution: {integrity: sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==} - dev: true - - /@types/use-sync-external-store@0.0.3: - resolution: {integrity: sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==} - dev: false - - /@types/utf8@3.0.1: - resolution: {integrity: sha512-1EkWuw7rT3BMz2HpmcEOr/HL61mWNA6Ulr/KdbXR9AI0A55wD4Qfv8hizd8Q1DnknSIzzDvQmvvY/guvX7jjZA==} - dev: true - - /@types/uuid@9.0.1: - resolution: {integrity: sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==} - dev: true - - /@types/warning@3.0.3: - resolution: {integrity: sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==} - dev: false - - /@types/yargs-parser@21.0.0: - resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} - - /@types/yargs@17.0.24: - resolution: {integrity: sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==} - dependencies: - '@types/yargs-parser': 21.0.0 - - /@typescript-eslint/eslint-plugin@5.59.5(@typescript-eslint/parser@5.59.5)(eslint@8.40.0)(typescript@4.9.5): - resolution: {integrity: sha512-feA9xbVRWJZor+AnLNAr7A8JRWeZqHUf4T9tlP+TN04b05pFVhO5eN7/O93Y/1OUlLMHKbnJisgDURs/qvtqdg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - '@typescript-eslint/parser': ^5.0.0 - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@eslint-community/regexpp': 4.5.1 - '@typescript-eslint/parser': 5.59.5(eslint@8.40.0)(typescript@4.9.5) - '@typescript-eslint/scope-manager': 5.59.5 - '@typescript-eslint/type-utils': 5.59.5(eslint@8.40.0)(typescript@4.9.5) - '@typescript-eslint/utils': 5.59.5(eslint@8.40.0)(typescript@4.9.5) - debug: 4.3.4 - eslint: 8.40.0 - grapheme-splitter: 1.0.4 - ignore: 5.2.4 - natural-compare-lite: 1.4.0 - semver: 7.5.1 - tsutils: 3.21.0(typescript@4.9.5) - typescript: 4.9.5 - transitivePeerDependencies: - - supports-color - dev: true - - /@typescript-eslint/parser@5.59.5(eslint@8.40.0)(typescript@4.9.5): - resolution: {integrity: sha512-NJXQC4MRnF9N9yWqQE2/KLRSOLvrrlZb48NGVfBa+RuPMN6B7ZcK5jZOvhuygv4D64fRKnZI4L4p8+M+rfeQuw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/scope-manager': 5.59.5 - '@typescript-eslint/types': 5.59.5 - '@typescript-eslint/typescript-estree': 5.59.5(typescript@4.9.5) - debug: 4.3.4 - eslint: 8.40.0 - typescript: 4.9.5 - transitivePeerDependencies: - - supports-color - dev: true - - /@typescript-eslint/scope-manager@5.59.5: - resolution: {integrity: sha512-jVecWwnkX6ZgutF+DovbBJirZcAxgxC0EOHYt/niMROf8p4PwxxG32Qdhj/iIQQIuOflLjNkxoXyArkcIP7C3A==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - '@typescript-eslint/types': 5.59.5 - '@typescript-eslint/visitor-keys': 5.59.5 - dev: true - - /@typescript-eslint/type-utils@5.59.5(eslint@8.40.0)(typescript@4.9.5): - resolution: {integrity: sha512-4eyhS7oGym67/pSxA2mmNq7X164oqDYNnZCUayBwJZIRVvKpBCMBzFnFxjeoDeShjtO6RQBHBuwybuX3POnDqg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: '*' - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/typescript-estree': 5.59.5(typescript@4.9.5) - '@typescript-eslint/utils': 5.59.5(eslint@8.40.0)(typescript@4.9.5) - debug: 4.3.4 - eslint: 8.40.0 - tsutils: 3.21.0(typescript@4.9.5) - typescript: 4.9.5 - transitivePeerDependencies: - - supports-color - dev: true - - /@typescript-eslint/types@5.59.5: - resolution: {integrity: sha512-xkfRPHbqSH4Ggx4eHRIO/eGL8XL4Ysb4woL8c87YuAo8Md7AUjyWKa9YMwTL519SyDPrfEgKdewjkxNCVeJW7w==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true - - /@typescript-eslint/typescript-estree@5.59.5(typescript@4.9.5): - resolution: {integrity: sha512-+XXdLN2CZLZcD/mO7mQtJMvCkzRfmODbeSKuMY/yXbGkzvA9rJyDY5qDYNoiz2kP/dmyAxXquL2BvLQLJFPQIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/types': 5.59.5 - '@typescript-eslint/visitor-keys': 5.59.5 - debug: 4.3.4 - globby: 11.1.0 - is-glob: 4.0.3 - semver: 7.5.1 - tsutils: 3.21.0(typescript@4.9.5) - typescript: 4.9.5 - transitivePeerDependencies: - - supports-color - dev: true - - /@typescript-eslint/utils@5.59.5(eslint@8.40.0)(typescript@4.9.5): - resolution: {integrity: sha512-sCEHOiw+RbyTii9c3/qN74hYDPNORb8yWCoPLmB7BIflhplJ65u2PBpdRla12e3SSTJ2erRkPjz7ngLHhUegxA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.40.0) - '@types/json-schema': 7.0.11 - '@types/semver': 7.5.0 - '@typescript-eslint/scope-manager': 5.59.5 - '@typescript-eslint/types': 5.59.5 - '@typescript-eslint/typescript-estree': 5.59.5(typescript@4.9.5) - eslint: 8.40.0 - eslint-scope: 5.1.1 - semver: 7.5.1 - transitivePeerDependencies: - - supports-color - - typescript - dev: true - - /@typescript-eslint/visitor-keys@5.59.5: - resolution: {integrity: sha512-qL+Oz+dbeBRTeyJTIy0eniD3uvqU7x+y1QceBismZ41hd4aBSRh8UAw4pZP0+XzLuPZmx4raNMq/I+59W2lXKA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - '@typescript-eslint/types': 5.59.5 - eslint-visitor-keys: 3.4.1 - dev: true - - /@vitejs/plugin-react@3.1.0(vite@4.3.5): - resolution: {integrity: sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - vite: ^4.1.0-beta.0 - dependencies: - '@babel/core': 7.21.8 - '@babel/plugin-transform-react-jsx-self': 7.21.0(@babel/core@7.21.8) - '@babel/plugin-transform-react-jsx-source': 7.19.6(@babel/core@7.21.8) - magic-string: 0.27.0 - react-refresh: 0.14.0 - vite: 4.3.5(@types/node@18.16.9)(sass@1.70.0) - transitivePeerDependencies: - - supports-color - dev: true - - /abab@2.0.6: - resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} - dev: true - - /acorn-globals@7.0.1: - resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==} - dependencies: - acorn: 8.8.2 - acorn-walk: 8.2.0 - dev: true - - /acorn-jsx@5.3.2(acorn@8.8.2): - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - dependencies: - acorn: 8.8.2 - dev: true - - /acorn-walk@8.2.0: - resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} - engines: {node: '>=0.4.0'} - dev: true - - /acorn@8.8.2: - resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} - engines: {node: '>=0.4.0'} - hasBin: true - dev: true - - /add-px-to-style@1.0.0: - resolution: {integrity: sha512-YMyxSlXpPjD8uWekCQGuN40lV4bnZagUwqa2m/uFv1z/tNImSk9fnXVMUI5qwME/zzI3MMQRvjZ+69zyfSSyew==} - dev: false - - /agent-base@6.0.2: - resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} - engines: {node: '>= 6.0.0'} - dependencies: - debug: 4.3.4 - transitivePeerDependencies: - - supports-color - dev: true - - /ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - dev: true - - /ansi-escapes@4.3.2: - resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} - engines: {node: '>=8'} - dependencies: - type-fest: 0.21.3 - - /ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - /ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - dependencies: - color-convert: 1.9.3 - - /ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - dependencies: - color-convert: 2.0.1 - - /ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - - /any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - dev: true - - /anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - - /arg@4.1.3: - resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} - dev: true - - /arg@5.0.2: - resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} - dev: true - - /argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} - dependencies: - sprintf-js: 1.0.3 - - /argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - dev: true - - /array-buffer-byte-length@1.0.0: - resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} - dependencies: - call-bind: 1.0.2 - is-array-buffer: 3.0.2 - dev: true - - /array-includes@3.1.6: - resolution: {integrity: sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 - get-intrinsic: 1.2.1 - is-string: 1.0.7 - dev: true - - /array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - dev: true - - /array.prototype.flatmap@1.3.1: - resolution: {integrity: sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 - es-shim-unscopables: 1.0.0 - dev: true - - /array.prototype.tosorted@1.1.1: - resolution: {integrity: sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 - es-shim-unscopables: 1.0.0 - get-intrinsic: 1.2.1 - dev: true - - /asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - dev: true - - /autoprefixer@10.4.14(postcss@8.4.23): - resolution: {integrity: sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==} - engines: {node: ^10 || ^12 || >=14} - hasBin: true - peerDependencies: - postcss: ^8.1.0 - dependencies: - browserslist: 4.21.5 - caniuse-lite: 1.0.30001487 - fraction.js: 4.2.0 - normalize-range: 0.1.2 - picocolors: 1.0.0 - postcss: 8.4.23 - postcss-value-parser: 4.2.0 - dev: true - - /available-typed-arrays@1.0.5: - resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} - engines: {node: '>= 0.4'} - dev: true - - /babel-jest@29.6.2(@babel/core@7.21.8): - resolution: {integrity: sha512-BYCzImLos6J3BH/+HvUCHG1dTf2MzmAB4jaVxHV+29RZLjR29XuYTmsf2sdDwkrb+FczkGo3kOhE7ga6sI0P4A==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@babel/core': ^7.8.0 - dependencies: - '@babel/core': 7.21.8 - '@jest/transform': 29.6.4 - '@types/babel__core': 7.20.0 - babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.5.0(@babel/core@7.21.8) - chalk: 4.1.2 - graceful-fs: 4.2.11 - slash: 3.0.0 - transitivePeerDependencies: - - supports-color - - /babel-plugin-istanbul@6.1.1: - resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} - engines: {node: '>=8'} - dependencies: - '@babel/helper-plugin-utils': 7.21.5 - '@istanbuljs/load-nyc-config': 1.1.0 - '@istanbuljs/schema': 0.1.3 - istanbul-lib-instrument: 5.2.1 - test-exclude: 6.0.0 - transitivePeerDependencies: - - supports-color - - /babel-plugin-jest-hoist@29.5.0: - resolution: {integrity: sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@babel/template': 7.20.7 - '@babel/types': 7.21.5 - '@types/babel__core': 7.20.0 - '@types/babel__traverse': 7.18.5 - - /babel-plugin-macros@3.1.0: - resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} - engines: {node: '>=10', npm: '>=6'} - dependencies: - '@babel/runtime': 7.23.4 - cosmiconfig: 7.1.0 - resolve: 1.22.2 - dev: false - - /babel-preset-current-node-syntax@1.0.1(@babel/core@7.21.8): - resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.21.8 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.21.8) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.21.8) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.21.8) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.21.8) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.21.8) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.21.8) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.21.8) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.21.8) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.21.8) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.21.8) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.21.8) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.21.8) - - /babel-preset-jest@29.5.0(@babel/core@7.21.8): - resolution: {integrity: sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.21.8 - babel-plugin-jest-hoist: 29.5.0 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.21.8) - - /balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - /binary-extensions@2.2.0: - resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} - engines: {node: '>=8'} - - /boolbase@1.0.0: - resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - dev: true - - /brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - - /braces@3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} - engines: {node: '>=8'} - dependencies: - fill-range: 7.0.1 - - /browserslist@4.21.5: - resolution: {integrity: sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - dependencies: - caniuse-lite: 1.0.30001487 - electron-to-chromium: 1.4.394 - node-releases: 2.0.10 - update-browserslist-db: 1.0.11(browserslist@4.21.5) - - /bs-logger@0.2.6: - resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} - engines: {node: '>= 6'} - dependencies: - fast-json-stable-stringify: 2.1.0 - dev: true - - /bser@2.1.1: - resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} - dependencies: - node-int64: 0.4.0 - - /buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - - /call-bind@1.0.2: - resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==} - dependencies: - function-bind: 1.1.1 - get-intrinsic: 1.2.1 - - /callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - - /camel-case@4.1.2: - resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} - dependencies: - pascal-case: 3.1.2 - tslib: 2.5.0 - dev: true - - /camelcase-css@2.0.1: - resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} - engines: {node: '>= 6'} - dev: true - - /camelcase@5.3.1: - resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} - engines: {node: '>=6'} - - /camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} - - /caniuse-lite@1.0.30001487: - resolution: {integrity: sha512-83564Z3yWGqXsh2vaH/mhXfEM0wX+NlBCm1jYHOb97TrTWJEmPTccZgeLTPBUUb0PNVo+oomb7wkimZBIERClA==} - - /capital-case@1.0.4: - resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==} - dependencies: - no-case: 3.0.4 - tslib: 2.5.0 - upper-case-first: 2.0.2 - dev: true - - /chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 - - /chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - - /change-case@4.1.2: - resolution: {integrity: sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==} - dependencies: - camel-case: 4.1.2 - capital-case: 1.0.4 - constant-case: 3.0.4 - dot-case: 3.0.4 - header-case: 2.0.4 - no-case: 3.0.4 - param-case: 3.0.4 - pascal-case: 3.1.2 - path-case: 3.0.4 - sentence-case: 3.0.4 - snake-case: 3.0.4 - tslib: 2.5.0 - dev: true - - /char-regex@1.0.2: - resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} - engines: {node: '>=10'} - - /chokidar@3.5.3: - resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} - engines: {node: '>= 8.10.0'} - dependencies: - anymatch: 3.1.3 - braces: 3.0.2 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.2 - - /ci-info@3.8.0: - resolution: {integrity: sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==} - engines: {node: '>=8'} - - /cjs-module-lexer@1.2.2: - resolution: {integrity: sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==} - - /classnames@2.3.2: - resolution: {integrity: sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==} - dev: false - - /cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - - /clone@2.1.2: - resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} - engines: {node: '>=0.8'} - dev: false - - /clsx@1.2.1: - resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} - engines: {node: '>=6'} - dev: false - - /clsx@2.0.0: - resolution: {integrity: sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==} - engines: {node: '>=6'} - dev: false - - /co@4.6.0: - resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} - engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - - /collect-v8-coverage@1.0.1: - resolution: {integrity: sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==} - - /color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - dependencies: - color-name: 1.1.3 - - /color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - dependencies: - color-name: 1.1.4 - - /color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - - /color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - /combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - dependencies: - delayed-stream: 1.0.0 - dev: true - - /commander@4.1.1: - resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} - engines: {node: '>= 6'} - dev: true - - /commander@7.2.0: - resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} - engines: {node: '>= 10'} - dev: true - - /commander@8.3.0: - resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} - engines: {node: '>= 12'} - - /compute-scroll-into-view@3.1.0: - resolution: {integrity: sha512-rj8l8pD4bJ1nx+dAkMhV1xB5RuZEyVysfxJqB1pRchh1KVvwOv9b7CGB8ZfjTImVv2oF+sYMUkMZq6Na5Ftmbg==} - dev: false - - /concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - - /constant-case@3.0.4: - resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==} - dependencies: - no-case: 3.0.4 - tslib: 2.5.0 - upper-case: 2.0.2 - dev: true - - /convert-source-map@1.9.0: - resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} - - /convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - - /cosmiconfig@7.1.0: - resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} - engines: {node: '>=10'} - dependencies: - '@types/parse-json': 4.0.0 - import-fresh: 3.3.0 - parse-json: 5.2.0 - path-type: 4.0.0 - yaml: 1.10.2 - dev: false - - /cosmiconfig@8.2.0: - resolution: {integrity: sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==} - engines: {node: '>=14'} - dependencies: - import-fresh: 3.3.0 - js-yaml: 4.1.0 - parse-json: 5.2.0 - path-type: 4.0.0 - dev: true - - /create-require@1.1.1: - resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - dev: true - - /cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - /css-box-model@1.2.1: - resolution: {integrity: sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==} - dependencies: - tiny-invariant: 1.3.1 - dev: false - - /css-select@5.1.0: - resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} - dependencies: - boolbase: 1.0.0 - css-what: 6.1.0 - domhandler: 5.0.3 - domutils: 3.1.0 - nth-check: 2.1.1 - dev: true - - /css-tree@2.2.1: - resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} - dependencies: - mdn-data: 2.0.28 - source-map-js: 1.0.2 - dev: true - - /css-tree@2.3.1: - resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} - dependencies: - mdn-data: 2.0.30 - source-map-js: 1.0.2 - dev: true - - /css-what@6.1.0: - resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} - engines: {node: '>= 6'} - dev: true - - /cssesc@3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} - hasBin: true - dev: true - - /csso@5.0.5: - resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} - dependencies: - css-tree: 2.2.1 - dev: true - - /cssom@0.3.8: - resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==} - dev: true - - /cssom@0.5.0: - resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==} - dev: true - - /cssstyle@2.3.0: - resolution: {integrity: sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==} - engines: {node: '>=8'} - dependencies: - cssom: 0.3.8 - dev: true - - /csstype@3.1.2: - resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} - - /data-urls@3.0.2: - resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==} - engines: {node: '>=12'} - dependencies: - abab: 2.0.6 - whatwg-mimetype: 3.0.0 - whatwg-url: 11.0.0 - dev: true - - /date-arithmetic@4.1.0: - resolution: {integrity: sha512-QWxYLR5P/6GStZcdem+V1xoto6DMadYWpMXU82ES3/RfR3Wdwr3D0+be7mgOJ+Ov0G9D5Dmb9T17sNLQYj9XOg==} - dev: false - - /date-fns@2.30.0: - resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} - engines: {node: '>=0.11'} - dependencies: - '@babel/runtime': 7.23.4 - - /dayjs@1.11.9: - resolution: {integrity: sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==} - dev: false - - /debug@4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.2 - - /decimal.js@10.4.3: - resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} - dev: true - - /dedent@0.7.0: - resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} - - /deep-equal@1.1.1: - resolution: {integrity: sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==} - dependencies: - is-arguments: 1.1.1 - is-date-object: 1.0.5 - is-regex: 1.1.4 - object-is: 1.1.5 - object-keys: 1.1.1 - regexp.prototype.flags: 1.5.0 - dev: false - - /deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - dev: true - - /deepmerge@4.3.1: - resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} - engines: {node: '>=0.10.0'} - - /define-properties@1.2.0: - resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==} - engines: {node: '>= 0.4'} - dependencies: - has-property-descriptors: 1.0.0 - object-keys: 1.1.1 - - /delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - dev: true - - /dequal@2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} - engines: {node: '>=6'} - dev: false - - /derive-valtio@0.1.0(valtio@1.12.1): - resolution: {integrity: sha512-OCg2UsLbXK7GmmpzMXhYkdO64vhJ1ROUUGaTFyHjVwEdMEcTTRj7W1TxLbSBxdY8QLBPCcp66MTyaSy0RpO17A==} - peerDependencies: - valtio: '*' - dependencies: - valtio: 1.12.1(@types/react@18.2.6)(react@18.2.0) - dev: false - - /detect-newline@3.1.0: - resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} - engines: {node: '>=8'} - - /didyoumean@1.2.2: - resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} - dev: true - - /diff-sequences@29.4.3: - resolution: {integrity: sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - /diff@4.0.2: - resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} - engines: {node: '>=0.3.1'} - dev: true - - /dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} - dependencies: - path-type: 4.0.0 - dev: true - - /direction@1.0.4: - resolution: {integrity: sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ==} - hasBin: true - dev: false - - /dlv@1.1.3: - resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} - dev: true - - /doctrine@2.1.0: - resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} - engines: {node: '>=0.10.0'} - dependencies: - esutils: 2.0.3 - dev: true - - /doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} - dependencies: - esutils: 2.0.3 - dev: true - - /dom-css@2.1.0: - resolution: {integrity: sha512-w9kU7FAbaSh3QKijL6n59ofAhkkmMJ31GclJIz/vyQdjogfyxcB6Zf8CZyibOERI5o0Hxz30VmJS7+7r5fEj2Q==} - dependencies: - add-px-to-style: 1.0.0 - prefix-style: 2.0.1 - to-camel-case: 1.0.0 - dev: false - - /dom-helpers@5.2.1: - resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} - dependencies: - '@babel/runtime': 7.23.4 - csstype: 3.1.2 - dev: false - - /dom-serializer@2.0.0: - resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} - dependencies: - domelementtype: 2.3.0 - domhandler: 5.0.3 - entities: 4.5.0 - dev: true - - /domelementtype@2.3.0: - resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} - dev: true - - /domexception@4.0.0: - resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==} - engines: {node: '>=12'} - dependencies: - webidl-conversions: 7.0.0 - dev: true - - /domhandler@5.0.3: - resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} - engines: {node: '>= 4'} - dependencies: - domelementtype: 2.3.0 - dev: true - - /domutils@3.1.0: - resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} - dependencies: - dom-serializer: 2.0.0 - domelementtype: 2.3.0 - domhandler: 5.0.3 - dev: true - - /dot-case@3.0.4: - resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} - dependencies: - no-case: 3.0.4 - tslib: 2.5.0 - dev: true - - /dynamic-dedupe@0.3.0: - resolution: {integrity: sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==} - dependencies: - xtend: 4.0.2 - dev: true - - /electron-to-chromium@1.4.394: - resolution: {integrity: sha512-0IbC2cfr8w5LxTz+nmn2cJTGafsK9iauV2r5A5scfzyovqLrxuLoxOHE5OBobP3oVIggJT+0JfKnw9sm87c8Hw==} - - /emittery@0.13.1: - resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} - engines: {node: '>=12'} - - /emoji-mart@5.5.2: - resolution: {integrity: sha512-Sqc/nso4cjxhOwWJsp9xkVm8OF5c+mJLZJFoFfzRuKO+yWiN7K8c96xmtughYb0d/fZ8UC6cLIQ/p4BR6Pv3/A==} - dev: false - - /emoji-regex@10.2.1: - resolution: {integrity: sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA==} - dev: false - - /emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - /entities@4.5.0: - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} - engines: {node: '>=0.12'} - dev: true - - /error-ex@1.3.2: - resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - dependencies: - is-arrayish: 0.2.1 - - /es-abstract@1.21.2: - resolution: {integrity: sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==} - engines: {node: '>= 0.4'} - dependencies: - array-buffer-byte-length: 1.0.0 - available-typed-arrays: 1.0.5 - call-bind: 1.0.2 - es-set-tostringtag: 2.0.1 - es-to-primitive: 1.2.1 - function.prototype.name: 1.1.5 - get-intrinsic: 1.2.1 - get-symbol-description: 1.0.0 - globalthis: 1.0.3 - gopd: 1.0.1 - has: 1.0.3 - has-property-descriptors: 1.0.0 - has-proto: 1.0.1 - has-symbols: 1.0.3 - internal-slot: 1.0.5 - is-array-buffer: 3.0.2 - is-callable: 1.2.7 - is-negative-zero: 2.0.2 - is-regex: 1.1.4 - is-shared-array-buffer: 1.0.2 - is-string: 1.0.7 - is-typed-array: 1.1.10 - is-weakref: 1.0.2 - object-inspect: 1.12.3 - object-keys: 1.1.1 - object.assign: 4.1.4 - regexp.prototype.flags: 1.5.0 - safe-regex-test: 1.0.0 - string.prototype.trim: 1.2.7 - string.prototype.trimend: 1.0.6 - string.prototype.trimstart: 1.0.6 - typed-array-length: 1.0.4 - unbox-primitive: 1.0.2 - which-typed-array: 1.1.9 - dev: true - - /es-set-tostringtag@2.0.1: - resolution: {integrity: sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==} - engines: {node: '>= 0.4'} - dependencies: - get-intrinsic: 1.2.1 - has: 1.0.3 - has-tostringtag: 1.0.0 - dev: true - - /es-shim-unscopables@1.0.0: - resolution: {integrity: sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==} - dependencies: - has: 1.0.3 - dev: true - - /es-to-primitive@1.2.1: - resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} - engines: {node: '>= 0.4'} - dependencies: - is-callable: 1.2.7 - is-date-object: 1.0.5 - is-symbol: 1.0.4 - dev: true - - /esbuild@0.17.19: - resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==} - engines: {node: '>=12'} - hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/android-arm': 0.17.19 - '@esbuild/android-arm64': 0.17.19 - '@esbuild/android-x64': 0.17.19 - '@esbuild/darwin-arm64': 0.17.19 - '@esbuild/darwin-x64': 0.17.19 - '@esbuild/freebsd-arm64': 0.17.19 - '@esbuild/freebsd-x64': 0.17.19 - '@esbuild/linux-arm': 0.17.19 - '@esbuild/linux-arm64': 0.17.19 - '@esbuild/linux-ia32': 0.17.19 - '@esbuild/linux-loong64': 0.17.19 - '@esbuild/linux-mips64el': 0.17.19 - '@esbuild/linux-ppc64': 0.17.19 - '@esbuild/linux-riscv64': 0.17.19 - '@esbuild/linux-s390x': 0.17.19 - '@esbuild/linux-x64': 0.17.19 - '@esbuild/netbsd-x64': 0.17.19 - '@esbuild/openbsd-x64': 0.17.19 - '@esbuild/sunos-x64': 0.17.19 - '@esbuild/win32-arm64': 0.17.19 - '@esbuild/win32-ia32': 0.17.19 - '@esbuild/win32-x64': 0.17.19 - dev: true - - /escalade@3.1.1: - resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} - engines: {node: '>=6'} - - /escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - - /escape-string-regexp@2.0.0: - resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} - engines: {node: '>=8'} - - /escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - - /escodegen@2.1.0: - resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} - engines: {node: '>=6.0'} - hasBin: true - dependencies: - esprima: 4.0.1 - estraverse: 5.3.0 - esutils: 2.0.3 - optionalDependencies: - source-map: 0.6.1 - dev: true - - /eslint-plugin-react-hooks@4.6.0(eslint@8.40.0): - resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} - engines: {node: '>=10'} - peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 - dependencies: - eslint: 8.40.0 - dev: true - - /eslint-plugin-react@7.32.2(eslint@8.40.0): - resolution: {integrity: sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==} - engines: {node: '>=4'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 - dependencies: - array-includes: 3.1.6 - array.prototype.flatmap: 1.3.1 - array.prototype.tosorted: 1.1.1 - doctrine: 2.1.0 - eslint: 8.40.0 - estraverse: 5.3.0 - jsx-ast-utils: 3.3.3 - minimatch: 3.1.2 - object.entries: 1.1.6 - object.fromentries: 2.0.6 - object.hasown: 1.1.2 - object.values: 1.1.6 - prop-types: 15.8.1 - resolve: 2.0.0-next.4 - semver: 6.3.0 - string.prototype.matchall: 4.0.8 - dev: true - - /eslint-scope@5.1.1: - resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} - engines: {node: '>=8.0.0'} - dependencies: - esrecurse: 4.3.0 - estraverse: 4.3.0 - dev: true - - /eslint-scope@7.2.0: - resolution: {integrity: sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - dev: true - - /eslint-visitor-keys@3.4.1: - resolution: {integrity: sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true - - /eslint@8.40.0: - resolution: {integrity: sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - hasBin: true - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.40.0) - '@eslint-community/regexpp': 4.5.1 - '@eslint/eslintrc': 2.0.3 - '@eslint/js': 8.40.0 - '@humanwhocodes/config-array': 0.11.8 - '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.3 - debug: 4.3.4 - doctrine: 3.0.0 - escape-string-regexp: 4.0.0 - eslint-scope: 7.2.0 - eslint-visitor-keys: 3.4.1 - espree: 9.5.2 - esquery: 1.5.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 - find-up: 5.0.0 - glob-parent: 6.0.2 - globals: 13.20.0 - grapheme-splitter: 1.0.4 - ignore: 5.2.4 - import-fresh: 3.3.0 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - is-path-inside: 3.0.3 - js-sdsl: 4.4.0 - js-yaml: 4.1.0 - json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.1 - strip-ansi: 6.0.1 - strip-json-comments: 3.1.1 - text-table: 0.2.0 - transitivePeerDependencies: - - supports-color - dev: true - - /espree@9.5.2: - resolution: {integrity: sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - acorn: 8.8.2 - acorn-jsx: 5.3.2(acorn@8.8.2) - eslint-visitor-keys: 3.4.1 - dev: true - - /esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true - - /esquery@1.5.0: - resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} - engines: {node: '>=0.10'} - dependencies: - estraverse: 5.3.0 - dev: true - - /esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} - dependencies: - estraverse: 5.3.0 - dev: true - - /estraverse@4.3.0: - resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} - engines: {node: '>=4.0'} - dev: true - - /estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - dev: true - - /estree-walker@2.0.2: - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - dev: true - - /esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - dev: true - - /eventemitter3@2.0.3: - resolution: {integrity: sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==} - dev: false - - /events@3.3.0: - resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} - engines: {node: '>=0.8.x'} - dev: false - - /execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} - dependencies: - cross-spawn: 7.0.3 - get-stream: 6.0.1 - human-signals: 2.1.0 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - - /exit@0.1.2: - resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} - engines: {node: '>= 0.8.0'} - - /expect@29.5.0: - resolution: {integrity: sha512-yM7xqUrCO2JdpFo4XpM82t+PJBFybdqoQuJLDGeDX2ij8NZzqRHyu3Hp188/JX7SWqud+7t4MUdvcgGBICMHZg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/expect-utils': 29.5.0 - jest-get-type: 29.4.3 - jest-matcher-utils: 29.5.0 - jest-message-util: 29.5.0 - jest-util: 29.5.0 - - /extend@3.0.2: - resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - dev: false - - /fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - dev: true - - /fast-diff@1.1.2: - resolution: {integrity: sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==} - dev: false - - /fast-diff@1.2.0: - resolution: {integrity: sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==} - dev: true - - /fast-diff@1.3.0: - resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} - dev: false - - /fast-glob@3.2.12: - resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} - engines: {node: '>=8.6.0'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.5 - dev: true - - /fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - - /fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - dev: true - - /fastq@1.15.0: - resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} - dependencies: - reusify: 1.0.4 - dev: true - - /fb-watchman@2.0.2: - resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} - dependencies: - bser: 2.1.1 - - /file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} - dependencies: - flat-cache: 3.0.4 - dev: true - - /fill-range@7.0.1: - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} - engines: {node: '>=8'} - dependencies: - to-regex-range: 5.0.1 - - /find-root@1.1.0: - resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} - dev: false - - /find-up@4.1.0: - resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} - engines: {node: '>=8'} - dependencies: - locate-path: 5.0.0 - path-exists: 4.0.0 - - /find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - dev: true - - /flat-cache@3.0.4: - resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} - engines: {node: ^10.12.0 || >=12.0.0} - dependencies: - flatted: 3.2.7 - rimraf: 3.0.2 - dev: true - - /flatted@3.2.7: - resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} - dev: true - - /for-each@0.3.3: - resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} - dependencies: - is-callable: 1.2.7 - dev: true - - /form-data@4.0.0: - resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} - engines: {node: '>= 6'} - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 - dev: true - - /fraction.js@4.2.0: - resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} - dev: true - - /fs-extra@10.1.0: - resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} - engines: {node: '>=12'} - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.1.0 - universalify: 2.0.0 - dev: true - - /fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - - /fsevents@2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - requiresBuild: true - optional: true - - /function-bind@1.1.1: - resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} - - /function.prototype.name@1.1.5: - resolution: {integrity: sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 - functions-have-names: 1.2.3 - dev: true - - /functions-have-names@1.2.3: - resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - - /gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - - /get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - - /get-intrinsic@1.2.1: - resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==} - dependencies: - function-bind: 1.1.1 - has: 1.0.3 - has-proto: 1.0.1 - has-symbols: 1.0.3 - - /get-package-type@0.1.0: - resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} - engines: {node: '>=8.0.0'} - - /get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} - - /get-symbol-description@1.0.0: - resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.1 - dev: true - - /glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - dependencies: - is-glob: 4.0.3 - - /glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} - dependencies: - is-glob: 4.0.3 - dev: true - - /glob@7.1.6: - resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - dev: true - - /glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - - /globalize@0.1.1: - resolution: {integrity: sha512-5e01v8eLGfuQSOvx2MsDMOWS0GFtCx1wPzQSmcHw4hkxFzrQDBO3Xwg/m8Hr/7qXMrHeOIE29qWVzyv06u1TZA==} - dev: false - - /globals@11.12.0: - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} - engines: {node: '>=4'} - - /globals@13.20.0: - resolution: {integrity: sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==} - engines: {node: '>=8'} - dependencies: - type-fest: 0.20.2 - dev: true - - /globalthis@1.0.3: - resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} - engines: {node: '>= 0.4'} - dependencies: - define-properties: 1.2.0 - dev: true - - /globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.2.12 - ignore: 5.2.4 - merge2: 1.4.1 - slash: 3.0.0 - dev: true - - /goober@2.1.13(csstype@3.1.2): - resolution: {integrity: sha512-jFj3BQeleOoy7t93E9rZ2de+ScC4lQICLwiAQmKMg9F6roKGaLSHoCDYKkWlSafg138jejvq/mTdvmnwDQgqoQ==} - peerDependencies: - csstype: ^3.0.10 - dependencies: - csstype: 3.1.2 - dev: false - - /google-protobuf@3.21.2: - resolution: {integrity: sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA==} - dev: false - - /gopd@1.0.1: - resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} - dependencies: - get-intrinsic: 1.2.1 - dev: true - - /graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - /grapheme-splitter@1.0.4: - resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} - dev: true - - /has-bigints@1.0.2: - resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} - dev: true - - /has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} - - /has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - /has-property-descriptors@1.0.0: - resolution: {integrity: sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==} - dependencies: - get-intrinsic: 1.2.1 - - /has-proto@1.0.1: - resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} - engines: {node: '>= 0.4'} - - /has-symbols@1.0.3: - resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} - engines: {node: '>= 0.4'} - - /has-tostringtag@1.0.0: - resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} - engines: {node: '>= 0.4'} - dependencies: - has-symbols: 1.0.3 - - /has@1.0.3: - resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} - engines: {node: '>= 0.4.0'} - dependencies: - function-bind: 1.1.1 - - /header-case@2.0.4: - resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==} - dependencies: - capital-case: 1.0.4 - tslib: 2.5.0 - dev: true - - /hoist-non-react-statics@3.3.2: - resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} - dependencies: - react-is: 16.13.1 - dev: false - - /html-encoding-sniffer@3.0.0: - resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==} - engines: {node: '>=12'} - dependencies: - whatwg-encoding: 2.0.0 - dev: true - - /html-escaper@2.0.2: - resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - - /html-parse-stringify@3.0.1: - resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} - dependencies: - void-elements: 3.1.0 - dev: false - - /http-proxy-agent@5.0.0: - resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} - engines: {node: '>= 6'} - dependencies: - '@tootallnate/once': 2.0.0 - agent-base: 6.0.2 - debug: 4.3.4 - transitivePeerDependencies: - - supports-color - dev: true - - /https-proxy-agent@5.0.1: - resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} - engines: {node: '>= 6'} - dependencies: - agent-base: 6.0.2 - debug: 4.3.4 - transitivePeerDependencies: - - supports-color - dev: true - - /human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} - - /i18next-browser-languagedetector@7.0.1: - resolution: {integrity: sha512-Pa5kFwaczXJAeHE56CHG2aWzFBMJNUNghf0Pm4SwSrEMps/PTKqW90EYWlIvhuYStf3Sn1K0vw+gH3+TLdkH1g==} - dependencies: - '@babel/runtime': 7.21.5 - dev: false - - /i18next-resources-to-backend@1.1.4: - resolution: {integrity: sha512-hMyr9AOmIea17AOaVe1srNxK/l3mbk81P7Uf3fdcjlw3ehZy3UNTd0OP3EEi6yu4J02kf9jzhCcjokz6AFlEOg==} - dependencies: - '@babel/runtime': 7.21.5 - dev: false - - /i18next@22.4.15: - resolution: {integrity: sha512-yYudtbFrrmWKLEhl6jvKUYyYunj4bTBCe2qIUYAxbXoPusY7YmdwPvOE6fx6UIfWvmlbCWDItr7wIs8KEBZ5Zg==} - dependencies: - '@babel/runtime': 7.21.5 - dev: false - - /iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} - dependencies: - safer-buffer: 2.1.2 - dev: true - - /ignore@5.2.4: - resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} - engines: {node: '>= 4'} - dev: true - - /immer@10.0.3: - resolution: {integrity: sha512-pwupu3eWfouuaowscykeckFmVTpqbzW+rXFCX8rQLkZzM9ftBmU/++Ra+o+L27mz03zJTlyV4UUr+fdKNffo4A==} - dev: false - - /immutable@4.3.4: - resolution: {integrity: sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==} - - /import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - - /import-local@3.1.0: - resolution: {integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==} - engines: {node: '>=8'} - hasBin: true - dependencies: - pkg-dir: 4.2.0 - resolve-cwd: 3.0.0 - - /imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - - /inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - - /inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - /internal-slot@1.0.5: - resolution: {integrity: sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==} - engines: {node: '>= 0.4'} - dependencies: - get-intrinsic: 1.2.1 - has: 1.0.3 - side-channel: 1.0.4 - dev: true - - /invariant@2.2.4: - resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} - dependencies: - loose-envify: 1.4.0 - dev: false - - /is-arguments@1.1.1: - resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - has-tostringtag: 1.0.0 - dev: false - - /is-array-buffer@3.0.2: - resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} - dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.1 - is-typed-array: 1.1.10 - dev: true - - /is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - - /is-bigint@1.0.4: - resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} - dependencies: - has-bigints: 1.0.2 - dev: true - - /is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - dependencies: - binary-extensions: 2.2.0 - - /is-boolean-object@1.1.2: - resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - has-tostringtag: 1.0.0 - dev: true - - /is-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} - dev: true - - /is-core-module@2.12.0: - resolution: {integrity: sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==} - dependencies: - has: 1.0.3 - - /is-date-object@1.0.5: - resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} - engines: {node: '>= 0.4'} - dependencies: - has-tostringtag: 1.0.0 - - /is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - /is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - /is-generator-fn@2.1.0: - resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} - engines: {node: '>=6'} - - /is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - dependencies: - is-extglob: 2.1.1 - - /is-hotkey@0.2.0: - resolution: {integrity: sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==} - dev: false - - /is-negative-zero@2.0.2: - resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} - engines: {node: '>= 0.4'} - dev: true - - /is-number-object@1.0.7: - resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} - engines: {node: '>= 0.4'} - dependencies: - has-tostringtag: 1.0.0 - dev: true - - /is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - - /is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - dev: true - - /is-plain-object@5.0.0: - resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} - engines: {node: '>=0.10.0'} - dev: false - - /is-potential-custom-element-name@1.0.1: - resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} - dev: true - - /is-regex@1.1.4: - resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - has-tostringtag: 1.0.0 - - /is-shared-array-buffer@1.0.2: - resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} - dependencies: - call-bind: 1.0.2 - dev: true - - /is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} - - /is-string@1.0.7: - resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} - engines: {node: '>= 0.4'} - dependencies: - has-tostringtag: 1.0.0 - dev: true - - /is-symbol@1.0.4: - resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} - engines: {node: '>= 0.4'} - dependencies: - has-symbols: 1.0.3 - dev: true - - /is-typed-array@1.1.10: - resolution: {integrity: sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==} - engines: {node: '>= 0.4'} - dependencies: - available-typed-arrays: 1.0.5 - call-bind: 1.0.2 - for-each: 0.3.3 - gopd: 1.0.1 - has-tostringtag: 1.0.0 - dev: true - - /is-weakref@1.0.2: - resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} - dependencies: - call-bind: 1.0.2 - dev: true - - /isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - /isomorphic.js@0.2.5: - resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==} - dev: false - - /istanbul-lib-coverage@3.2.0: - resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==} - engines: {node: '>=8'} - - /istanbul-lib-instrument@5.2.1: - resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} - engines: {node: '>=8'} - dependencies: - '@babel/core': 7.21.8 - '@babel/parser': 7.21.8 - '@istanbuljs/schema': 0.1.3 - istanbul-lib-coverage: 3.2.0 - semver: 6.3.0 - transitivePeerDependencies: - - supports-color - - /istanbul-lib-report@3.0.0: - resolution: {integrity: sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==} - engines: {node: '>=8'} - dependencies: - istanbul-lib-coverage: 3.2.0 - make-dir: 3.1.0 - supports-color: 7.2.0 - - /istanbul-lib-source-maps@4.0.1: - resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} - engines: {node: '>=10'} - dependencies: - debug: 4.3.4 - istanbul-lib-coverage: 3.2.0 - source-map: 0.6.1 - transitivePeerDependencies: - - supports-color - - /istanbul-reports@3.1.5: - resolution: {integrity: sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==} - engines: {node: '>=8'} - dependencies: - html-escaper: 2.0.2 - istanbul-lib-report: 3.0.0 - - /jest-changed-files@29.5.0: - resolution: {integrity: sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - execa: 5.1.1 - p-limit: 3.1.0 - - /jest-circus@29.5.0: - resolution: {integrity: sha512-gq/ongqeQKAplVxqJmbeUOJJKkW3dDNPY8PjhJ5G0lBRvu0e3EWGxGy5cI4LAGA7gV2UHCtWBI4EMXK8c9nQKA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/environment': 29.6.4 - '@jest/expect': 29.5.0 - '@jest/test-result': 29.5.0 - '@jest/types': 29.6.3 - '@types/node': 18.16.9 - chalk: 4.1.2 - co: 4.6.0 - dedent: 0.7.0 - is-generator-fn: 2.1.0 - jest-each: 29.5.0 - jest-matcher-utils: 29.5.0 - jest-message-util: 29.6.3 - jest-runtime: 29.5.0 - jest-snapshot: 29.5.0 - jest-util: 29.6.3 - p-limit: 3.1.0 - pretty-format: 29.5.0 - pure-rand: 6.0.2 - slash: 3.0.0 - stack-utils: 2.0.6 - transitivePeerDependencies: - - supports-color - - /jest-cli@29.5.0(@types/node@18.16.9): - resolution: {integrity: sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - dependencies: - '@jest/core': 29.5.0 - '@jest/test-result': 29.5.0 - '@jest/types': 29.5.0 - chalk: 4.1.2 - exit: 0.1.2 - graceful-fs: 4.2.11 - import-local: 3.1.0 - jest-config: 29.5.0(@types/node@18.16.9) - jest-util: 29.5.0 - jest-validate: 29.5.0 - prompts: 2.4.2 - yargs: 17.7.2 - transitivePeerDependencies: - - '@types/node' - - supports-color - - ts-node - - /jest-config@29.5.0(@types/node@18.16.9): - resolution: {integrity: sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@types/node': '*' - ts-node: '>=9.0.0' - peerDependenciesMeta: - '@types/node': - optional: true - ts-node: - optional: true - dependencies: - '@babel/core': 7.21.8 - '@jest/test-sequencer': 29.5.0 - '@jest/types': 29.5.0 - '@types/node': 18.16.9 - babel-jest: 29.6.2(@babel/core@7.21.8) - chalk: 4.1.2 - ci-info: 3.8.0 - deepmerge: 4.3.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-circus: 29.5.0 - jest-environment-node: 29.5.0 - jest-get-type: 29.4.3 - jest-regex-util: 29.4.3 - jest-resolve: 29.5.0 - jest-runner: 29.5.0 - jest-util: 29.5.0 - jest-validate: 29.5.0 - micromatch: 4.0.5 - parse-json: 5.2.0 - pretty-format: 29.5.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - - /jest-diff@29.5.0: - resolution: {integrity: sha512-LtxijLLZBduXnHSniy0WMdaHjmQnt3g5sa16W4p0HqukYTTsyTW3GD1q41TyGl5YFXj/5B2U6dlh5FM1LIMgxw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - chalk: 4.1.2 - diff-sequences: 29.4.3 - jest-get-type: 29.4.3 - pretty-format: 29.5.0 - - /jest-docblock@29.4.3: - resolution: {integrity: sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - detect-newline: 3.1.0 - - /jest-each@29.5.0: - resolution: {integrity: sha512-HM5kIJ1BTnVt+DQZ2ALp3rzXEl+g726csObrW/jpEGl+CDSSQpOJJX2KE/vEg8cxcMXdyEPu6U4QX5eruQv5hA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.6.3 - chalk: 4.1.2 - jest-get-type: 29.4.3 - jest-util: 29.6.3 - pretty-format: 29.5.0 - - /jest-environment-jsdom@29.6.2: - resolution: {integrity: sha512-7oa/+266AAEgkzae8i1awNEfTfjwawWKLpiw2XesZmaoVVj9u9t8JOYx18cG29rbPNtkUlZ8V4b5Jb36y/VxoQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - canvas: ^2.5.0 - peerDependenciesMeta: - canvas: - optional: true - dependencies: - '@jest/environment': 29.6.4 - '@jest/fake-timers': 29.6.4 - '@jest/types': 29.6.3 - '@types/jsdom': 20.0.1 - '@types/node': 18.16.9 - jest-mock: 29.6.3 - jest-util: 29.6.3 - jsdom: 20.0.3 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - dev: true - - /jest-environment-node@29.5.0: - resolution: {integrity: sha512-ExxuIK/+yQ+6PRGaHkKewYtg6hto2uGCgvKdb2nfJfKXgZ17DfXjvbZ+jA1Qt9A8EQSfPnt5FKIfnOO3u1h9qw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/environment': 29.6.4 - '@jest/fake-timers': 29.6.4 - '@jest/types': 29.6.3 - '@types/node': 18.16.9 - jest-mock: 29.6.3 - jest-util: 29.6.3 - - /jest-get-type@29.4.3: - resolution: {integrity: sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - /jest-haste-map@29.5.0: - resolution: {integrity: sha512-IspOPnnBro8YfVYSw6yDRKh/TiCdRngjxeacCps1cQ9cgVN6+10JUcuJ1EabrgYLOATsIAigxA0rLR9x/YlrSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.5.0 - '@types/graceful-fs': 4.1.6 - '@types/node': 18.16.9 - anymatch: 3.1.3 - fb-watchman: 2.0.2 - graceful-fs: 4.2.11 - jest-regex-util: 29.4.3 - jest-util: 29.5.0 - jest-worker: 29.5.0 - micromatch: 4.0.5 - walker: 1.0.8 - optionalDependencies: - fsevents: 2.3.2 - - /jest-haste-map@29.6.4: - resolution: {integrity: sha512-12Ad+VNTDHxKf7k+M65sviyynRoZYuL1/GTuhEVb8RYsNSNln71nANRb/faSyWvx0j+gHcivChXHIoMJrGYjog==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.6.3 - '@types/graceful-fs': 4.1.6 - '@types/node': 18.16.9 - anymatch: 3.1.3 - fb-watchman: 2.0.2 - graceful-fs: 4.2.11 - jest-regex-util: 29.6.3 - jest-util: 29.6.3 - jest-worker: 29.6.4 - micromatch: 4.0.5 - walker: 1.0.8 - optionalDependencies: - fsevents: 2.3.2 - - /jest-leak-detector@29.5.0: - resolution: {integrity: sha512-u9YdeeVnghBUtpN5mVxjID7KbkKE1QU4f6uUwuxiY0vYRi9BUCLKlPEZfDGR67ofdFmDz9oPAy2G92Ujrntmow==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - jest-get-type: 29.4.3 - pretty-format: 29.5.0 - - /jest-matcher-utils@29.5.0: - resolution: {integrity: sha512-lecRtgm/rjIK0CQ7LPQwzCs2VwW6WAahA55YBuI+xqmhm7LAaxokSB8C97yJeYyT+HvQkH741StzpU41wohhWw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - chalk: 4.1.2 - jest-diff: 29.5.0 - jest-get-type: 29.4.3 - pretty-format: 29.5.0 - - /jest-message-util@29.5.0: - resolution: {integrity: sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@babel/code-frame': 7.21.4 - '@jest/types': 29.5.0 - '@types/stack-utils': 2.0.1 - chalk: 4.1.2 - graceful-fs: 4.2.11 - micromatch: 4.0.5 - pretty-format: 29.5.0 - slash: 3.0.0 - stack-utils: 2.0.6 - - /jest-message-util@29.6.3: - resolution: {integrity: sha512-FtzaEEHzjDpQp51HX4UMkPZjy46ati4T5pEMyM6Ik48ztu4T9LQplZ6OsimHx7EuM9dfEh5HJa6D3trEftu3dA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@babel/code-frame': 7.21.4 - '@jest/types': 29.6.3 - '@types/stack-utils': 2.0.1 - chalk: 4.1.2 - graceful-fs: 4.2.11 - micromatch: 4.0.5 - pretty-format: 29.6.3 - slash: 3.0.0 - stack-utils: 2.0.6 - - /jest-mock@29.5.0: - resolution: {integrity: sha512-GqOzvdWDE4fAV2bWQLQCkujxYWL7RxjCnj71b5VhDAGOevB3qj3Ovg26A5NI84ZpODxyzaozXLOh2NCgkbvyaw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.5.0 - '@types/node': 18.16.9 - jest-util: 29.5.0 - - /jest-mock@29.6.3: - resolution: {integrity: sha512-Z7Gs/mOyTSR4yPsaZ72a/MtuK6RnC3JYqWONe48oLaoEcYwEDxqvbXz85G4SJrm2Z5Ar9zp6MiHF4AlFlRM4Pg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.6.3 - '@types/node': 18.16.9 - jest-util: 29.6.3 - - /jest-pnp-resolver@1.2.3(jest-resolve@29.5.0): - resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} - engines: {node: '>=6'} - peerDependencies: - jest-resolve: '*' - peerDependenciesMeta: - jest-resolve: - optional: true - dependencies: - jest-resolve: 29.5.0 - - /jest-regex-util@29.4.3: - resolution: {integrity: sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - /jest-regex-util@29.6.3: - resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - /jest-resolve-dependencies@29.5.0: - resolution: {integrity: sha512-sjV3GFr0hDJMBpYeUuGduP+YeCRbd7S/ck6IvL3kQ9cpySYKqcqhdLLC2rFwrcL7tz5vYibomBrsFYWkIGGjOg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - jest-regex-util: 29.4.3 - jest-snapshot: 29.5.0 - transitivePeerDependencies: - - supports-color - - /jest-resolve@29.5.0: - resolution: {integrity: sha512-1TzxJ37FQq7J10jPtQjcc+MkCkE3GBpBecsSUWJ0qZNJpmg6m0D9/7II03yJulm3H/fvVjgqLh/k2eYg+ui52w==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - chalk: 4.1.2 - graceful-fs: 4.2.11 - jest-haste-map: 29.5.0 - jest-pnp-resolver: 1.2.3(jest-resolve@29.5.0) - jest-util: 29.5.0 - jest-validate: 29.5.0 - resolve: 1.22.2 - resolve.exports: 2.0.2 - slash: 3.0.0 - - /jest-runner@29.5.0: - resolution: {integrity: sha512-m7b6ypERhFghJsslMLhydaXBiLf7+jXy8FwGRHO3BGV1mcQpPbwiqiKUR2zU2NJuNeMenJmlFZCsIqzJCTeGLQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/console': 29.5.0 - '@jest/environment': 29.5.0 - '@jest/test-result': 29.5.0 - '@jest/transform': 29.5.0 - '@jest/types': 29.5.0 - '@types/node': 18.16.9 - chalk: 4.1.2 - emittery: 0.13.1 - graceful-fs: 4.2.11 - jest-docblock: 29.4.3 - jest-environment-node: 29.5.0 - jest-haste-map: 29.5.0 - jest-leak-detector: 29.5.0 - jest-message-util: 29.5.0 - jest-resolve: 29.5.0 - jest-runtime: 29.5.0 - jest-util: 29.5.0 - jest-watcher: 29.5.0 - jest-worker: 29.5.0 - p-limit: 3.1.0 - source-map-support: 0.5.13 - transitivePeerDependencies: - - supports-color - - /jest-runtime@29.5.0: - resolution: {integrity: sha512-1Hr6Hh7bAgXQP+pln3homOiEZtCDZFqwmle7Ew2j8OlbkIu6uE3Y/etJQG8MLQs3Zy90xrp2C0BRrtPHG4zryw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/environment': 29.5.0 - '@jest/fake-timers': 29.5.0 - '@jest/globals': 29.5.0 - '@jest/source-map': 29.4.3 - '@jest/test-result': 29.5.0 - '@jest/transform': 29.5.0 - '@jest/types': 29.5.0 - '@types/node': 18.16.9 - chalk: 4.1.2 - cjs-module-lexer: 1.2.2 - collect-v8-coverage: 1.0.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-haste-map: 29.5.0 - jest-message-util: 29.5.0 - jest-mock: 29.5.0 - jest-regex-util: 29.4.3 - jest-resolve: 29.5.0 - jest-snapshot: 29.5.0 - jest-util: 29.5.0 - slash: 3.0.0 - strip-bom: 4.0.0 - transitivePeerDependencies: - - supports-color - - /jest-snapshot@29.5.0: - resolution: {integrity: sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@babel/core': 7.21.8 - '@babel/generator': 7.21.5 - '@babel/plugin-syntax-jsx': 7.21.4(@babel/core@7.21.8) - '@babel/plugin-syntax-typescript': 7.21.4(@babel/core@7.21.8) - '@babel/traverse': 7.23.7 - '@babel/types': 7.21.5 - '@jest/expect-utils': 29.5.0 - '@jest/transform': 29.5.0 - '@jest/types': 29.5.0 - '@types/babel__traverse': 7.18.5 - '@types/prettier': 2.7.2 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.21.8) - chalk: 4.1.2 - expect: 29.5.0 - graceful-fs: 4.2.11 - jest-diff: 29.5.0 - jest-get-type: 29.4.3 - jest-matcher-utils: 29.5.0 - jest-message-util: 29.5.0 - jest-util: 29.5.0 - natural-compare: 1.4.0 - pretty-format: 29.5.0 - semver: 7.5.1 - transitivePeerDependencies: - - supports-color - - /jest-util@29.5.0: - resolution: {integrity: sha512-RYMgG/MTadOr5t8KdhejfvUU82MxsCu5MF6KuDUHl+NuwzUt+Sm6jJWxTJVrDR1j5M/gJVCPKQEpWXY+yIQ6lQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.5.0 - '@types/node': 18.16.9 - chalk: 4.1.2 - ci-info: 3.8.0 - graceful-fs: 4.2.11 - picomatch: 2.3.1 - - /jest-util@29.6.3: - resolution: {integrity: sha512-QUjna/xSy4B32fzcKTSz1w7YYzgiHrjjJjevdRf61HYk998R5vVMMNmrHESYZVDS5DSWs+1srPLPKxXPkeSDOA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.6.3 - '@types/node': 18.16.9 - chalk: 4.1.2 - ci-info: 3.8.0 - graceful-fs: 4.2.11 - picomatch: 2.3.1 - - /jest-validate@29.5.0: - resolution: {integrity: sha512-pC26etNIi+y3HV8A+tUGr/lph9B18GnzSRAkPaaZJIE1eFdiYm6/CewuiJQ8/RlfHd1u/8Ioi8/sJ+CmbA+zAQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.5.0 - camelcase: 6.3.0 - chalk: 4.1.2 - jest-get-type: 29.4.3 - leven: 3.1.0 - pretty-format: 29.5.0 - - /jest-watcher@29.5.0: - resolution: {integrity: sha512-KmTojKcapuqYrKDpRwfqcQ3zjMlwu27SYext9pt4GlF5FUgB+7XE1mcCnSm6a4uUpFyQIkb6ZhzZvHl+jiBCiA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/test-result': 29.5.0 - '@jest/types': 29.5.0 - '@types/node': 18.16.9 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - emittery: 0.13.1 - jest-util: 29.5.0 - string-length: 4.0.2 - - /jest-worker@29.5.0: - resolution: {integrity: sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@types/node': 18.16.9 - jest-util: 29.5.0 - merge-stream: 2.0.0 - supports-color: 8.1.1 - - /jest-worker@29.6.4: - resolution: {integrity: sha512-6dpvFV4WjcWbDVGgHTWo/aupl8/LbBx2NSKfiwqf79xC/yeJjKHT1+StcKy/2KTmW16hE68ccKVOtXf+WZGz7Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@types/node': 18.16.9 - jest-util: 29.6.3 - merge-stream: 2.0.0 - supports-color: 8.1.1 - - /jest@29.5.0(@types/node@18.16.9): - resolution: {integrity: sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - dependencies: - '@jest/core': 29.5.0 - '@jest/types': 29.5.0 - import-local: 3.1.0 - jest-cli: 29.5.0(@types/node@18.16.9) - transitivePeerDependencies: - - '@types/node' - - supports-color - - ts-node - - /jiti@1.18.2: - resolution: {integrity: sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==} - hasBin: true - dev: true - - /js-base64@3.7.5: - resolution: {integrity: sha512-3MEt5DTINKqfScXKfJFrRbxkrnk2AxPWGBL/ycjz4dK8iqiSJ06UxD8jh8xuh6p10TX4t2+7FsBYVxxQbMg+qA==} - dev: false - - /js-sdsl@4.4.0: - resolution: {integrity: sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==} - dev: true - - /js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - /js-yaml@3.14.1: - resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} - hasBin: true - dependencies: - argparse: 1.0.10 - esprima: 4.0.1 - - /js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true - dependencies: - argparse: 2.0.1 - dev: true - - /jsdom@20.0.3: - resolution: {integrity: sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==} - engines: {node: '>=14'} - peerDependencies: - canvas: ^2.5.0 - peerDependenciesMeta: - canvas: - optional: true - dependencies: - abab: 2.0.6 - acorn: 8.8.2 - acorn-globals: 7.0.1 - cssom: 0.5.0 - cssstyle: 2.3.0 - data-urls: 3.0.2 - decimal.js: 10.4.3 - domexception: 4.0.0 - escodegen: 2.1.0 - form-data: 4.0.0 - html-encoding-sniffer: 3.0.0 - http-proxy-agent: 5.0.0 - https-proxy-agent: 5.0.1 - is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.7 - parse5: 7.1.2 - saxes: 6.0.0 - symbol-tree: 3.2.4 - tough-cookie: 4.1.3 - w3c-xmlserializer: 4.0.0 - webidl-conversions: 7.0.0 - whatwg-encoding: 2.0.0 - whatwg-mimetype: 3.0.0 - whatwg-url: 11.0.0 - ws: 8.14.1 - xml-name-validator: 4.0.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - dev: true - - /jsesc@2.5.2: - resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} - engines: {node: '>=4'} - hasBin: true - - /json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - - /json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - dev: true - - /json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - dev: true - - /json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - - /jsonc-parser@3.2.0: - resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} - dev: true - - /jsonfile@6.1.0: - resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} - dependencies: - universalify: 2.0.0 - optionalDependencies: - graceful-fs: 4.2.11 - dev: true - - /jsx-ast-utils@3.3.3: - resolution: {integrity: sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==} - engines: {node: '>=4.0'} - dependencies: - array-includes: 3.1.6 - object.assign: 4.1.4 - dev: true - - /katex@0.16.7: - resolution: {integrity: sha512-Xk9C6oGKRwJTfqfIbtr0Kes9OSv6IFsuhFGc7tW4urlpMJtuh+7YhzU6YEG9n8gmWKcMAFzkp7nr+r69kV0zrA==} - hasBin: true - dependencies: - commander: 8.3.0 - dev: false - - /keycode@2.2.1: - resolution: {integrity: sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg==} - dev: false - - /kleur@3.0.3: - resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} - engines: {node: '>=6'} - - /leven@3.1.0: - resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} - engines: {node: '>=6'} - - /levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - dev: true - - /lib0@0.2.74: - resolution: {integrity: sha512-roj9i46/JwG5ik5KNTkxP2IytlnrssAkD/OhlAVtE+GqectrdkfR+pttszVLrOzMDeXNs1MPt6yo66MUolWSiA==} - engines: {node: '>=14'} - hasBin: true - dependencies: - isomorphic.js: 0.2.5 - dev: false - - /lib0@0.2.88: - resolution: {integrity: sha512-KyroiEvCeZcZEMx5Ys+b4u4eEBbA1ch7XUaBhYpwa/nPMrzTjUhI4RfcytmQfYoTBPcdyx+FX6WFNIoNuJzJfQ==} - engines: {node: '>=16'} - hasBin: true - dependencies: - isomorphic.js: 0.2.5 - dev: false - - /lilconfig@2.1.0: - resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} - engines: {node: '>=10'} - dev: true - - /lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - - /locate-path@5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} - engines: {node: '>=8'} - dependencies: - p-locate: 4.1.0 - - /locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - dependencies: - p-locate: 5.0.0 - dev: true - - /lodash-es@4.17.21: - resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} - dev: false - - /lodash.clonedeep@4.5.0: - resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} - - /lodash.isequal@4.5.0: - resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} - - /lodash.memoize@4.1.2: - resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} - dev: true - - /lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - dev: true - - /lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - - /loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true - dependencies: - js-tokens: 4.0.0 - - /lower-case@2.0.2: - resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} - dependencies: - tslib: 2.5.0 - dev: true - - /lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - dependencies: - yallist: 3.1.1 - - /lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} - dependencies: - yallist: 4.0.0 - - /luxon@3.4.4: - resolution: {integrity: sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==} - engines: {node: '>=12'} - dev: false - - /magic-string@0.27.0: - resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==} - engines: {node: '>=12'} - dependencies: - '@jridgewell/sourcemap-codec': 1.4.15 - dev: true - - /make-dir@3.1.0: - resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} - engines: {node: '>=8'} - dependencies: - semver: 6.3.0 - - /make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - dev: true - - /makeerror@1.0.12: - resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} - dependencies: - tmpl: 1.0.5 - - /material-colors@1.2.6: - resolution: {integrity: sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==} - dev: false - - /mdn-data@2.0.28: - resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} - dev: true - - /mdn-data@2.0.30: - resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} - dev: true - - /memoize-one@5.2.1: - resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} - dev: false - - /memoize-one@6.0.0: - resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} - dev: false - - /merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - - /merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - dev: true - - /micromatch@4.0.5: - resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} - engines: {node: '>=8.6'} - dependencies: - braces: 3.0.2 - picomatch: 2.3.1 - - /mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - dev: true - - /mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - dependencies: - mime-db: 1.52.0 - dev: true - - /mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - - /minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - dependencies: - brace-expansion: 1.1.11 - - /minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - dev: true - - /mkdirp@1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} - hasBin: true - dev: true - - /moment-timezone@0.5.44: - resolution: {integrity: sha512-nv3YpzI/8lkQn0U6RkLd+f0W/zy/JnoR5/EyPz/dNkPTBjA2jNLCVxaiQ8QpeLymhSZvX0wCL5s27NQWdOPwAw==} - dependencies: - moment: 2.30.1 - dev: false - - /moment@2.30.1: - resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} - dev: false - - /ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - - /mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - dependencies: - any-promise: 1.3.0 - object-assign: 4.1.1 - thenify-all: 1.6.0 - dev: true - - /nanoid@3.3.6: - resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - dev: true - - /nanoid@4.0.2: - resolution: {integrity: sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==} - engines: {node: ^14 || ^16 || >=18} - hasBin: true - dev: false - - /natural-compare-lite@1.4.0: - resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} - dev: true - - /natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - - /no-case@3.0.4: - resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} - dependencies: - lower-case: 2.0.2 - tslib: 2.5.0 - dev: true - - /node-int64@0.4.0: - resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} - - /node-releases@2.0.10: - resolution: {integrity: sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==} - - /normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - /normalize-range@0.1.2: - resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} - engines: {node: '>=0.10.0'} - dev: true - - /npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} - dependencies: - path-key: 3.1.1 - - /nth-check@2.1.1: - resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - dependencies: - boolbase: 1.0.0 - dev: true - - /nwsapi@2.2.7: - resolution: {integrity: sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==} - dev: true - - /object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - /object-hash@3.0.0: - resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} - engines: {node: '>= 6'} - dev: true - - /object-inspect@1.12.3: - resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} - dev: true - - /object-is@1.1.5: - resolution: {integrity: sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - dev: false - - /object-keys@1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} - engines: {node: '>= 0.4'} - - /object.assign@4.1.4: - resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - has-symbols: 1.0.3 - object-keys: 1.1.1 - dev: true - - /object.entries@1.1.6: - resolution: {integrity: sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 - dev: true - - /object.fromentries@2.0.6: - resolution: {integrity: sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 - dev: true - - /object.hasown@1.1.2: - resolution: {integrity: sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==} - dependencies: - define-properties: 1.2.0 - es-abstract: 1.21.2 - dev: true - - /object.values@1.1.6: - resolution: {integrity: sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 - dev: true - - /once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - dependencies: - wrappy: 1.0.2 - - /onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - dependencies: - mimic-fn: 2.1.0 - - /optionator@0.9.1: - resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==} - engines: {node: '>= 0.8.0'} - dependencies: - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 - word-wrap: 1.2.3 - dev: true - - /p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} - dependencies: - p-try: 2.2.0 - - /p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - dependencies: - yocto-queue: 0.1.0 - - /p-locate@4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} - engines: {node: '>=8'} - dependencies: - p-limit: 2.3.0 - - /p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - dependencies: - p-limit: 3.1.0 - dev: true - - /p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} - - /param-case@3.0.4: - resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} - dependencies: - dot-case: 3.0.4 - tslib: 2.5.0 - dev: true - - /parchment@1.1.4: - resolution: {integrity: sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==} - - /parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - dependencies: - callsites: 3.1.0 - - /parse-json@5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} - dependencies: - '@babel/code-frame': 7.21.4 - error-ex: 1.3.2 - json-parse-even-better-errors: 2.3.1 - lines-and-columns: 1.2.4 - - /parse5@7.1.2: - resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} - dependencies: - entities: 4.5.0 - dev: true - - /pascal-case@3.1.2: - resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} - dependencies: - no-case: 3.0.4 - tslib: 2.5.0 - dev: true - - /path-case@3.0.4: - resolution: {integrity: sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==} - dependencies: - dot-case: 3.0.4 - tslib: 2.5.0 - dev: true - - /path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - - /path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - - /path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - /path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - - /path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - - /performance-now@2.1.0: - resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} - dev: false - - /picocolors@1.0.0: - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - - /picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - - /pify@2.3.0: - resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} - engines: {node: '>=0.10.0'} - dev: true - - /pirates@4.0.5: - resolution: {integrity: sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==} - engines: {node: '>= 6'} - - /pkg-dir@4.2.0: - resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} - engines: {node: '>=8'} - dependencies: - find-up: 4.1.0 - - /postcss-import@15.1.0(postcss@8.4.23): - resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} - engines: {node: '>=14.0.0'} - peerDependencies: - postcss: ^8.0.0 - dependencies: - postcss: 8.4.23 - postcss-value-parser: 4.2.0 - read-cache: 1.0.0 - resolve: 1.22.2 - dev: true - - /postcss-js@4.0.1(postcss@8.4.23): - resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} - engines: {node: ^12 || ^14 || >= 16} - peerDependencies: - postcss: ^8.4.21 - dependencies: - camelcase-css: 2.0.1 - postcss: 8.4.23 - dev: true - - /postcss-load-config@4.0.1(postcss@8.4.23): - resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==} - engines: {node: '>= 14'} - peerDependencies: - postcss: '>=8.0.9' - ts-node: '>=9.0.0' - peerDependenciesMeta: - postcss: - optional: true - ts-node: - optional: true - dependencies: - lilconfig: 2.1.0 - postcss: 8.4.23 - yaml: 2.2.2 - dev: true - - /postcss-nested@6.0.1(postcss@8.4.23): - resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==} - engines: {node: '>=12.0'} - peerDependencies: - postcss: ^8.2.14 - dependencies: - postcss: 8.4.23 - postcss-selector-parser: 6.0.12 - dev: true - - /postcss-selector-parser@6.0.12: - resolution: {integrity: sha512-NdxGCAZdRrwVI1sy59+Wzrh+pMMHxapGnpfenDVlMEXoOcvt4pGE0JLK9YY2F5dLxcFYA/YbVQKhcGU+FtSYQg==} - engines: {node: '>=4'} - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - dev: true - - /postcss-value-parser@4.2.0: - resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - dev: true - - /postcss@8.4.23: - resolution: {integrity: sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==} - engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: 3.3.6 - picocolors: 1.0.0 - source-map-js: 1.0.2 - dev: true - - /prefix-style@2.0.1: - resolution: {integrity: sha512-gdr1MBNVT0drzTq95CbSNdsrBDoHGlb2aDJP/FoY+1e+jSDPOb1Cv554gH2MGiSr2WTcXi/zu+NaFzfcHQkfBQ==} - dev: false - - /prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - dev: true - - /prettier-plugin-tailwindcss@0.2.8(prettier@2.8.4): - resolution: {integrity: sha512-KgPcEnJeIijlMjsA6WwYgRs5rh3/q76oInqtMXBA/EMcamrcYJpyhtRhyX1ayT9hnHlHTuO8sIifHF10WuSDKg==} - engines: {node: '>=12.17.0'} - peerDependencies: - '@ianvs/prettier-plugin-sort-imports': '*' - '@prettier/plugin-pug': '*' - '@shopify/prettier-plugin-liquid': '*' - '@shufo/prettier-plugin-blade': '*' - '@trivago/prettier-plugin-sort-imports': '*' - prettier: '>=2.2.0' - prettier-plugin-astro: '*' - prettier-plugin-css-order: '*' - prettier-plugin-import-sort: '*' - prettier-plugin-jsdoc: '*' - prettier-plugin-organize-attributes: '*' - prettier-plugin-organize-imports: '*' - prettier-plugin-style-order: '*' - prettier-plugin-svelte: '*' - prettier-plugin-twig-melody: '*' - peerDependenciesMeta: - '@ianvs/prettier-plugin-sort-imports': - optional: true - '@prettier/plugin-pug': - optional: true - '@shopify/prettier-plugin-liquid': - optional: true - '@shufo/prettier-plugin-blade': - optional: true - '@trivago/prettier-plugin-sort-imports': - optional: true - prettier-plugin-astro: - optional: true - prettier-plugin-css-order: - optional: true - prettier-plugin-import-sort: - optional: true - prettier-plugin-jsdoc: - optional: true - prettier-plugin-organize-attributes: - optional: true - prettier-plugin-organize-imports: - optional: true - prettier-plugin-style-order: - optional: true - prettier-plugin-svelte: - optional: true - prettier-plugin-twig-melody: - optional: true - dependencies: - prettier: 2.8.4 - dev: true - - /prettier@2.8.4: - resolution: {integrity: sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==} - engines: {node: '>=10.13.0'} - hasBin: true - dev: true - - /pretty-format@29.5.0: - resolution: {integrity: sha512-V2mGkI31qdttvTFX7Mt4efOqHXqJWMu4/r66Xh3Z3BwZaPfPJgp6/gbwoujRpPUtfEF6AUUWx3Jim3GCw5g/Qw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/schemas': 29.4.3 - ansi-styles: 5.2.0 - react-is: 18.2.0 - - /pretty-format@29.6.3: - resolution: {integrity: sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/schemas': 29.6.3 - ansi-styles: 5.2.0 - react-is: 18.2.0 - - /prismjs@1.29.0: - resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==} - engines: {node: '>=6'} - dev: false - - /prompts@2.4.2: - resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} - engines: {node: '>= 6'} - dependencies: - kleur: 3.0.3 - sisteransi: 1.0.5 - - /prop-types@15.8.1: - resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - react-is: 16.13.1 - - /protoc-gen-ts@0.8.7: - resolution: {integrity: sha512-jr4VJey2J9LVYCV7EVyVe53g1VMw28cCmYJhBe5e3YX5wiyiDwgxWxeDf9oTqAe4P1bN/YGAkW2jhlH8LohwiQ==} - hasBin: true - dev: false - - /proxy-compare@2.5.1: - resolution: {integrity: sha512-oyfc0Tx87Cpwva5ZXezSp5V9vht1c7dZBhvuV/y3ctkgMVUmiAGDVeeB0dKhGSyT0v1ZTEQYpe/RXlBVBNuCLA==} - dev: false - - /psl@1.9.0: - resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} - dev: true - - /punycode@2.3.0: - resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} - engines: {node: '>=6'} - dev: true - - /pure-rand@6.0.2: - resolution: {integrity: sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==} - - /querystringify@2.2.0: - resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} - dev: true - - /queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - dev: true - - /quill-delta@3.6.3: - resolution: {integrity: sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==} - engines: {node: '>=0.10'} - dependencies: - deep-equal: 1.1.1 - extend: 3.0.2 - fast-diff: 1.1.2 - dev: false - - /quill-delta@4.2.2: - resolution: {integrity: sha512-qjbn82b/yJzOjstBgkhtBjN2TNK+ZHP/BgUQO+j6bRhWQQdmj2lH6hXG7+nwwLF41Xgn//7/83lxs9n2BkTtTg==} - dependencies: - fast-diff: 1.2.0 - lodash.clonedeep: 4.5.0 - lodash.isequal: 4.5.0 - dev: true - - /quill-delta@5.1.0: - resolution: {integrity: sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==} - engines: {node: '>= 12.0.0'} - dependencies: - fast-diff: 1.3.0 - lodash.clonedeep: 4.5.0 - lodash.isequal: 4.5.0 - dev: false - - /quill@1.3.7: - resolution: {integrity: sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==} - dependencies: - clone: 2.1.2 - deep-equal: 1.1.1 - eventemitter3: 2.0.3 - extend: 3.0.2 - parchment: 1.1.4 - quill-delta: 3.6.3 - dev: false - - /raf-schd@4.0.3: - resolution: {integrity: sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==} - dev: false - - /raf@3.4.1: - resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==} - dependencies: - performance-now: 2.1.0 - dev: false - - /react-beautiful-dnd@13.1.1(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==} - peerDependencies: - react: ^16.8.5 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.5 || ^17.0.0 || ^18.0.0 - dependencies: - '@babel/runtime': 7.21.5 - css-box-model: 1.2.1 - memoize-one: 5.2.1 - raf-schd: 4.0.3 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-redux: 7.2.9(react-dom@18.2.0)(react@18.2.0) - redux: 4.2.1 - use-memo-one: 1.1.3(react@18.2.0) - transitivePeerDependencies: - - react-native - dev: false - - /react-big-calendar@1.8.5(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-cra8WPfoTSQthFfqxi0k9xm/Shv5jWSw19LkNzpSJcnQhP6XGes/eJjd8P8g/iwaJjXIWPpg3+HB5wO5wabRyA==} - peerDependencies: - react: ^16.14.0 || ^17 || ^18 - react-dom: ^16.14.0 || ^17 || ^18 - dependencies: - '@babel/runtime': 7.23.4 - clsx: 1.2.1 - date-arithmetic: 4.1.0 - dayjs: 1.11.9 - dom-helpers: 5.2.1 - globalize: 0.1.1 - invariant: 2.2.4 - lodash: 4.17.21 - lodash-es: 4.17.21 - luxon: 3.4.4 - memoize-one: 6.0.0 - moment: 2.30.1 - moment-timezone: 0.5.44 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-overlays: 5.2.1(react-dom@18.2.0)(react@18.2.0) - uncontrollable: 7.2.1(react@18.2.0) - dev: false - - /react-color@2.19.3(react@18.2.0): - resolution: {integrity: sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA==} - peerDependencies: - react: '*' - dependencies: - '@icons/material': 0.2.4(react@18.2.0) - lodash: 4.17.21 - lodash-es: 4.17.21 - material-colors: 1.2.6 - prop-types: 15.8.1 - react: 18.2.0 - reactcss: 1.2.3(react@18.2.0) - tinycolor2: 1.6.0 - dev: false - - /react-custom-scrollbars@4.2.1(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-VtJTUvZ7kPh/auZWIbBRceGPkE30XBYe+HktFxuMWBR2eVQQ+Ur6yFJMoaYcNpyGq22uYJ9Wx4UAEcC0K+LNPQ==} - peerDependencies: - react: ^0.14.0 || ^15.0.0 || ^16.0.0 - react-dom: ^0.14.0 || ^15.0.0 || ^16.0.0 - dependencies: - dom-css: 2.1.0 - prop-types: 15.8.1 - raf: 3.4.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /react-datepicker@4.23.0(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-w+msqlOZ14v6H1UknTKtZw/dw9naFMgAOspf59eY130gWpvy5dvKj/bgsFICDdvxB7PtKWxDcbGlAqCloY1d2A==} - peerDependencies: - react: ^16.9.0 || ^17 || ^18 - react-dom: ^16.9.0 || ^17 || ^18 - dependencies: - '@popperjs/core': 2.11.8 - classnames: 2.3.2 - date-fns: 2.30.0 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-onclickoutside: 6.13.0(react-dom@18.2.0)(react@18.2.0) - react-popper: 2.3.0(@popperjs/core@2.11.8)(react-dom@18.2.0)(react@18.2.0) - dev: false - - /react-dom@18.2.0(react@18.2.0): - resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} - peerDependencies: - react: ^18.2.0 - dependencies: - loose-envify: 1.4.0 - react: 18.2.0 - scheduler: 0.23.0 - - /react-error-boundary@3.1.4(react@18.2.0): - resolution: {integrity: sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==} - engines: {node: '>=10', npm: '>=6'} - peerDependencies: - react: '>=16.13.1' - dependencies: - '@babel/runtime': 7.21.5 - react: 18.2.0 - dev: false - - /react-event-listener@0.6.6(react@18.2.0): - resolution: {integrity: sha512-+hCNqfy7o9wvO6UgjqFmBzARJS7qrNoda0VqzvOuioEpoEXKutiKuv92dSz6kP7rYLmyHPyYNLesi5t/aH1gfw==} - peerDependencies: - react: ^16.3.0 - dependencies: - '@babel/runtime': 7.23.4 - prop-types: 15.8.1 - react: 18.2.0 - warning: 4.0.3 - dev: false - - /react-fast-compare@3.2.2: - resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} - - /react-hot-toast@2.4.1(csstype@3.1.2)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==} - engines: {node: '>=10'} - peerDependencies: - react: '>=16' - react-dom: '>=16' - dependencies: - goober: 2.1.13(csstype@3.1.2) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - transitivePeerDependencies: - - csstype - dev: false - - /react-i18next@12.2.2(i18next@22.4.15)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-KBB6buBmVKXUWNxXHdnthp+38gPyBT46hJCAIQ8rX19NFL/m2ahte2KARfIDf2tMnSAL7wwck6eDOd/9zn6aFg==} - peerDependencies: - i18next: '>= 19.0.0' - react: '>= 16.8.0' - react-dom: '*' - react-native: '*' - peerDependenciesMeta: - react-dom: - optional: true - react-native: - optional: true - dependencies: - '@babel/runtime': 7.21.5 - html-parse-stringify: 3.0.1 - i18next: 22.4.15 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /react-is@16.13.1: - resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - - /react-is@17.0.2: - resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - dev: false - - /react-is@18.2.0: - resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} - - /react-katex@3.0.1(prop-types@15.8.1)(react@18.2.0): - resolution: {integrity: sha512-wIUW1fU5dHlkKvq4POfDkHruQsYp3fM8xNb/jnc8dnQ+nNCnaj0sx5pw7E6UyuEdLRyFKK0HZjmXBo+AtXXy0A==} - peerDependencies: - prop-types: ^15.8.1 - react: '>=15.3.2 <=18' - dependencies: - katex: 0.16.7 - prop-types: 15.8.1 - react: 18.2.0 - dev: false - - /react-lifecycles-compat@3.0.4: - resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==} - dev: false - - /react-onclickoutside@6.13.0(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A==} - peerDependencies: - react: ^15.5.x || ^16.x || ^17.x || ^18.x - react-dom: ^15.5.x || ^16.x || ^17.x || ^18.x - dependencies: - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /react-overlays@5.2.1(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-GLLSOLWr21CqtJn8geSwQfoJufdt3mfdsnIiQswouuQ2MMPns+ihZklxvsTDKD3cR2tF8ELbi5xUsvqVhR6WvA==} - peerDependencies: - react: '>=16.3.0' - react-dom: '>=16.3.0' - dependencies: - '@babel/runtime': 7.23.4 - '@popperjs/core': 2.11.8 - '@restart/hooks': 0.4.15(react@18.2.0) - '@types/warning': 3.0.3 - dom-helpers: 5.2.1 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - uncontrollable: 7.2.1(react@18.2.0) - warning: 4.0.3 - dev: false - - /react-popper@2.3.0(@popperjs/core@2.11.8)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==} - peerDependencies: - '@popperjs/core': ^2.0.0 - react: ^16.8.0 || ^17 || ^18 - react-dom: ^16.8.0 || ^17 || ^18 - dependencies: - '@popperjs/core': 2.11.8 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-fast-compare: 3.2.2 - warning: 4.0.3 - - /react-redux@7.2.9(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==} - peerDependencies: - react: ^16.8.3 || ^17 || ^18 - react-dom: '*' - react-native: '*' - peerDependenciesMeta: - react-dom: - optional: true - react-native: - optional: true - dependencies: - '@babel/runtime': 7.23.4 - '@types/react-redux': 7.1.25 - hoist-non-react-statics: 3.3.2 - loose-envify: 1.4.0 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-is: 17.0.2 - dev: false - - /react-redux@8.0.5(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1): - resolution: {integrity: sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==} - peerDependencies: - '@types/react': ^16.8 || ^17.0 || ^18.0 - '@types/react-dom': ^16.8 || ^17.0 || ^18.0 - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - react-native: '>=0.59' - redux: ^4 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - react-dom: - optional: true - react-native: - optional: true - redux: - optional: true - dependencies: - '@babel/runtime': 7.21.5 - '@types/hoist-non-react-statics': 3.3.1 - '@types/react': 18.2.6 - '@types/react-dom': 18.2.4 - '@types/use-sync-external-store': 0.0.3 - hoist-non-react-statics: 3.3.2 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-is: 18.2.0 - redux: 4.2.1 - use-sync-external-store: 1.2.0(react@18.2.0) - dev: false - - /react-refresh@0.14.0: - resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==} - engines: {node: '>=0.10.0'} - dev: true - - /react-router-dom@6.11.1(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-dPC2MhoPeTQ1YUOt5uIK376SMNWbwUxYRWk2ZmTT4fZfwlOvabF8uduRKKJIyfkCZvMgiF0GSCQckmkGGijIrg==} - engines: {node: '>=14'} - peerDependencies: - react: '>=16.8' - react-dom: '>=16.8' - dependencies: - '@remix-run/router': 1.6.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-router: 6.11.1(react@18.2.0) - dev: false - - /react-router@6.11.1(react@18.2.0): - resolution: {integrity: sha512-OZINSdjJ2WgvAi7hgNLazrEV8SGn6xrKA+MkJe9wVDMZ3zQ6fdJocUjpCUCI0cNrelWjcvon0S/QK/j0NzL3KA==} - engines: {node: '>=14'} - peerDependencies: - react: '>=16.8' - dependencies: - '@remix-run/router': 1.6.1 - react: 18.2.0 - dev: false - - /react-swipeable-views-core@0.14.0: - resolution: {integrity: sha512-0W/e9uPweNEOSPjmYtuKSC/SvKKg1sfo+WtPdnxeLF3t2L82h7jjszuOHz9C23fzkvLfdgkaOmcbAxE9w2GEjA==} - engines: {node: '>=6.0.0'} - dependencies: - '@babel/runtime': 7.0.0 - warning: 4.0.3 - dev: false - - /react-swipeable-views-utils@0.14.0(react@18.2.0): - resolution: {integrity: sha512-W+fXBOsDqgFK1/g7MzRMVcDurp3LqO3ksC8UgInh2P/tKgb5DusuuB1geKHFc6o1wKl+4oyER4Zh3Lxmr8xbXA==} - engines: {node: '>=6.0.0'} - dependencies: - '@babel/runtime': 7.0.0 - keycode: 2.2.1 - prop-types: 15.8.1 - react-event-listener: 0.6.6(react@18.2.0) - react-swipeable-views-core: 0.14.0 - shallow-equal: 1.2.1 - transitivePeerDependencies: - - react - dev: false - - /react-swipeable-views@0.14.0(react@18.2.0): - resolution: {integrity: sha512-wrTT6bi2nC3JbmyNAsPXffUXLn0DVT9SbbcFr36gKpbaCgEp7rX/OFxsu5hPc/NBsUhHyoSRGvwqJNNrWTwCww==} - engines: {node: '>=6.0.0'} - peerDependencies: - react: ^15.3.0 || ^16.0.0 || ^17.0.0 - dependencies: - '@babel/runtime': 7.0.0 - prop-types: 15.8.1 - react: 18.2.0 - react-swipeable-views-core: 0.14.0 - react-swipeable-views-utils: 0.14.0(react@18.2.0) - warning: 4.0.3 - dev: false - - /react-transition-group@4.4.5(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} - peerDependencies: - react: '>=16.6.0' - react-dom: '>=16.6.0' - dependencies: - '@babel/runtime': 7.21.5 - dom-helpers: 5.2.1 - loose-envify: 1.4.0 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /react-virtualized-auto-sizer@1.0.20(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-OdIyHwj4S4wyhbKHOKM1wLSj/UDXm839Z3Cvfg2a9j+He6yDa6i5p0qQvEiCnyQlGO/HyfSnigQwuxvYalaAXA==} - peerDependencies: - react: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0-rc - react-dom: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0-rc - dependencies: - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /react-vtree@2.0.4(@types/react-window@1.8.8)(react-dom@18.2.0)(react-window@1.8.10)(react@18.2.0): - resolution: {integrity: sha512-UOld0VqyAZrryF06K753X4bcEVN6/wW831exvVlMZeZAVHk9KXnlHs4rpqDAeoiBgUwJqoW/rtn0hwsokRRxPA==} - peerDependencies: - '@types/react-window': ^1.8.2 - react: ^16.13.1 - react-dom: ^16.13.1 - react-window: ^1.8.5 - dependencies: - '@babel/runtime': 7.23.4 - '@types/react-window': 1.8.8 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-window: 1.8.10(react-dom@18.2.0)(react@18.2.0) - dev: false - - /react-window@1.8.10(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-Y0Cx+dnU6NLa5/EvoHukUD0BklJ8qITCtVEPY1C/nL8wwoZ0b5aEw8Ff1dOVHw7fCzMt55XfJDd8S8W8LCaUCg==} - engines: {node: '>8.0.0'} - peerDependencies: - react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 - react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 - dependencies: - '@babel/runtime': 7.23.4 - memoize-one: 5.2.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /react18-input-otp@1.1.3(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-55dZMVX61In2ngUhA4Fv0NMY4j5RZjxrJaSOAnJGJmkAhxKB6puVHYEmipyy2+W2CPydFF7pv+0NKzPUA03EVg==} - peerDependencies: - react: 16.2.0 - 18 - react-dom: 16.2.0 - 18 - dependencies: - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /react@18.2.0: - resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} - engines: {node: '>=0.10.0'} - dependencies: - loose-envify: 1.4.0 - - /reactcss@1.2.3(react@18.2.0): - resolution: {integrity: sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==} - peerDependencies: - react: '*' - dependencies: - lodash: 4.17.21 - react: 18.2.0 - dev: false - - /read-cache@1.0.0: - resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} - dependencies: - pify: 2.3.0 - dev: true - - /readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - dependencies: - picomatch: 2.3.1 - - /redux-thunk@3.1.0(redux@5.0.0): - resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} - peerDependencies: - redux: ^5.0.0 - dependencies: - redux: 5.0.0 - dev: false - - /redux@4.2.1: - resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==} - dependencies: - '@babel/runtime': 7.21.5 - dev: false - - /redux@5.0.0: - resolution: {integrity: sha512-blLIYmYetpZMET6Q6uCY7Jtl/Im5OBldy+vNPauA8vvsdqyt66oep4EUpAMWNHauTC6xa9JuRPhRB72rY82QGA==} - dev: false - - /regenerator-runtime@0.12.1: - resolution: {integrity: sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==} - dev: false - - /regenerator-runtime@0.13.11: - resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} - dev: false - - /regenerator-runtime@0.14.0: - resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==} - - /regexp.prototype.flags@1.5.0: - resolution: {integrity: sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - functions-have-names: 1.2.3 - - /require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - - /requires-port@1.0.0: - resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} - dev: true - - /reselect@5.0.1: - resolution: {integrity: sha512-D72j2ubjgHpvuCiORWkOUxndHJrxDaSolheiz5CO+roz8ka97/4msh2E8F5qay4GawR5vzBt5MkbDHT+Rdy/Wg==} - dev: false - - /resolve-cwd@3.0.0: - resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} - engines: {node: '>=8'} - dependencies: - resolve-from: 5.0.0 - - /resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - - /resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - - /resolve.exports@2.0.2: - resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} - engines: {node: '>=10'} - - /resolve@1.22.2: - resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==} - hasBin: true - dependencies: - is-core-module: 2.12.0 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - - /resolve@2.0.0-next.4: - resolution: {integrity: sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==} - hasBin: true - dependencies: - is-core-module: 2.12.0 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - dev: true - - /reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - dev: true - - /rimraf@2.7.1: - resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} - hasBin: true - dependencies: - glob: 7.2.3 - dev: true - - /rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - hasBin: true - dependencies: - glob: 7.2.3 - dev: true - - /rollup@3.21.7: - resolution: {integrity: sha512-KXPaEuR8FfUoK2uHwNjxTmJ18ApyvD6zJpYv9FOJSqLStmt6xOY84l1IjK2dSolQmoXknrhEFRaPRgOPdqCT5w==} - engines: {node: '>=14.18.0', npm: '>=8.0.0'} - hasBin: true - optionalDependencies: - fsevents: 2.3.2 - dev: true - - /run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - dependencies: - queue-microtask: 1.2.3 - dev: true - - /rxjs@7.8.1: - resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} - dependencies: - tslib: 2.5.0 - dev: false - - /safe-regex-test@1.0.0: - resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} - dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.1 - is-regex: 1.1.4 - dev: true - - /safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - dev: true - - /sass@1.70.0: - resolution: {integrity: sha512-uUxNQ3zAHeAx5nRFskBnrWzDUJrrvpCPD5FNAoRvTi0WwremlheES3tg+56PaVtCs5QDRX5CBLxxKMDJMEa1WQ==} - engines: {node: '>=14.0.0'} - hasBin: true - dependencies: - chokidar: 3.5.3 - immutable: 4.3.4 - source-map-js: 1.0.2 - - /saxes@6.0.0: - resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} - engines: {node: '>=v12.22.7'} - dependencies: - xmlchars: 2.2.0 - dev: true - - /scheduler@0.23.0: - resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} - dependencies: - loose-envify: 1.4.0 - - /scroll-into-view-if-needed@3.1.0: - resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==} - dependencies: - compute-scroll-into-view: 3.1.0 - dev: false - - /semver@6.3.0: - resolution: {integrity: sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==} - hasBin: true - - /semver@7.5.1: - resolution: {integrity: sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==} - engines: {node: '>=10'} - hasBin: true - dependencies: - lru-cache: 6.0.0 - - /semver@7.5.4: - resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} - engines: {node: '>=10'} - hasBin: true - dependencies: - lru-cache: 6.0.0 - dev: true - - /sentence-case@3.0.4: - resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==} - dependencies: - no-case: 3.0.4 - tslib: 2.5.0 - upper-case-first: 2.0.2 - dev: true - - /shallow-equal@1.2.1: - resolution: {integrity: sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==} - dev: false - - /shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - dependencies: - shebang-regex: 3.0.0 - - /shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - /side-channel@1.0.4: - resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} - dependencies: - call-bind: 1.0.2 - get-intrinsic: 1.2.1 - object-inspect: 1.12.3 - dev: true - - /signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - - /sisteransi@1.0.5: - resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - - /slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - - /slate-history@0.100.0(slate@0.101.4): - resolution: {integrity: sha512-x5rUuWLNtH97hs9PrFovGgt3Qc5zkTm/5mcUB+0NR/TK923eLax4HsL6xACLHMs245nI6aJElyM1y6hN0y5W/Q==} - peerDependencies: - slate: '>=0.65.3' - dependencies: - is-plain-object: 5.0.0 - slate: 0.101.4 - dev: false - - /slate-react@0.101.3(react-dom@18.2.0)(react@18.2.0)(slate@0.101.4): - resolution: {integrity: sha512-KMXK9FLeS7HYhhoVcI8SUi4Qp1I9C1lTQ2EgbPH95sVXfH/vq+hbhurEGIGCe0VQ9Opj4rSKJIv/g7De1+nJMA==} - peerDependencies: - react: '>=18.2.0' - react-dom: '>=18.2.0' - slate: '>=0.99.0' - dependencies: - '@juggle/resize-observer': 3.4.0 - '@types/is-hotkey': 0.1.10 - '@types/lodash': 4.14.202 - direction: 1.0.4 - is-hotkey: 0.2.0 - is-plain-object: 5.0.0 - lodash: 4.17.21 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - scroll-into-view-if-needed: 3.1.0 - slate: 0.101.4 - tiny-invariant: 1.3.1 - dev: false - - /slate@0.101.4: - resolution: {integrity: sha512-8LazZrNDsYFKDg1wpb0HouAfX5Pw/UmOZ/vIrtqD2GSCDZvraOkV2nVJ9Ery8kIlsU1jeybwgcaCy4KkVwfvEg==} - dependencies: - immer: 10.0.3 - is-plain-object: 5.0.0 - tiny-warning: 1.0.3 - dev: false - - /snake-case@3.0.4: - resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} - dependencies: - dot-case: 3.0.4 - tslib: 2.5.0 - dev: true - - /source-map-js@1.0.2: - resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} - engines: {node: '>=0.10.0'} - - /source-map-support@0.5.13: - resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 - - /source-map@0.5.7: - resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} - engines: {node: '>=0.10.0'} - dev: false - - /source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - - /sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - - /stack-utils@2.0.6: - resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} - engines: {node: '>=10'} - dependencies: - escape-string-regexp: 2.0.0 - - /string-length@4.0.2: - resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} - engines: {node: '>=10'} - dependencies: - char-regex: 1.0.2 - strip-ansi: 6.0.1 - - /string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - /string.prototype.matchall@4.0.8: - resolution: {integrity: sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 - get-intrinsic: 1.2.1 - has-symbols: 1.0.3 - internal-slot: 1.0.5 - regexp.prototype.flags: 1.5.0 - side-channel: 1.0.4 - dev: true - - /string.prototype.trim@1.2.7: - resolution: {integrity: sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 - dev: true - - /string.prototype.trimend@1.0.6: - resolution: {integrity: sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 - dev: true - - /string.prototype.trimstart@1.0.6: - resolution: {integrity: sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==} - dependencies: - call-bind: 1.0.2 - define-properties: 1.2.0 - es-abstract: 1.21.2 - dev: true - - /strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - dependencies: - ansi-regex: 5.0.1 - - /strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} - dev: true - - /strip-bom@4.0.0: - resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} - engines: {node: '>=8'} - - /strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - - /strip-json-comments@2.0.1: - resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} - engines: {node: '>=0.10.0'} - dev: true - - /strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - - /style-dictionary@3.8.0: - resolution: {integrity: sha512-wHlB/f5eO3mDcYv6WtOz6gvQC477jBKrwuIXe+PtHskTCBsJdAOvL8hCquczJxDui2TnwpeNE+2msK91JJomZg==} - engines: {node: '>=12.0.0'} - hasBin: true - dependencies: - chalk: 4.1.2 - change-case: 4.1.2 - commander: 8.3.0 - fs-extra: 10.1.0 - glob: 7.2.3 - json5: 2.2.3 - jsonc-parser: 3.2.0 - lodash: 4.17.21 - tinycolor2: 1.6.0 - dev: true - - /stylis@4.2.0: - resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} - dev: false - - /sucrase@3.32.0: - resolution: {integrity: sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==} - engines: {node: '>=8'} - hasBin: true - dependencies: - '@jridgewell/gen-mapping': 0.3.3 - commander: 4.1.1 - glob: 7.1.6 - lines-and-columns: 1.2.4 - mz: 2.7.0 - pirates: 4.0.5 - ts-interface-checker: 0.1.13 - dev: true - - /supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} - dependencies: - has-flag: 3.0.0 - - /supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - dependencies: - has-flag: 4.0.0 - - /supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} - dependencies: - has-flag: 4.0.0 - - /supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - - /svg-parser@2.0.4: - resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} - dev: true - - /svgo@3.0.2: - resolution: {integrity: sha512-Z706C1U2pb1+JGP48fbazf3KxHrWOsLme6Rv7imFBn5EnuanDW1GPaA/P1/dvObE670JDePC3mnj0k0B7P0jjQ==} - engines: {node: '>=14.0.0'} - hasBin: true - dependencies: - '@trysound/sax': 0.2.0 - commander: 7.2.0 - css-select: 5.1.0 - css-tree: 2.3.1 - csso: 5.0.5 - picocolors: 1.0.0 - dev: true - - /symbol-tree@3.2.4: - resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - dev: true - - /tailwindcss@3.3.2: - resolution: {integrity: sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==} - engines: {node: '>=14.0.0'} - hasBin: true - dependencies: - '@alloc/quick-lru': 5.2.0 - arg: 5.0.2 - chokidar: 3.5.3 - didyoumean: 1.2.2 - dlv: 1.1.3 - fast-glob: 3.2.12 - glob-parent: 6.0.2 - is-glob: 4.0.3 - jiti: 1.18.2 - lilconfig: 2.1.0 - micromatch: 4.0.5 - normalize-path: 3.0.0 - object-hash: 3.0.0 - picocolors: 1.0.0 - postcss: 8.4.23 - postcss-import: 15.1.0(postcss@8.4.23) - postcss-js: 4.0.1(postcss@8.4.23) - postcss-load-config: 4.0.1(postcss@8.4.23) - postcss-nested: 6.0.1(postcss@8.4.23) - postcss-selector-parser: 6.0.12 - postcss-value-parser: 4.2.0 - resolve: 1.22.2 - sucrase: 3.32.0 - transitivePeerDependencies: - - ts-node - dev: true - - /test-exclude@6.0.0: - resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} - engines: {node: '>=8'} - dependencies: - '@istanbuljs/schema': 0.1.3 - glob: 7.2.3 - minimatch: 3.1.2 - - /text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - dev: true - - /thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} - dependencies: - thenify: 3.3.1 - dev: true - - /thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - dependencies: - any-promise: 1.3.0 - dev: true - - /tiny-invariant@1.3.1: - resolution: {integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==} - dev: false - - /tiny-warning@1.0.3: - resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} - dev: false - - /tinycolor2@1.6.0: - resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} - - /tmpl@1.0.5: - resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} - - /to-camel-case@1.0.0: - resolution: {integrity: sha512-nD8pQi5H34kyu1QDMFjzEIYqk0xa9Alt6ZfrdEMuHCFOfTLhDG5pgTu/aAM9Wt9lXILwlXmWP43b8sav0GNE8Q==} - dependencies: - to-space-case: 1.0.0 - dev: false - - /to-fast-properties@2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} - engines: {node: '>=4'} - - /to-no-case@1.0.2: - resolution: {integrity: sha512-Z3g735FxuZY8rodxV4gH7LxClE4H0hTIyHNIHdk+vpQxjLm0cwnKXq/OFVZ76SOQmto7txVcwSCwkU5kqp+FKg==} - dev: false - - /to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - dependencies: - is-number: 7.0.0 - - /to-space-case@1.0.0: - resolution: {integrity: sha512-rLdvwXZ39VOn1IxGL3V6ZstoTbwLRckQmn/U8ZDLuWwIXNpuZDhQ3AiRUlhTbOXFVE9C+dR51wM0CBDhk31VcA==} - dependencies: - to-no-case: 1.0.2 - dev: false - - /tough-cookie@4.1.3: - resolution: {integrity: sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==} - engines: {node: '>=6'} - dependencies: - psl: 1.9.0 - punycode: 2.3.0 - universalify: 0.2.0 - url-parse: 1.5.10 - dev: true - - /tr46@3.0.0: - resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==} - engines: {node: '>=12'} - dependencies: - punycode: 2.3.0 - dev: true - - /tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true - dev: true - - /ts-interface-checker@0.1.13: - resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - dev: true - - /ts-jest@29.1.1(@babel/core@7.21.8)(babel-jest@29.6.2)(jest@29.5.0)(typescript@4.9.5): - resolution: {integrity: sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - peerDependencies: - '@babel/core': '>=7.0.0-beta.0 <8' - '@jest/types': ^29.0.0 - babel-jest: ^29.0.0 - esbuild: '*' - jest: ^29.0.0 - typescript: '>=4.3 <6' - peerDependenciesMeta: - '@babel/core': - optional: true - '@jest/types': - optional: true - babel-jest: - optional: true - esbuild: - optional: true - dependencies: - '@babel/core': 7.21.8 - babel-jest: 29.6.2(@babel/core@7.21.8) - bs-logger: 0.2.6 - fast-json-stable-stringify: 2.1.0 - jest: 29.5.0(@types/node@18.16.9) - jest-util: 29.5.0 - json5: 2.2.3 - lodash.memoize: 4.1.2 - make-error: 1.3.6 - semver: 7.5.4 - typescript: 4.9.5 - yargs-parser: 21.1.1 - dev: true - - /ts-node-dev@2.0.0(@types/node@18.16.9)(typescript@4.9.5): - resolution: {integrity: sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==} - engines: {node: '>=0.8.0'} - hasBin: true - peerDependencies: - node-notifier: '*' - typescript: '*' - peerDependenciesMeta: - node-notifier: - optional: true - dependencies: - chokidar: 3.5.3 - dynamic-dedupe: 0.3.0 - minimist: 1.2.8 - mkdirp: 1.0.4 - resolve: 1.22.2 - rimraf: 2.7.1 - source-map-support: 0.5.13 - tree-kill: 1.2.2 - ts-node: 10.9.1(@types/node@18.16.9)(typescript@4.9.5) - tsconfig: 7.0.0 - typescript: 4.9.5 - transitivePeerDependencies: - - '@swc/core' - - '@swc/wasm' - - '@types/node' - dev: true - - /ts-node@10.9.1(@types/node@18.16.9)(typescript@4.9.5): - resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.9 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 18.16.9 - acorn: 8.8.2 - acorn-walk: 8.2.0 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 - typescript: 4.9.5 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - dev: true - - /ts-results@3.3.0: - resolution: {integrity: sha512-FWqxGX2NHp5oCyaMd96o2y2uMQmSu8Dey6kvyuFdRJ2AzfmWo3kWa4UsPlCGlfQ/qu03m09ZZtppMoY8EMHuiA==} - dev: false - - /tsconfig-paths-jest@0.0.1: - resolution: {integrity: sha512-YKhUKqbteklNppC2NqL7dv1cWF8eEobgHVD5kjF1y9Q4ocqpBiaDlYslQ9eMhtbqIPRrA68RIEXqknEjlxdwxw==} - dev: true - - /tsconfig@7.0.0: - resolution: {integrity: sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==} - dependencies: - '@types/strip-bom': 3.0.0 - '@types/strip-json-comments': 0.0.30 - strip-bom: 3.0.0 - strip-json-comments: 2.0.1 - dev: true - - /tslib@1.14.1: - resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - dev: true - - /tslib@2.5.0: - resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==} - - /tsutils@3.21.0(typescript@4.9.5): - resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} - engines: {node: '>= 6'} - peerDependencies: - typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' - dependencies: - tslib: 1.14.1 - typescript: 4.9.5 - dev: true - - /type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - dependencies: - prelude-ls: 1.2.1 - dev: true - - /type-detect@4.0.8: - resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} - engines: {node: '>=4'} - - /type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - dev: true - - /type-fest@0.21.3: - resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} - engines: {node: '>=10'} - - /typed-array-length@1.0.4: - resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} - dependencies: - call-bind: 1.0.2 - for-each: 0.3.3 - is-typed-array: 1.1.10 - dev: true - - /typescript@4.9.5: - resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} - engines: {node: '>=4.2.0'} - hasBin: true - dev: true - - /unbox-primitive@1.0.2: - resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} - dependencies: - call-bind: 1.0.2 - has-bigints: 1.0.2 - has-symbols: 1.0.3 - which-boxed-primitive: 1.0.2 - dev: true - - /uncontrollable@7.2.1(react@18.2.0): - resolution: {integrity: sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==} - peerDependencies: - react: '>=15.0.0' - dependencies: - '@babel/runtime': 7.23.4 - '@types/react': 18.2.6 - invariant: 2.2.4 - react: 18.2.0 - react-lifecycles-compat: 3.0.4 - dev: false - - /universalify@0.2.0: - resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} - engines: {node: '>= 4.0.0'} - dev: true - - /universalify@2.0.0: - resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} - engines: {node: '>= 10.0.0'} - dev: true - - /unsplash-js@7.0.19: - resolution: {integrity: sha512-j6qT2floy5Q2g2d939FJpwey1yw/GpQecFiSouyJtsHQPj3oqmqq3K4rI+GF8vU1zwGCT7ZwIGQd2dtCQLjYJw==} - engines: {node: '>=10'} - dev: false - - /update-browserslist-db@1.0.11(browserslist@4.21.5): - resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - dependencies: - browserslist: 4.21.5 - escalade: 3.1.1 - picocolors: 1.0.0 - - /upper-case-first@2.0.2: - resolution: {integrity: sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==} - dependencies: - tslib: 2.5.0 - dev: true - - /upper-case@2.0.2: - resolution: {integrity: sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==} - dependencies: - tslib: 2.5.0 - dev: true - - /uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - dependencies: - punycode: 2.3.0 - dev: true - - /url-parse@1.5.10: - resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} - dependencies: - querystringify: 2.2.0 - requires-port: 1.0.0 - dev: true - - /use-memo-one@1.1.3(react@18.2.0): - resolution: {integrity: sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - dependencies: - react: 18.2.0 - dev: false - - /use-sync-external-store@1.2.0(react@18.2.0): - resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - dependencies: - react: 18.2.0 - dev: false - - /utf8@3.0.0: - resolution: {integrity: sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==} - dev: false - - /util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - dev: true - - /uuid@9.0.0: - resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==} - hasBin: true - dev: true - - /v8-compile-cache-lib@3.0.1: - resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} - dev: true - - /v8-to-istanbul@9.1.0: - resolution: {integrity: sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==} - engines: {node: '>=10.12.0'} - dependencies: - '@jridgewell/trace-mapping': 0.3.18 - '@types/istanbul-lib-coverage': 2.0.4 - convert-source-map: 1.9.0 - - /valtio@1.12.1(@types/react@18.2.6)(react@18.2.0): - resolution: {integrity: sha512-R0V4H86Xi2Pp7pmxN/EtV4Q6jr6PMN3t1IwxEvKUp6160r8FimvPh941oWyeK1iec/DTsh9Jb3Q+GputMS8SYg==} - engines: {node: '>=12.20.0'} - peerDependencies: - '@types/react': '>=16.8' - react: '>=16.8' - peerDependenciesMeta: - '@types/react': - optional: true - react: - optional: true - dependencies: - '@types/react': 18.2.6 - derive-valtio: 0.1.0(valtio@1.12.1) - proxy-compare: 2.5.1 - react: 18.2.0 - use-sync-external-store: 1.2.0(react@18.2.0) - dev: false - - /vite-plugin-svgr@3.2.0(vite@4.3.5): - resolution: {integrity: sha512-Uvq6niTvhqJU6ga78qLKBFJSDvxWhOnyfQSoKpDPMAGxJPo5S3+9hyjExE5YDj6Lpa4uaLkGc1cBgxXov+LjSw==} - peerDependencies: - vite: ^2.6.0 || 3 || 4 - dependencies: - '@rollup/pluginutils': 5.0.2 - '@svgr/core': 7.0.0 - '@svgr/plugin-jsx': 7.0.0 - vite: 4.3.5(@types/node@18.16.9)(sass@1.70.0) - transitivePeerDependencies: - - rollup - - supports-color - dev: true - - /vite@4.3.5(@types/node@18.16.9)(sass@1.70.0): - resolution: {integrity: sha512-0gEnL9wiRFxgz40o/i/eTBwm+NEbpUeTWhzKrZDSdKm6nplj+z4lKz8ANDgildxHm47Vg8EUia0aicKbawUVVA==} - engines: {node: ^14.18.0 || >=16.0.0} - hasBin: true - peerDependencies: - '@types/node': '>= 14' - less: '*' - sass: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - sass: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - dependencies: - '@types/node': 18.16.9 - esbuild: 0.17.19 - postcss: 8.4.23 - rollup: 3.21.7 - sass: 1.70.0 - optionalDependencies: - fsevents: 2.3.2 - dev: true - - /void-elements@3.1.0: - resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} - engines: {node: '>=0.10.0'} - dev: false - - /w3c-xmlserializer@4.0.0: - resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==} - engines: {node: '>=14'} - dependencies: - xml-name-validator: 4.0.0 - dev: true - - /walker@1.0.8: - resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} - dependencies: - makeerror: 1.0.12 - - /warning@4.0.3: - resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==} - dependencies: - loose-envify: 1.4.0 - - /webidl-conversions@7.0.0: - resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} - engines: {node: '>=12'} - dev: true - - /whatwg-encoding@2.0.0: - resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} - engines: {node: '>=12'} - dependencies: - iconv-lite: 0.6.3 - dev: true - - /whatwg-mimetype@3.0.0: - resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} - engines: {node: '>=12'} - dev: true - - /whatwg-url@11.0.0: - resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==} - engines: {node: '>=12'} - dependencies: - tr46: 3.0.0 - webidl-conversions: 7.0.0 - dev: true - - /which-boxed-primitive@1.0.2: - resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} - dependencies: - is-bigint: 1.0.4 - is-boolean-object: 1.1.2 - is-number-object: 1.0.7 - is-string: 1.0.7 - is-symbol: 1.0.4 - dev: true - - /which-typed-array@1.1.9: - resolution: {integrity: sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==} - engines: {node: '>= 0.4'} - dependencies: - available-typed-arrays: 1.0.5 - call-bind: 1.0.2 - for-each: 0.3.3 - gopd: 1.0.1 - has-tostringtag: 1.0.0 - is-typed-array: 1.1.10 - dev: true - - /which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - dependencies: - isexe: 2.0.0 - - /word-wrap@1.2.3: - resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==} - engines: {node: '>=0.10.0'} - dev: true - - /wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - /wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - /write-file-atomic@4.0.2: - resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - dependencies: - imurmurhash: 0.1.4 - signal-exit: 3.0.7 - - /ws@8.14.1: - resolution: {integrity: sha512-4OOseMUq8AzRBI/7SLMUwO+FEDnguetSk7KMb1sHwvF2w2Wv5Hoj0nlifx8vtGsftE/jWHojPy8sMMzYLJ2G/A==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - dev: true - - /xml-name-validator@4.0.0: - resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} - engines: {node: '>=12'} - dev: true - - /xmlchars@2.2.0: - resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} - dev: true - - /xtend@4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} - dev: true - - /y-protocols@1.0.6(yjs@13.6.1): - resolution: {integrity: sha512-vHRF2L6iT3rwj1jub/K5tYcTT/mEYDUppgNPXwp8fmLpui9f7Yeq3OEtTLVF012j39QnV+KEQpNqoN7CWU7Y9Q==} - engines: {node: '>=16.0.0', npm: '>=8.0.0'} - peerDependencies: - yjs: ^13.0.0 - dependencies: - lib0: 0.2.88 - yjs: 13.6.1 - dev: false - - /y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - - /yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - - /yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - - /yaml@1.10.2: - resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} - engines: {node: '>= 6'} - dev: false - - /yaml@2.2.2: - resolution: {integrity: sha512-CBKFWExMn46Foo4cldiChEzn7S7SRV+wqiluAb6xmueD/fGyRHIhX8m14vVGgeFWjN540nKCNVj6P21eQjgTuA==} - engines: {node: '>= 14'} - dev: true - - /yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} - - /yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} - dependencies: - cliui: 8.0.1 - escalade: 3.1.1 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 21.1.1 - - /yjs@13.6.1: - resolution: {integrity: sha512-IyyHL+/v9N2S4YLSjGHMa0vMAfFxq8RDG5Nvb77raTTHJPweU3L/fRlqw6ElZvZUuHWnax3ufHR0Tx0ntfG63Q==} - engines: {node: '>=16.0.0', npm: '>=8.0.0'} - dependencies: - lib0: 0.2.74 - dev: false - - /yn@3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} - engines: {node: '>=6'} - dev: true - - /yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false diff --git a/frontend/appflowy_tauri/postcss.config.cjs b/frontend/appflowy_tauri/postcss.config.cjs deleted file mode 100644 index 12a703d900..0000000000 --- a/frontend/appflowy_tauri/postcss.config.cjs +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; diff --git a/frontend/appflowy_tauri/public/google_fonts/Poppins/OFL.txt b/frontend/appflowy_tauri/public/google_fonts/Poppins/OFL.txt deleted file mode 100644 index 246c977c9f..0000000000 --- a/frontend/appflowy_tauri/public/google_fonts/Poppins/OFL.txt +++ /dev/null @@ -1,93 +0,0 @@ -Copyright 2020 The Poppins Project Authors (https://github.com/itfoundry/Poppins) - -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -http://scripts.sil.org/OFL - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-Black.ttf b/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-Black.ttf deleted file mode 100644 index 71c0f995ee..0000000000 Binary files a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-Black.ttf and /dev/null differ diff --git a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-BlackItalic.ttf b/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-BlackItalic.ttf deleted file mode 100644 index 7aeb58bd1b..0000000000 Binary files a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-BlackItalic.ttf and /dev/null differ diff --git a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-Bold.ttf b/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-Bold.ttf deleted file mode 100644 index 00559eeb29..0000000000 Binary files a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-Bold.ttf and /dev/null differ diff --git a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-BoldItalic.ttf b/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-BoldItalic.ttf deleted file mode 100644 index e61e8e88bd..0000000000 Binary files a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-BoldItalic.ttf and /dev/null differ diff --git a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-ExtraBold.ttf b/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-ExtraBold.ttf deleted file mode 100644 index df7093608a..0000000000 Binary files a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-ExtraBold.ttf and /dev/null differ diff --git a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-ExtraBoldItalic.ttf b/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-ExtraBoldItalic.ttf deleted file mode 100644 index 14d2b375dc..0000000000 Binary files a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-ExtraBoldItalic.ttf and /dev/null differ diff --git a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-ExtraLight.ttf b/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-ExtraLight.ttf deleted file mode 100644 index e76ec69a65..0000000000 Binary files a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-ExtraLight.ttf and /dev/null differ diff --git a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-ExtraLightItalic.ttf b/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-ExtraLightItalic.ttf deleted file mode 100644 index 89513d9469..0000000000 Binary files a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-ExtraLightItalic.ttf and /dev/null differ diff --git a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-Italic.ttf b/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-Italic.ttf deleted file mode 100644 index 12b7b3c40b..0000000000 Binary files a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-Italic.ttf and /dev/null differ diff --git a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-Light.ttf b/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-Light.ttf deleted file mode 100644 index bc36bcc242..0000000000 Binary files a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-Light.ttf and /dev/null differ diff --git a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-LightItalic.ttf b/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-LightItalic.ttf deleted file mode 100644 index 9e70be6a9e..0000000000 Binary files a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-LightItalic.ttf and /dev/null differ diff --git a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-Medium.ttf b/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-Medium.ttf deleted file mode 100644 index 6bcdcc27f2..0000000000 Binary files a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-Medium.ttf and /dev/null differ diff --git a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-MediumItalic.ttf b/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-MediumItalic.ttf deleted file mode 100644 index be67410fd0..0000000000 Binary files a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-MediumItalic.ttf and /dev/null differ diff --git a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-Regular.ttf b/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-Regular.ttf deleted file mode 100644 index 9f0c71b70a..0000000000 Binary files a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-Regular.ttf and /dev/null differ diff --git a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-SemiBold.ttf b/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-SemiBold.ttf deleted file mode 100644 index 74c726e327..0000000000 Binary files a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-SemiBold.ttf and /dev/null differ diff --git a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-SemiBoldItalic.ttf b/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-SemiBoldItalic.ttf deleted file mode 100644 index 3e6c942233..0000000000 Binary files a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-SemiBoldItalic.ttf and /dev/null differ diff --git a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-Thin.ttf b/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-Thin.ttf deleted file mode 100644 index 03e736613a..0000000000 Binary files a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-Thin.ttf and /dev/null differ diff --git a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-ThinItalic.ttf b/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-ThinItalic.ttf deleted file mode 100644 index e26db5dd3d..0000000000 Binary files a/frontend/appflowy_tauri/public/google_fonts/Poppins/Poppins-ThinItalic.ttf and /dev/null differ diff --git a/frontend/appflowy_tauri/public/google_fonts/Roboto_Mono/LICENSE.txt b/frontend/appflowy_tauri/public/google_fonts/Roboto_Mono/LICENSE.txt deleted file mode 100644 index 75b52484ea..0000000000 --- a/frontend/appflowy_tauri/public/google_fonts/Roboto_Mono/LICENSE.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/frontend/appflowy_tauri/public/google_fonts/Roboto_Mono/RobotoMono-Italic.ttf b/frontend/appflowy_tauri/public/google_fonts/Roboto_Mono/RobotoMono-Italic.ttf deleted file mode 100644 index 61e5303325..0000000000 Binary files a/frontend/appflowy_tauri/public/google_fonts/Roboto_Mono/RobotoMono-Italic.ttf and /dev/null differ diff --git a/frontend/appflowy_tauri/public/google_fonts/Roboto_Mono/RobotoMono-Regular.ttf b/frontend/appflowy_tauri/public/google_fonts/Roboto_Mono/RobotoMono-Regular.ttf deleted file mode 100644 index 6df2b25360..0000000000 Binary files a/frontend/appflowy_tauri/public/google_fonts/Roboto_Mono/RobotoMono-Regular.ttf and /dev/null differ diff --git a/frontend/appflowy_tauri/public/launch_splash.jpg b/frontend/appflowy_tauri/public/launch_splash.jpg deleted file mode 100644 index 7e3bb9cee6..0000000000 Binary files a/frontend/appflowy_tauri/public/launch_splash.jpg and /dev/null differ diff --git a/frontend/appflowy_tauri/public/tauri.svg b/frontend/appflowy_tauri/public/tauri.svg deleted file mode 100644 index 31b62c9280..0000000000 --- a/frontend/appflowy_tauri/public/tauri.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/frontend/appflowy_tauri/public/vite.svg b/frontend/appflowy_tauri/public/vite.svg deleted file mode 100644 index e7b8dfb1b2..0000000000 --- a/frontend/appflowy_tauri/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/appflowy_tauri/scripts/i18n/index.cjs b/frontend/appflowy_tauri/scripts/i18n/index.cjs deleted file mode 100644 index c3789e0c56..0000000000 --- a/frontend/appflowy_tauri/scripts/i18n/index.cjs +++ /dev/null @@ -1,63 +0,0 @@ -const languages = [ - 'ar-SA', - 'ca-ES', - 'de-DE', - 'en', - 'es-VE', - 'eu-ES', - 'fr-FR', - 'hu-HU', - 'id-ID', - 'it-IT', - 'ja-JP', - 'ko-KR', - 'pl-PL', - 'pt-BR', - 'pt-PT', - 'ru-RU', - 'sv-SE', - 'th-TH', - 'tr-TR', - 'zh-CN', - 'zh-TW', -]; - -const fs = require('fs'); -languages.forEach(language => { - const json = require(`../../../resources/translations/${language}.json`); - const outputJSON = flattenJSON(json); - const output = JSON.stringify(outputJSON); - const isExistDir = fs.existsSync('./src/appflowy_app/i18n/translations'); - if (!isExistDir) { - fs.mkdirSync('./src/appflowy_app/i18n/translations'); - } - fs.writeFile(`./src/appflowy_app/i18n/translations/${language}.json`, new Uint8Array(Buffer.from(output)), (res) => { - if (res) { - console.error(res); - } - }) -}); - - -function flattenJSON(obj, prefix = '') { - let result = {}; - const pluralsKey = ["one", "other", "few", "many", "two", "zero"]; - - for (let key in obj) { - if (typeof obj[key] === 'object' && obj[key] !== null) { - - const nestedKeys = flattenJSON(obj[key], `${prefix}${key}.`); - result = { ...result, ...nestedKeys }; - } else { - let newKey = `${prefix}${key}`; - let replaceChar = '{' - if (pluralsKey.includes(key)) { - newKey = `${prefix.slice(0, -1)}_${key}`; - } - result[newKey] = obj[key].replaceAll('{', '{{').replaceAll('}', '}}'); - } - } - - return result; -} - diff --git a/frontend/appflowy_tauri/scripts/update_version.cjs b/frontend/appflowy_tauri/scripts/update_version.cjs deleted file mode 100644 index 498b8c3e4f..0000000000 --- a/frontend/appflowy_tauri/scripts/update_version.cjs +++ /dev/null @@ -1,31 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -if (process.argv.length < 3) { - console.error('Usage: node update-tauri-version.js '); - process.exit(1); -} - -const newVersion = process.argv[2]; - -const tauriConfigPath = path.join(__dirname, '../src-tauri', 'tauri.conf.json'); - -fs.readFile(tauriConfigPath, 'utf8', (err, data) => { - if (err) { - console.error('Error reading tauri.conf.json:', err); - return; - } - - const config = JSON.parse(data); - - config.package.version = newVersion; - - fs.writeFile(tauriConfigPath, JSON.stringify(config, null, 2), 'utf8', (err) => { - if (err) { - console.error('Error writing tauri.conf.json:', err); - return; - } - - console.log(`Tauri version updated to ${newVersion} successfully.`); - }); -}); diff --git a/frontend/appflowy_tauri/src-tauri/.cargo/config.toml b/frontend/appflowy_tauri/src-tauri/.cargo/config.toml deleted file mode 100644 index bff29e6e17..0000000000 --- a/frontend/appflowy_tauri/src-tauri/.cargo/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[build] -rustflags = ["--cfg", "tokio_unstable"] diff --git a/frontend/appflowy_tauri/src-tauri/.gitignore b/frontend/appflowy_tauri/src-tauri/.gitignore deleted file mode 100644 index 61e1bdd46a..0000000000 --- a/frontend/appflowy_tauri/src-tauri/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -.env diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.lock b/frontend/appflowy_tauri/src-tauri/Cargo.lock deleted file mode 100644 index 4b4d22625e..0000000000 --- a/frontend/appflowy_tauri/src-tauri/Cargo.lock +++ /dev/null @@ -1,9227 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "accessory" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "850bb534b9dc04744fbbb71d30ad6d25a7e4cf6dc33e223c81ef3a92ebab4e0b" -dependencies = [ - "macroific", - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "addr2line" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common", - "generic-array", -] - -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "aes-gcm" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "209b47e8954a928e1d72e86eca7000ebb6655fe1436d33eefc2201cad027e237" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", - "subtle", -] - -[[package]] -name = "again" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05802a5ad4d172eaf796f7047b42d0af9db513585d16d4169660a21613d34b93" -dependencies = [ - "log", - "rand 0.7.3", - "wasm-timer", -] - -[[package]] -name = "ahash" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" -dependencies = [ - "getrandom 0.2.10", - "once_cell", - "version_check", -] - -[[package]] -name = "ahash" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91429305e9f0a25f6205c5b8e0d2db09e0708a7a6df0f42212bb56c32c8ac97a" -dependencies = [ - "cfg-if", - "getrandom 0.2.10", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "aho-corasick" -version = "0.7.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" -dependencies = [ - "memchr", -] - -[[package]] -name = "aho-corasick" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" -dependencies = [ - "memchr", -] - -[[package]] -name = "allo-isolate" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b6d794345b06592d0ebeed8e477e41b71e5a0a49df4fc0e4184d5938b99509" -dependencies = [ - "atomic", - "pin-project", -] - -[[package]] -name = "alloc-no-stdlib" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" -dependencies = [ - "alloc-no-stdlib", -] - -[[package]] -name = "allocator-api2" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anyhow" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" - -[[package]] -name = "app-error" -version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ea131f0baab67defe7591067357eced490072372#ea131f0baab67defe7591067357eced490072372" -dependencies = [ - "anyhow", - "bincode", - "getrandom 0.2.10", - "reqwest", - "serde", - "serde_json", - "serde_repr", - "thiserror", - "tokio", - "tsify", - "url", - "uuid", - "wasm-bindgen", -] - -[[package]] -name = "appflowy-ai-client" -version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ea131f0baab67defe7591067357eced490072372#ea131f0baab67defe7591067357eced490072372" -dependencies = [ - "anyhow", - "bytes", - "futures", - "pin-project", - "serde", - "serde_json", - "serde_repr", - "thiserror", -] - -[[package]] -name = "appflowy-local-ai" -version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=6f064efe232268f8d396edbb4b84d57fbb640f13#6f064efe232268f8d396edbb4b84d57fbb640f13" -dependencies = [ - "anyhow", - "appflowy-plugin", - "bytes", - "reqwest", - "serde", - "serde_json", - "tokio", - "tokio-stream", - "tokio-util", - "tracing", - "zip 2.2.0", - "zip-extensions", -] - -[[package]] -name = "appflowy-plugin" -version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=6f064efe232268f8d396edbb4b84d57fbb640f13#6f064efe232268f8d396edbb4b84d57fbb640f13" -dependencies = [ - "anyhow", - "cfg-if", - "crossbeam-utils", - "log", - "once_cell", - "parking_lot 0.12.1", - "serde", - "serde_json", - "thiserror", - "tokio", - "tokio-stream", - "tracing", - "xattr 1.3.1", -] - -[[package]] -name = "appflowy_tauri" -version = "0.0.0" -dependencies = [ - "bytes", - "dotenv", - "flowy-ai", - "flowy-config", - "flowy-core", - "flowy-date", - "flowy-document", - "flowy-error", - "flowy-notification", - "flowy-search", - "flowy-user", - "lib-dispatch", - "semver", - "serde", - "serde_json", - "tauri", - "tauri-build", - "tauri-plugin-deep-link", - "tauri-utils", - "tracing", - "uuid", -] - -[[package]] -name = "arbitrary" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" -dependencies = [ - "derive_arbitrary", -] - -[[package]] -name = "arc-swap" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" - -[[package]] -name = "arrayvec" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" - -[[package]] -name = "async-compression" -version = "0.4.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "103db485efc3e41214fe4fda9f3dbeae2eb9082f48fd236e6095627a9422066e" -dependencies = [ - "bzip2", - "deflate64", - "flate2", - "futures-core", - "futures-io", - "memchr", - "pin-project-lite", - "xz2", - "zstd 0.13.2", - "zstd-safe 7.2.0", -] - -[[package]] -name = "async-lock" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" -dependencies = [ - "event-listener", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-recursion" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "async-stream" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "async-trait" -version = "0.1.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "async_zip" -version = "0.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b9f7252833d5ed4b00aa9604b563529dd5e11de9c23615de2dcdf91eb87b52" -dependencies = [ - "async-compression", - "chrono", - "crc32fast", - "futures-lite", - "pin-project", - "thiserror", - "tokio", - "tokio-util", -] - -[[package]] -name = "atk" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c3d816ce6f0e2909a96830d6911c2aff044370b1ef92d7f267b43bae5addedd" -dependencies = [ - "atk-sys", - "bitflags 1.3.2", - "glib", - "libc", -] - -[[package]] -name = "atk-sys" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58aeb089fb698e06db8089971c7ee317ab9644bade33383f63631437b03aafb6" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps 6.1.1", -] - -[[package]] -name = "atomic" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" - -[[package]] -name = "atomic_refcell" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79d6dc922a2792b006573f60b2648076355daeae5ce9cb59507e5908c9625d31" - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "backtrace" -version = "0.3.67" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide 0.6.2", - "object", - "rustc-demangle", -] - -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.21.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "base64ct" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bindgen" -version = "0.69.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" -dependencies = [ - "bitflags 2.4.0", - "cexpr", - "clang-sys", - "itertools 0.12.1", - "lazy_static", - "lazycell", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 2.0.47", -] - -[[package]] -name = "bit-set" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" - -[[package]] -name = "bitpacking" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c1d3e2bfd8d06048a179f7b17afc3188effa10385e7b00dc65af6aae732ea92" -dependencies = [ - "crunchy", -] - -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "borsh" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d4d6dafc1a3bb54687538972158f07b2c948bc57d5890df22c0739098b3028" -dependencies = [ - "borsh-derive", - "cfg_aliases", -] - -[[package]] -name = "borsh-derive" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4918709cc4dd777ad2b6303ed03cb37f3ca0ccede8c1b0d28ac6db8f4710e0" -dependencies = [ - "once_cell", - "proc-macro-crate 2.0.2", - "proc-macro2", - "quote", - "syn 2.0.47", - "syn_derive", -] - -[[package]] -name = "brotli" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516074a47ef4bce09577a3b379392300159ce5b1ba2e501ff1c819950066100f" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "2.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - -[[package]] -name = "bstr" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a246e68bb43f6cd9db24bea052a53e40405417c5fb372e3d1a8a7f770a564ef5" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "bumpalo" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - -[[package]] -name = "bytecheck" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6372023ac861f6e6dc89c8344a8f398fb42aaba2b5dbc649ca0c0e9dbcb627" -dependencies = [ - "bytecheck_derive", - "ptr_meta", - "simdutf8", -] - -[[package]] -name = "bytecheck_derive" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7ec4c6f261935ad534c0c22dbef2201b45918860eb1c574b972bd213a76af61" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "bytemuck" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" - -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "bytes" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" -dependencies = [ - "serde", -] - -[[package]] -name = "bzip2" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" -dependencies = [ - "bzip2-sys", - "libc", -] - -[[package]] -name = "bzip2-sys" -version = "0.1.11+1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - -[[package]] -name = "cairo-rs" -version = "0.15.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c76ee391b03d35510d9fa917357c7f1855bd9a6659c95a1b392e33f49b3369bc" -dependencies = [ - "bitflags 1.3.2", - "cairo-sys-rs", - "glib", - "libc", - "thiserror", -] - -[[package]] -name = "cairo-sys-rs" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c55d429bef56ac9172d25fecb85dc8068307d17acd74b377866b7a1ef25d3c8" -dependencies = [ - "glib-sys", - "libc", - "system-deps 6.1.1", -] - -[[package]] -name = "cargo_toml" -version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "599aa35200ffff8f04c1925aa1acc92fa2e08874379ef42e210a80e527e60838" -dependencies = [ - "serde", - "toml 0.7.5", -] - -[[package]] -name = "cc" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" -dependencies = [ - "jobserver", -] - -[[package]] -name = "census" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f4c707c6a209cbe82d10abd08e1ea8995e9ea937d2550646e02798948992be0" - -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - -[[package]] -name = "cfb" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" -dependencies = [ - "byteorder", - "fnv", - "uuid", -] - -[[package]] -name = "cfg-expr" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3431df59f28accaf4cb4eed4a9acc66bea3f3c3753aa6cdc2f024174ef232af7" -dependencies = [ - "smallvec", -] - -[[package]] -name = "cfg-expr" -version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "215c0072ecc28f92eeb0eea38ba63ddfcb65c2828c46311d646f1a3ff5f9841c" -dependencies = [ - "smallvec", - "target-lexicon", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "cfg_aliases" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" - -[[package]] -name = "chrono" -version = "0.4.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "serde", - "wasm-bindgen", - "windows-targets 0.52.0", -] - -[[package]] -name = "chrono-tz" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58549f1842da3080ce63002102d5bc954c7bc843d4f47818e642abdc36253552" -dependencies = [ - "chrono", - "chrono-tz-build 0.0.2", - "phf 0.10.1", -] - -[[package]] -name = "chrono-tz" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9cc2b23599e6d7479755f3594285efb3f74a1bdca7a7374948bc831e23a552" -dependencies = [ - "chrono", - "chrono-tz-build 0.1.0", - "phf 0.11.2", -] - -[[package]] -name = "chrono-tz" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6dd8046d00723a59a2f8c5f295c515b9bb9a331ee4f8f3d4dd49e428acd3b6" -dependencies = [ - "chrono", - "chrono-tz-build 0.4.0", - "phf 0.11.2", -] - -[[package]] -name = "chrono-tz-build" -version = "0.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db058d493fb2f65f41861bfed7e3fe6335264a9f0f92710cab5bdf01fef09069" -dependencies = [ - "parse-zoneinfo", - "phf 0.10.1", - "phf_codegen 0.10.0", -] - -[[package]] -name = "chrono-tz-build" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9998fb9f7e9b2111641485bf8beb32f92945f97f92a3d061f744cfef335f751" -dependencies = [ - "parse-zoneinfo", - "phf 0.11.2", - "phf_codegen 0.11.2", -] - -[[package]] -name = "chrono-tz-build" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94fea34d77a245229e7746bd2beb786cd2a896f306ff491fb8cecb3074b10a7" -dependencies = [ - "parse-zoneinfo", - "phf_codegen 0.11.2", -] - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - -[[package]] -name = "clang-sys" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" -dependencies = [ - "glob", - "libc", - "libloading", -] - -[[package]] -name = "client-api" -version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ea131f0baab67defe7591067357eced490072372#ea131f0baab67defe7591067357eced490072372" -dependencies = [ - "again", - "anyhow", - "app-error", - "arc-swap", - "async-trait", - "base64 0.22.1", - "bincode", - "brotli", - "bytes", - "chrono", - "client-api-entity", - "client-websocket", - "collab", - "collab-rt-entity", - "collab-rt-protocol", - "futures", - "futures-core", - "futures-util", - "getrandom 0.2.10", - "gotrue", - "infra", - "lazy_static", - "md5", - "mime", - "mime_guess", - "parking_lot 0.12.1", - "percent-encoding", - "pin-project", - "prost 0.13.3", - "rayon", - "reqwest", - "scraper 0.17.1", - "semver", - "serde", - "serde_json", - "serde_repr", - "serde_urlencoded", - "shared-entity", - "thiserror", - "tokio", - "tokio-retry", - "tokio-stream", - "tokio-util", - "tracing", - "url", - "uuid", - "wasm-bindgen-futures", - "yrs", - "zstd 0.13.2", -] - -[[package]] -name = "client-api-entity" -version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ea131f0baab67defe7591067357eced490072372#ea131f0baab67defe7591067357eced490072372" -dependencies = [ - "collab-entity", - "collab-rt-entity", - "database-entity", - "gotrue-entity", - "shared-entity", - "uuid", -] - -[[package]] -name = "client-websocket" -version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ea131f0baab67defe7591067357eced490072372#ea131f0baab67defe7591067357eced490072372" -dependencies = [ - "futures-channel", - "futures-util", - "http", - "httparse", - "js-sys", - "percent-encoding", - "thiserror", - "tokio", - "tokio-tungstenite", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "cmd_lib" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ba0f413777386d37f85afa5242f277a7b461905254c1af3c339d4af06800f62" -dependencies = [ - "cmd_lib_macros", - "faccess", - "lazy_static", - "log", - "os_pipe", -] - -[[package]] -name = "cmd_lib_macros" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e66605092ff6c6e37e0246601ae6c3f62dc1880e0599359b5f303497c112dc0" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "cocoa" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" -dependencies = [ - "bitflags 1.3.2", - "block", - "cocoa-foundation", - "core-foundation", - "core-graphics", - "foreign-types", - "libc", - "objc", -] - -[[package]] -name = "cocoa-foundation" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "931d3837c286f56e3c58423ce4eba12d08db2374461a785c86f672b08b5650d6" -dependencies = [ - "bitflags 1.3.2", - "block", - "core-foundation", - "core-graphics-types", - "foreign-types", - "libc", - "objc", -] - -[[package]] -name = "collab" -version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=67b31e4bcc64d81332c37efecb0a8618681f1db9#67b31e4bcc64d81332c37efecb0a8618681f1db9" -dependencies = [ - "anyhow", - "arc-swap", - "async-trait", - "bincode", - "bytes", - "chrono", - "js-sys", - "lazy_static", - "serde", - "serde_json", - "serde_repr", - "thiserror", - "tokio", - "tokio-stream", - "tracing", - "unicode-segmentation", - "web-sys", - "yrs", -] - -[[package]] -name = "collab-database" -version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=67b31e4bcc64d81332c37efecb0a8618681f1db9#67b31e4bcc64d81332c37efecb0a8618681f1db9" -dependencies = [ - "anyhow", - "async-trait", - "base64 0.22.1", - "chrono", - "chrono-tz 0.10.0", - "collab", - "collab-entity", - "csv", - "dashmap 5.5.3", - "fancy-regex 0.13.0", - "futures", - "getrandom 0.2.10", - "iana-time-zone", - "js-sys", - "lazy_static", - "nanoid", - "percent-encoding", - "rayon", - "rust_decimal", - "rusty-money", - "serde", - "serde_json", - "serde_repr", - "sha2", - "strum", - "strum_macros 0.25.2", - "thiserror", - "tokio", - "tokio-stream", - "tokio-util", - "tracing", - "uuid", - "yrs", -] - -[[package]] -name = "collab-document" -version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=67b31e4bcc64d81332c37efecb0a8618681f1db9#67b31e4bcc64d81332c37efecb0a8618681f1db9" -dependencies = [ - "anyhow", - "arc-swap", - "collab", - "collab-entity", - "getrandom 0.2.10", - "markdown", - "nanoid", - "serde", - "serde_json", - "thiserror", - "tokio", - "tokio-stream", - "tracing", - "uuid", -] - -[[package]] -name = "collab-entity" -version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=67b31e4bcc64d81332c37efecb0a8618681f1db9#67b31e4bcc64d81332c37efecb0a8618681f1db9" -dependencies = [ - "anyhow", - "bytes", - "collab", - "getrandom 0.2.10", - "prost 0.13.3", - "prost-build", - "protoc-bin-vendored", - "serde", - "serde_json", - "serde_repr", - "thiserror", - "uuid", - "walkdir", -] - -[[package]] -name = "collab-folder" -version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=67b31e4bcc64d81332c37efecb0a8618681f1db9#67b31e4bcc64d81332c37efecb0a8618681f1db9" -dependencies = [ - "anyhow", - "arc-swap", - "chrono", - "collab", - "collab-entity", - "dashmap 5.5.3", - "getrandom 0.2.10", - "serde", - "serde_json", - "serde_repr", - "thiserror", - "tokio", - "tokio-stream", - "tracing", - "uuid", -] - -[[package]] -name = "collab-importer" -version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=67b31e4bcc64d81332c37efecb0a8618681f1db9#67b31e4bcc64d81332c37efecb0a8618681f1db9" -dependencies = [ - "anyhow", - "async-recursion", - "async-trait", - "async_zip", - "base64 0.22.1", - "chrono", - "collab", - "collab-database", - "collab-document", - "collab-entity", - "collab-folder", - "csv", - "fancy-regex 0.13.0", - "futures", - "futures-lite", - "futures-util", - "fxhash", - "hex", - "markdown", - "percent-encoding", - "rayon", - "sanitize-filename", - "serde", - "serde_json", - "sha2", - "thiserror", - "tokio", - "tokio-util", - "tracing", - "uuid", - "walkdir", - "zip 0.6.6", -] - -[[package]] -name = "collab-integrate" -version = "0.1.0" -dependencies = [ - "anyhow", - "arc-swap", - "async-trait", - "collab", - "collab-database", - "collab-document", - "collab-entity", - "collab-folder", - "collab-plugins", - "collab-user", - "diesel", - "flowy-error", - "flowy-sqlite", - "futures", - "lib-infra", - "serde", - "serde_json", - "tokio", - "tracing", -] - -[[package]] -name = "collab-plugins" -version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=67b31e4bcc64d81332c37efecb0a8618681f1db9#67b31e4bcc64d81332c37efecb0a8618681f1db9" -dependencies = [ - "anyhow", - "async-stream", - "async-trait", - "bincode", - "bytes", - "chrono", - "collab", - "collab-entity", - "futures", - "futures-util", - "getrandom 0.2.10", - "indexed_db_futures", - "js-sys", - "lazy_static", - "rand 0.8.5", - "rocksdb", - "serde", - "serde_json", - "similar 2.2.1", - "smallvec", - "thiserror", - "tokio", - "tokio-retry", - "tokio-stream", - "tracing", - "tracing-wasm", - "uuid", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "yrs", -] - -[[package]] -name = "collab-rt-entity" -version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ea131f0baab67defe7591067357eced490072372#ea131f0baab67defe7591067357eced490072372" -dependencies = [ - "anyhow", - "bincode", - "bytes", - "chrono", - "client-websocket", - "collab", - "collab-entity", - "collab-rt-protocol", - "database-entity", - "prost 0.13.3", - "prost-build", - "protoc-bin-vendored", - "serde", - "serde_json", - "serde_repr", - "thiserror", - "tokio-tungstenite", - "yrs", -] - -[[package]] -name = "collab-rt-protocol" -version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ea131f0baab67defe7591067357eced490072372#ea131f0baab67defe7591067357eced490072372" -dependencies = [ - "anyhow", - "async-trait", - "bincode", - "collab", - "collab-entity", - "serde", - "thiserror", - "tokio", - "tracing", - "yrs", -] - -[[package]] -name = "collab-user" -version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=67b31e4bcc64d81332c37efecb0a8618681f1db9#67b31e4bcc64d81332c37efecb0a8618681f1db9" -dependencies = [ - "anyhow", - "collab", - "collab-entity", - "getrandom 0.2.10", - "serde", - "serde_json", - "tokio", - "tokio-stream", - "tracing", -] - -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - -[[package]] -name = "combine" -version = "4.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "console" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3993e6445baa160675931ec041a5e03ca84b9c6e32a056150d3aa2bdda0a1f45" -dependencies = [ - "encode_unicode", - "lazy_static", - "libc", - "regex", - "terminal_size", - "unicode-width", - "winapi", -] - -[[package]] -name = "constant_time_eq" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" - -[[package]] -name = "constant_time_eq" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" - -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - -[[package]] -name = "cookie" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" -dependencies = [ - "percent-encoding", - "time", - "version_check", -] - -[[package]] -name = "cookie_store" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "387461abbc748185c3a6e1673d826918b450b87ff22639429c694619a83b6cf6" -dependencies = [ - "cookie", - "idna 0.3.0", - "log", - "publicsuffix", - "serde", - "serde_derive", - "serde_json", - "time", - "url", -] - -[[package]] -name = "core-foundation" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" - -[[package]] -name = "core-graphics" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "core-graphics-types", - "foreign-types", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bb142d41022986c1d8ff29103a1411c8a3dfad3552f87a4f8dc50d61d4f4e33" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "libc", -] - -[[package]] -name = "cpufeatures" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c" -dependencies = [ - "libc", -] - -[[package]] -name = "crc" -version = "3.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" -dependencies = [ - "crc-catalog", -] - -[[package]] -name = "crc-catalog" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" - -[[package]] -name = "crc32fast" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" -dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "memoffset", - "scopeguard", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "rand_core 0.6.4", - "typenum", -] - -[[package]] -name = "cssparser" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" -dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa 0.4.8", - "matches", - "phf 0.8.0", - "proc-macro2", - "quote", - "smallvec", - "syn 1.0.109", -] - -[[package]] -name = "cssparser" -version = "0.31.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b3df4f93e5fbbe73ec01ec8d3f68bba73107993a5b1e7519273c32db9b0d5be" -dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa 1.0.6", - "phf 0.8.0", - "smallvec", -] - -[[package]] -name = "cssparser-macros" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" -dependencies = [ - "quote", - "syn 2.0.47", -] - -[[package]] -name = "csv" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" -dependencies = [ - "csv-core", - "itoa 1.0.6", - "ryu", - "serde", -] - -[[package]] -name = "csv-core" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" -dependencies = [ - "memchr", -] - -[[package]] -name = "ctor" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e" -dependencies = [ - "quote", - "syn 2.0.47", -] - -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher", -] - -[[package]] -name = "darling" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0558d22a7b463ed0241e993f76f09f30b126687447751a8638587b864e4b3944" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab8bfa2e259f8ee1ce5e97824a3c55ec4404a0d772ca7fa96bf19f0752a046eb" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.10.0", - "syn 2.0.47", -] - -[[package]] -name = "darling_macro" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "dashmap" -version = "5.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" -dependencies = [ - "cfg-if", - "hashbrown 0.14.3", - "lock_api", - "once_cell", - "parking_lot_core 0.9.8", -] - -[[package]] -name = "dashmap" -version = "6.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804c8821570c3f8b70230c2ba75ffa5c0f9a4189b9a432b6656c536712acae28" -dependencies = [ - "cfg-if", - "crossbeam-utils", - "hashbrown 0.14.3", - "lock_api", - "once_cell", - "parking_lot_core 0.9.8", -] - -[[package]] -name = "data-encoding" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" - -[[package]] -name = "database-entity" -version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ea131f0baab67defe7591067357eced490072372#ea131f0baab67defe7591067357eced490072372" -dependencies = [ - "anyhow", - "app-error", - "appflowy-ai-client", - "bincode", - "bytes", - "chrono", - "collab-entity", - "infra", - "prost 0.13.3", - "serde", - "serde_json", - "serde_repr", - "thiserror", - "tracing", - "uuid", - "validator 0.19.0", -] - -[[package]] -name = "date_time_parser" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0521d96e513670773ac503e5f5239178c3aef16cffda1e77a3cdbdbe993fb5a" -dependencies = [ - "chrono", - "regex", -] - -[[package]] -name = "deflate64" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" - -[[package]] -name = "delegate-display" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98a85201f233142ac819bbf6226e36d0b5e129a47bd325084674261c82d4cd66" -dependencies = [ - "macroific", - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "deranged" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", - "serde", -] - -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "derive_arbitrary" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "derive_more" -version = "0.99.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn 1.0.109", -] - -[[package]] -name = "deunicode" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "850878694b7933ca4c9569d30a34b55031b9b139ee1fc7b94a527c4ef960d690" - -[[package]] -name = "diesel" -version = "2.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62c6fcf842f17f8c78ecf7c81d75c5ce84436b41ee07e03f490fbb5f5a8731d8" -dependencies = [ - "chrono", - "diesel_derives", - "libsqlite3-sys", - "r2d2", - "serde_json", - "time", -] - -[[package]] -name = "diesel_derives" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef8337737574f55a468005a83499da720f20c65586241ffea339db9ecdfd2b44" -dependencies = [ - "diesel_table_macro_syntax", - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "diesel_migrations" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6036b3f0120c5961381b570ee20a02432d7e2d27ea60de9578799cf9156914ac" -dependencies = [ - "diesel", - "migrations_internals", - "migrations_macros", -] - -[[package]] -name = "diesel_table_macro_syntax" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5" -dependencies = [ - "syn 2.0.47", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", - "subtle", -] - -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.48.0", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "dispatch" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "dotenv" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" - -[[package]] -name = "downcast-rs" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" - -[[package]] -name = "dtoa" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65d09067bfacaa79114679b279d7f5885b53295b1e2cfb4e79c8e4bd3d633169" - -[[package]] -name = "dtoa-short" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbaceec3c6e4211c79e7b1800fb9680527106beb2f9c51904a3210c03a448c74" -dependencies = [ - "dtoa", -] - -[[package]] -name = "dunce" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" - -[[package]] -name = "dyn-clone" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" - -[[package]] -name = "ego-tree" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a68a4904193147e0a8dec3314640e6db742afd5f6e634f428a6af230d9b3591" - -[[package]] -name = "either" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" - -[[package]] -name = "embed-resource" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80663502655af01a2902dff3f06869330782267924bf1788410b74edcd93770a" -dependencies = [ - "cc", - "rustc_version", - "toml 0.7.5", - "vswhom", - "winreg 0.11.0", -] - -[[package]] -name = "embed_plist" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" - -[[package]] -name = "encode_unicode" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" - -[[package]] -name = "encoding_rs" -version = "0.8.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "equivalent" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" - -[[package]] -name = "errno" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "event-listener" -version = "5.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" -dependencies = [ - "event-listener", - "pin-project-lite", -] - -[[package]] -name = "faccess" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ae66425802d6a903e268ae1a08b8c38ba143520f227a205edf4e9c7e3e26d5" -dependencies = [ - "bitflags 1.3.2", - "libc", - "winapi", -] - -[[package]] -name = "fancy-regex" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0678ab2d46fa5195aaf59ad034c083d351377d4af57f3e073c074d0da3e3c766" -dependencies = [ - "bit-set", - "regex", -] - -[[package]] -name = "fancy-regex" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" -dependencies = [ - "bit-set", - "regex", -] - -[[package]] -name = "fancy-regex" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531e46835a22af56d1e3b66f04844bed63158bc094a628bec1d321d9b4c44bf2" -dependencies = [ - "bit-set", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", -] - -[[package]] -name = "fancy_constructor" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f71f317e4af73b2f8f608fac190c52eac4b1879d2145df1db2fe48881ca69435" -dependencies = [ - "macroific", - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "fastdivide" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25c7df09945d65ea8d70b3321547ed414bbc540aad5bac6883d021b970f35b04" - -[[package]] -name = "fastrand" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" -dependencies = [ - "getrandom 0.2.10", -] - -[[package]] -name = "fdeflate" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "field-offset" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" -dependencies = [ - "memoffset", - "rustc_version", -] - -[[package]] -name = "filetime" -version = "0.2.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.4.1", - "windows-sys 0.52.0", -] - -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - -[[package]] -name = "flate2" -version = "1.0.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" -dependencies = [ - "crc32fast", - "miniz_oxide 0.7.1", -] - -[[package]] -name = "flowy-ai" -version = "0.1.0" -dependencies = [ - "allo-isolate", - "anyhow", - "appflowy-local-ai", - "appflowy-plugin", - "arc-swap", - "base64 0.21.5", - "bytes", - "collab-integrate", - "dashmap 6.0.1", - "flowy-ai-pub", - "flowy-codegen", - "flowy-derive", - "flowy-error", - "flowy-notification", - "flowy-sqlite", - "flowy-storage-pub", - "futures", - "futures-util", - "lib-dispatch", - "lib-infra", - "log", - "md5", - "notify", - "pin-project", - "protobuf", - "reqwest", - "serde", - "serde_json", - "sha2", - "strum_macros 0.21.1", - "tokio", - "tokio-stream", - "tokio-util", - "tracing", - "uuid", - "validator 0.18.1", - "zip 2.2.0", - "zip-extensions", -] - -[[package]] -name = "flowy-ai-pub" -version = "0.1.0" -dependencies = [ - "bytes", - "client-api", - "flowy-error", - "futures", - "lib-infra", - "serde_json", -] - -[[package]] -name = "flowy-ast" -version = "0.1.0" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "flowy-codegen" -version = "0.1.0" -dependencies = [ - "cmd_lib", - "console", - "fancy-regex 0.10.0", - "flowy-ast", - "itertools 0.10.5", - "lazy_static", - "log", - "phf 0.8.0", - "protoc-bin-vendored", - "protoc-rust", - "quote", - "serde", - "serde_json", - "similar 1.3.0", - "syn 1.0.109", - "tera", - "toml 0.5.11", - "walkdir", -] - -[[package]] -name = "flowy-config" -version = "0.1.0" -dependencies = [ - "bytes", - "flowy-codegen", - "flowy-derive", - "flowy-error", - "flowy-sqlite", - "lib-dispatch", - "protobuf", - "strum_macros 0.21.1", -] - -[[package]] -name = "flowy-core" -version = "0.1.0" -dependencies = [ - "anyhow", - "appflowy-local-ai", - "arc-swap", - "base64 0.21.5", - "bytes", - "client-api", - "collab", - "collab-entity", - "collab-folder", - "collab-integrate", - "collab-plugins", - "dashmap 6.0.1", - "diesel", - "flowy-ai", - "flowy-ai-pub", - "flowy-config", - "flowy-database-pub", - "flowy-database2", - "flowy-date", - "flowy-document", - "flowy-document-pub", - "flowy-error", - "flowy-folder", - "flowy-folder-pub", - "flowy-search", - "flowy-search-pub", - "flowy-server", - "flowy-server-pub", - "flowy-sqlite", - "flowy-storage", - "flowy-storage-pub", - "flowy-user", - "flowy-user-pub", - "futures", - "futures-core", - "lib-dispatch", - "lib-infra", - "lib-log", - "semver", - "serde", - "serde_json", - "serde_repr", - "sysinfo", - "tokio", - "tokio-stream", - "tracing", - "uuid", - "walkdir", -] - -[[package]] -name = "flowy-database-pub" -version = "0.1.0" -dependencies = [ - "anyhow", - "client-api", - "collab", - "collab-entity", - "flowy-error", - "lib-infra", -] - -[[package]] -name = "flowy-database2" -version = "0.1.0" -dependencies = [ - "anyhow", - "arc-swap", - "async-stream", - "async-trait", - "bytes", - "chrono", - "chrono-tz 0.8.2", - "collab", - "collab-database", - "collab-entity", - "collab-integrate", - "collab-plugins", - "csv", - "dashmap 6.0.1", - "fancy-regex 0.11.0", - "flowy-codegen", - "flowy-database-pub", - "flowy-derive", - "flowy-error", - "flowy-notification", - "futures", - "indexmap 2.1.0", - "lazy_static", - "lib-dispatch", - "lib-infra", - "moka", - "nanoid", - "protobuf", - "rayon", - "rust_decimal", - "rusty-money", - "serde", - "serde_json", - "serde_repr", - "strum", - "strum_macros 0.25.2", - "tokio", - "tokio-util", - "tracing", - "url", - "validator 0.18.1", -] - -[[package]] -name = "flowy-date" -version = "0.1.0" -dependencies = [ - "bytes", - "chrono", - "date_time_parser", - "fancy-regex 0.11.0", - "flowy-codegen", - "flowy-derive", - "flowy-error", - "lib-dispatch", - "protobuf", - "strum_macros 0.21.1", - "tracing", -] - -[[package]] -name = "flowy-derive" -version = "0.1.0" -dependencies = [ - "dashmap 6.0.1", - "flowy-ast", - "flowy-codegen", - "lazy_static", - "proc-macro2", - "quote", - "serde_json", - "syn 1.0.109", - "walkdir", -] - -[[package]] -name = "flowy-document" -version = "0.1.0" -dependencies = [ - "anyhow", - "bytes", - "collab", - "collab-document", - "collab-entity", - "collab-integrate", - "collab-plugins", - "dashmap 6.0.1", - "flowy-codegen", - "flowy-derive", - "flowy-document-pub", - "flowy-error", - "flowy-notification", - "flowy-storage-pub", - "futures", - "getrandom 0.2.10", - "indexmap 2.1.0", - "lib-dispatch", - "lib-infra", - "nanoid", - "protobuf", - "scraper 0.18.1", - "serde", - "serde_json", - "strum_macros 0.21.1", - "tokio", - "tokio-stream", - "tracing", - "uuid", - "validator 0.18.1", -] - -[[package]] -name = "flowy-document-pub" -version = "0.1.0" -dependencies = [ - "anyhow", - "collab", - "collab-document", - "flowy-error", - "lib-infra", -] - -[[package]] -name = "flowy-encrypt" -version = "0.1.0" -dependencies = [ - "aes-gcm", - "anyhow", - "base64 0.21.5", - "getrandom 0.2.10", - "hmac", - "pbkdf2 0.12.2", - "rand 0.8.5", - "sha2", -] - -[[package]] -name = "flowy-error" -version = "0.1.0" -dependencies = [ - "anyhow", - "bytes", - "client-api", - "collab", - "collab-database", - "collab-document", - "collab-folder", - "collab-plugins", - "fancy-regex 0.11.0", - "flowy-codegen", - "flowy-derive", - "flowy-sqlite", - "lib-dispatch", - "protobuf", - "r2d2", - "reqwest", - "serde", - "serde_json", - "serde_repr", - "tantivy", - "thiserror", - "tokio", - "url", - "validator 0.18.1", -] - -[[package]] -name = "flowy-folder" -version = "0.1.0" -dependencies = [ - "arc-swap", - "async-trait", - "bytes", - "chrono", - "client-api", - "collab", - "collab-document", - "collab-entity", - "collab-folder", - "collab-integrate", - "collab-plugins", - "dashmap 6.0.1", - "flowy-codegen", - "flowy-derive", - "flowy-error", - "flowy-folder-pub", - "flowy-notification", - "flowy-search-pub", - "flowy-sqlite", - "futures", - "lazy_static", - "lib-dispatch", - "lib-infra", - "nanoid", - "protobuf", - "regex", - "serde", - "serde_json", - "strum_macros 0.21.1", - "tokio", - "tokio-stream", - "tracing", - "unicode-segmentation", - "uuid", - "validator 0.18.1", -] - -[[package]] -name = "flowy-folder-pub" -version = "0.1.0" -dependencies = [ - "anyhow", - "client-api", - "collab", - "collab-entity", - "collab-folder", - "flowy-error", - "lib-infra", - "serde", - "serde_json", - "uuid", -] - -[[package]] -name = "flowy-notification" -version = "0.1.0" -dependencies = [ - "bytes", - "dashmap 6.0.1", - "flowy-codegen", - "flowy-derive", - "lazy_static", - "lib-dispatch", - "protobuf", - "serde", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "flowy-search" -version = "0.1.0" -dependencies = [ - "async-stream", - "bytes", - "collab", - "collab-folder", - "diesel", - "diesel_derives", - "diesel_migrations", - "flowy-codegen", - "flowy-derive", - "flowy-error", - "flowy-folder", - "flowy-notification", - "flowy-search-pub", - "flowy-sqlite", - "flowy-user", - "futures", - "lib-dispatch", - "lib-infra", - "protobuf", - "serde", - "serde_json", - "strsim 0.11.0", - "strum_macros 0.26.1", - "tantivy", - "tempfile", - "tokio", - "tracing", - "validator 0.18.1", -] - -[[package]] -name = "flowy-search-pub" -version = "0.1.0" -dependencies = [ - "client-api", - "collab", - "collab-folder", - "flowy-error", - "futures", - "lib-infra", -] - -[[package]] -name = "flowy-server" -version = "0.1.0" -dependencies = [ - "anyhow", - "arc-swap", - "bytes", - "chrono", - "client-api", - "collab", - "collab-database", - "collab-document", - "collab-entity", - "collab-folder", - "collab-plugins", - "collab-user", - "dashmap 6.0.1", - "flowy-ai-pub", - "flowy-database-pub", - "flowy-document-pub", - "flowy-encrypt", - "flowy-error", - "flowy-folder-pub", - "flowy-search-pub", - "flowy-server-pub", - "flowy-storage", - "flowy-storage-pub", - "flowy-user-pub", - "futures", - "futures-util", - "hex", - "hyper", - "lazy_static", - "lib-dispatch", - "lib-infra", - "mime_guess", - "postgrest", - "rand 0.8.5", - "reqwest", - "semver", - "serde", - "serde_json", - "thiserror", - "tokio", - "tokio-retry", - "tokio-stream", - "tokio-util", - "tracing", - "url", - "uuid", - "yrs", -] - -[[package]] -name = "flowy-server-pub" -version = "0.1.0" -dependencies = [ - "flowy-error", - "serde", - "serde_repr", -] - -[[package]] -name = "flowy-sqlite" -version = "0.1.0" -dependencies = [ - "anyhow", - "diesel", - "diesel_derives", - "diesel_migrations", - "libsqlite3-sys", - "r2d2", - "scheduled-thread-pool", - "serde", - "serde_json", - "thiserror", - "tracing", -] - -[[package]] -name = "flowy-storage" -version = "0.1.0" -dependencies = [ - "allo-isolate", - "anyhow", - "async-trait", - "bytes", - "chrono", - "collab-importer", - "dashmap 6.0.1", - "flowy-codegen", - "flowy-derive", - "flowy-error", - "flowy-notification", - "flowy-sqlite", - "flowy-storage-pub", - "futures-util", - "fxhash", - "lib-dispatch", - "lib-infra", - "mime_guess", - "protobuf", - "serde", - "serde_json", - "strum_macros 0.25.2", - "tokio", - "tracing", - "url", -] - -[[package]] -name = "flowy-storage-pub" -version = "0.1.0" -dependencies = [ - "anyhow", - "async-trait", - "bytes", - "client-api-entity", - "flowy-error", - "lib-infra", - "mime", - "mime_guess", - "serde", - "serde_json", - "tokio", - "tracing", -] - -[[package]] -name = "flowy-user" -version = "0.1.0" -dependencies = [ - "anyhow", - "arc-swap", - "base64 0.21.5", - "bytes", - "chrono", - "client-api", - "collab", - "collab-database", - "collab-document", - "collab-entity", - "collab-folder", - "collab-integrate", - "collab-plugins", - "collab-user", - "dashmap 6.0.1", - "diesel", - "diesel_derives", - "fancy-regex 0.11.0", - "flowy-codegen", - "flowy-derive", - "flowy-encrypt", - "flowy-error", - "flowy-folder-pub", - "flowy-notification", - "flowy-server-pub", - "flowy-sqlite", - "flowy-user-pub", - "lazy_static", - "lib-dispatch", - "lib-infra", - "once_cell", - "protobuf", - "rayon", - "semver", - "serde", - "serde_json", - "serde_repr", - "strum", - "strum_macros 0.25.2", - "tokio", - "tokio-stream", - "tracing", - "unicode-segmentation", - "uuid", - "validator 0.18.1", -] - -[[package]] -name = "flowy-user-pub" -version = "0.1.0" -dependencies = [ - "anyhow", - "base64 0.21.5", - "chrono", - "client-api", - "collab", - "collab-entity", - "collab-folder", - "flowy-error", - "flowy-folder-pub", - "lib-infra", - "serde", - "serde_json", - "serde_repr", - "tokio", - "tokio-stream", - "tracing", - "uuid", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "fs4" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e180ac76c23b45e767bd7ae9579bc0bb458618c4bc71835926e098e61d15f8" -dependencies = [ - "rustix", - "windows-sys 0.52.0", -] - -[[package]] -name = "fsevent-sys" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" -dependencies = [ - "libc", -] - -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - -[[package]] -name = "futf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" -dependencies = [ - "mac", - "new_debug_unreachable", -] - -[[package]] -name = "futures" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" - -[[package]] -name = "futures-executor" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" - -[[package]] -name = "futures-lite" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - -[[package]] -name = "futures-macro" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "futures-sink" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" - -[[package]] -name = "futures-task" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" - -[[package]] -name = "futures-util" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - -[[package]] -name = "gdk" -version = "0.15.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6e05c1f572ab0e1f15be94217f0dc29088c248b14f792a5ff0af0d84bcda9e8" -dependencies = [ - "bitflags 1.3.2", - "cairo-rs", - "gdk-pixbuf", - "gdk-sys", - "gio", - "glib", - "libc", - "pango", -] - -[[package]] -name = "gdk-pixbuf" -version = "0.15.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad38dd9cc8b099cceecdf41375bb6d481b1b5a7cd5cd603e10a69a9383f8619a" -dependencies = [ - "bitflags 1.3.2", - "gdk-pixbuf-sys", - "gio", - "glib", - "libc", -] - -[[package]] -name = "gdk-pixbuf-sys" -version = "0.15.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140b2f5378256527150350a8346dbdb08fadc13453a7a2d73aecd5fab3c402a7" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps 6.1.1", -] - -[[package]] -name = "gdk-sys" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e7a08c1e8f06f4177fb7e51a777b8c1689f743a7bc11ea91d44d2226073a88" -dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "pkg-config", - "system-deps 6.1.1", -] - -[[package]] -name = "gdkwayland-sys" -version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cca49a59ad8cfdf36ef7330fe7bdfbe1d34323220cc16a0de2679ee773aee2c2" -dependencies = [ - "gdk-sys", - "glib-sys", - "gobject-sys", - "libc", - "pkg-config", - "system-deps 6.1.1", -] - -[[package]] -name = "gdkx11-sys" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b7f8c7a84b407aa9b143877e267e848ff34106578b64d1e0a24bf550716178" -dependencies = [ - "gdk-sys", - "glib-sys", - "libc", - "system-deps 6.1.1", - "x11", -] - -[[package]] -name = "generator" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" -dependencies = [ - "cc", - "libc", - "log", - "rustversion", - "windows 0.48.0", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "gethostname" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "getopts" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" -dependencies = [ - "unicode-width", -] - -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", -] - -[[package]] -name = "ghash" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" -dependencies = [ - "opaque-debug", - "polyval", -] - -[[package]] -name = "gimli" -version = "0.27.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" - -[[package]] -name = "gio" -version = "0.15.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68fdbc90312d462781a395f7a16d96a2b379bb6ef8cd6310a2df272771c4283b" -dependencies = [ - "bitflags 1.3.2", - "futures-channel", - "futures-core", - "futures-io", - "gio-sys", - "glib", - "libc", - "once_cell", - "thiserror", -] - -[[package]] -name = "gio-sys" -version = "0.15.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32157a475271e2c4a023382e9cab31c4584ee30a97da41d3c4e9fdd605abcf8d" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps 6.1.1", - "winapi", -] - -[[package]] -name = "glib" -version = "0.15.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb0306fbad0ab5428b0ca674a23893db909a98582969c9b537be4ced78c505d" -dependencies = [ - "bitflags 1.3.2", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "once_cell", - "smallvec", - "thiserror", -] - -[[package]] -name = "glib-macros" -version = "0.15.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10c6ae9f6fa26f4fb2ac16b528d138d971ead56141de489f8111e259b9df3c4a" -dependencies = [ - "anyhow", - "heck 0.4.1", - "proc-macro-crate 1.3.1", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "glib-sys" -version = "0.15.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4b192f8e65e9cf76cbf4ea71fa8e3be4a0e18ffe3d68b8da6836974cc5bad4" -dependencies = [ - "libc", - "system-deps 6.1.1", -] - -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - -[[package]] -name = "globset" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc" -dependencies = [ - "aho-corasick 0.7.20", - "bstr", - "fnv", - "log", - "regex", -] - -[[package]] -name = "globwalk" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" -dependencies = [ - "bitflags 1.3.2", - "ignore", - "walkdir", -] - -[[package]] -name = "gloo-utils" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e" -dependencies = [ - "js-sys", - "serde", - "serde_json", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "gobject-sys" -version = "0.15.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d57ce44246becd17153bd035ab4d32cfee096a657fc01f2231c9278378d1e0a" -dependencies = [ - "glib-sys", - "libc", - "system-deps 6.1.1", -] - -[[package]] -name = "gotrue" -version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ea131f0baab67defe7591067357eced490072372#ea131f0baab67defe7591067357eced490072372" -dependencies = [ - "anyhow", - "futures-util", - "getrandom 0.2.10", - "gotrue-entity", - "infra", - "reqwest", - "serde", - "serde_json", - "tokio", - "tracing", -] - -[[package]] -name = "gotrue-entity" -version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ea131f0baab67defe7591067357eced490072372#ea131f0baab67defe7591067357eced490072372" -dependencies = [ - "anyhow", - "app-error", - "chrono", - "jsonwebtoken", - "lazy_static", - "serde", - "serde_json", -] - -[[package]] -name = "gtk" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e3004a2d5d6d8b5057d2b57b3712c9529b62e82c77f25c1fecde1fd5c23bd0" -dependencies = [ - "atk", - "bitflags 1.3.2", - "cairo-rs", - "field-offset", - "futures-channel", - "gdk", - "gdk-pixbuf", - "gio", - "glib", - "gtk-sys", - "gtk3-macros", - "libc", - "once_cell", - "pango", - "pkg-config", -] - -[[package]] -name = "gtk-sys" -version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5bc2f0587cba247f60246a0ca11fe25fb733eabc3de12d1965fc07efab87c84" -dependencies = [ - "atk-sys", - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "system-deps 6.1.1", -] - -[[package]] -name = "gtk3-macros" -version = "0.15.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "684c0456c086e8e7e9af73ec5b84e35938df394712054550e81558d21c44ab0d" -dependencies = [ - "anyhow", - "proc-macro-crate 1.3.1", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "h2" -version = "0.3.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap 1.9.3", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash 0.7.6", -] - -[[package]] -name = "hashbrown" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" -dependencies = [ - "ahash 0.8.6", - "allocator-api2", -] - -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "hermit-abi" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "html5ever" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5c13fb08e5d4dfc151ee5e88bae63f7773d61852f3bdc73c9f4b9e1bde03148" -dependencies = [ - "log", - "mac", - "markup5ever 0.10.1", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "html5ever" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" -dependencies = [ - "log", - "mac", - "markup5ever 0.11.0", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "htmlescape" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163" - -[[package]] -name = "http" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" -dependencies = [ - "bytes", - "fnv", - "itoa 1.0.6", -] - -[[package]] -name = "http-body" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - -[[package]] -name = "http-range" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" - -[[package]] -name = "httparse" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" - -[[package]] -name = "httpdate" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" - -[[package]] -name = "humansize" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" -dependencies = [ - "libm", -] - -[[package]] -name = "hyper" -version = "0.14.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa 1.0.6", - "pin-project-lite", - "socket2 0.4.9", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0646026eb1b3eea4cd9ba47912ea5ce9cc07713d105b1a14698f4e6433d348b7" -dependencies = [ - "http", - "hyper", - "rustls", - "tokio", - "tokio-rustls", -] - -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper", - "native-tls", - "tokio", - "tokio-native-tls", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "ico" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3804960be0bb5e4edb1e1ad67afd321a9ecfd875c3e65c099468fd2717d7cae" -dependencies = [ - "byteorder", - "png", -] - -[[package]] -name = "icu_collections" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locid" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - -[[package]] -name = "icu_normalizer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "write16", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" - -[[package]] -name = "icu_properties" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" - -[[package]] -name = "icu_provider" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "idna" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "idna" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "ignore" -version = "0.4.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" -dependencies = [ - "globset", - "lazy_static", - "log", - "memchr", - "regex", - "same-file", - "thread_local", - "walkdir", - "winapi-util", -] - -[[package]] -name = "image" -version = "0.24.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527909aa81e20ac3a44803521443a765550f09b5130c2c2fa1ea59c2f8f50a3a" -dependencies = [ - "bytemuck", - "byteorder", - "color_quant", - "num-rational", - "num-traits", -] - -[[package]] -name = "indexed_db_futures" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0704b71f13f81b5933d791abf2de26b33c40935143985220299a357721166706" -dependencies = [ - "accessory", - "cfg-if", - "delegate-display", - "fancy_constructor", - "js-sys", - "uuid", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - -[[package]] -name = "indexmap" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" -dependencies = [ - "equivalent", - "hashbrown 0.14.3", - "serde", -] - -[[package]] -name = "infer" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f551f8c3a39f68f986517db0d1759de85881894fdc7db798bd2a9df9cb04b7fc" -dependencies = [ - "cfb", -] - -[[package]] -name = "infra" -version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ea131f0baab67defe7591067357eced490072372#ea131f0baab67defe7591067357eced490072372" -dependencies = [ - "anyhow", - "bytes", - "futures", - "pin-project", - "reqwest", - "serde", - "serde_json", - "tokio", - "tracing", - "validator 0.19.0", -] - -[[package]] -name = "inotify" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" -dependencies = [ - "bitflags 1.3.2", - "inotify-sys", - "libc", -] - -[[package]] -name = "inotify-sys" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" -dependencies = [ - "libc", -] - -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "generic-array", -] - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "interprocess" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81f2533f3be42fffe3b5e63b71aeca416c1c3bc33e4e27be018521e76b1f38fb" -dependencies = [ - "cfg-if", - "libc", - "rustc_version", - "to_method", - "winapi", -] - -[[package]] -name = "ipnet" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - -[[package]] -name = "itoa" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" - -[[package]] -name = "javascriptcore-rs" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf053e7843f2812ff03ef5afe34bb9c06ffee120385caad4f6b9967fcd37d41c" -dependencies = [ - "bitflags 1.3.2", - "glib", - "javascriptcore-rs-sys", -] - -[[package]] -name = "javascriptcore-rs-sys" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "905fbb87419c5cde6e3269537e4ea7d46431f3008c5d057e915ef3f115e7793c" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps 5.0.0", -] - -[[package]] -name = "jni" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c" -dependencies = [ - "cesu8", - "combine", - "jni-sys", - "log", - "thiserror", - "walkdir", -] - -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - -[[package]] -name = "jobserver" -version = "0.1.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" -dependencies = [ - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.67" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "json-patch" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ff1e1486799e3f64129f8ccad108b38290df9cd7015cd31bed17239f0789d6" -dependencies = [ - "serde", - "serde_json", - "thiserror", - "treediff", -] - -[[package]] -name = "jsonwebtoken" -version = "8.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" -dependencies = [ - "base64 0.21.5", - "pem", - "ring", - "serde", - "serde_json", - "simple_asn1", -] - -[[package]] -name = "kqueue" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" -dependencies = [ - "kqueue-sys", - "libc", -] - -[[package]] -name = "kqueue-sys" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" -dependencies = [ - "bitflags 1.3.2", - "libc", -] - -[[package]] -name = "kuchiki" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ea8e9c6e031377cff82ee3001dc8026cdf431ed4e2e6b51f98ab8c73484a358" -dependencies = [ - "cssparser 0.27.2", - "html5ever 0.25.2", - "matches", - "selectors 0.22.0", -] - -[[package]] -name = "kuchikiki" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8" -dependencies = [ - "cssparser 0.27.2", - "html5ever 0.26.0", - "indexmap 1.9.3", - "matches", - "selectors 0.22.0", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - -[[package]] -name = "levenshtein_automata" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2cdeb66e45e9f36bfad5bbdb4d2384e70936afbee843c6f6543f0c551ebb25" - -[[package]] -name = "lib-dispatch" -version = "0.1.0" -dependencies = [ - "bincode", - "bytes", - "derivative", - "dyn-clone", - "futures", - "futures-channel", - "futures-core", - "futures-util", - "getrandom 0.2.10", - "nanoid", - "pin-project", - "protobuf", - "serde", - "serde_json", - "serde_repr", - "thread-id", - "tokio", - "tracing", - "validator 0.18.1", - "wasm-bindgen", - "wasm-bindgen-futures", -] - -[[package]] -name = "lib-infra" -version = "0.1.0" -dependencies = [ - "allo-isolate", - "anyhow", - "async-trait", - "atomic_refcell", - "bytes", - "cfg-if", - "chrono", - "futures", - "futures-core", - "futures-util", - "md5", - "pin-project", - "tempfile", - "tokio", - "tracing", - "validator 0.18.1", - "walkdir", - "zip 2.2.0", -] - -[[package]] -name = "lib-log" -version = "0.1.0" -dependencies = [ - "chrono", - "lazy_static", - "lib-infra", - "serde", - "serde_json", - "tracing", - "tracing-appender", - "tracing-bunyan-formatter", - "tracing-core", - "tracing-subscriber", -] - -[[package]] -name = "libc" -version = "0.2.152" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" - -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] - -[[package]] -name = "libm" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" - -[[package]] -name = "librocksdb-sys" -version = "0.16.0+8.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce3d60bc059831dc1c83903fb45c103f75db65c5a7bf22272764d9cc683e348c" -dependencies = [ - "bindgen", - "bzip2-sys", - "cc", - "glob", - "libc", - "libz-sys", - "zstd-sys", -] - -[[package]] -name = "libsqlite3-sys" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "libz-sys" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ee889ecc9568871456d42f603d6a0ce59ff328d291063a45cbdf0036baf6db" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "line-wrap" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" -dependencies = [ - "safemem", -] - -[[package]] -name = "linux-raw-sys" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" - -[[package]] -name = "litemap" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" - -[[package]] -name = "lock_api" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "lockfree-object-pool" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" - -[[package]] -name = "log" -version = "0.4.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" - -[[package]] -name = "loom" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" -dependencies = [ - "cfg-if", - "generator", - "pin-utils", - "scoped-tls", - "serde", - "serde_json", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "lru" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" -dependencies = [ - "hashbrown 0.14.3", -] - -[[package]] -name = "lz4_flex" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "912b45c753ff5f7f5208307e8ace7d2a2e30d024e26d3509f3dce546c044ce15" - -[[package]] -name = "lzma-rs" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" -dependencies = [ - "byteorder", - "crc", -] - -[[package]] -name = "lzma-sys" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - -[[package]] -name = "mac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" - -[[package]] -name = "macroific" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05c00ac596022625d01047c421a0d97d7f09a18e429187b341c201cb631b9dd" -dependencies = [ - "macroific_attr_parse", - "macroific_core", - "macroific_macro", -] - -[[package]] -name = "macroific_attr_parse" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd94d5da95b30ae6e10621ad02340909346ad91661f3f8c0f2b62345e46a2f67" -dependencies = [ - "cfg-if", - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "macroific_core" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13198c120864097a565ccb3ff947672d969932b7975ebd4085732c9f09435e55" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "macroific_macro" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c9853143cbed7f1e41dc39fee95f9b361bec65c8dc2a01bf609be01b61f5ae" -dependencies = [ - "macroific_attr_parse", - "macroific_core", - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - -[[package]] -name = "markdown" -version = "1.0.0-alpha.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6491e6c702bf7e3b24e769d800746d5f2c06a6c6a2db7992612e0f429029e81" -dependencies = [ - "unicode-id", -] - -[[package]] -name = "markup5ever" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd" -dependencies = [ - "log", - "phf 0.8.0", - "phf_codegen 0.8.0", - "string_cache", - "string_cache_codegen", - "tendril", -] - -[[package]] -name = "markup5ever" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" -dependencies = [ - "log", - "phf 0.10.1", - "phf_codegen 0.10.0", - "string_cache", - "string_cache_codegen", - "tendril", -] - -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata 0.1.10", -] - -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - -[[package]] -name = "md5" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" - -[[package]] -name = "measure_time" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56220900f1a0923789ecd6bf25fbae8af3b2f1ff3e9e297fc9b6b8674dd4d852" -dependencies = [ - "instant", - "log", -] - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "memmap2" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" -dependencies = [ - "libc", -] - -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] - -[[package]] -name = "migrations_internals" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f23f71580015254b020e856feac3df5878c2c7a8812297edd6c0a485ac9dada" -dependencies = [ - "serde", - "toml 0.7.5", -] - -[[package]] -name = "migrations_macros" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cce3325ac70e67bbab5bd837a31cae01f1a6db64e0e744a33cb03a543469ef08" -dependencies = [ - "migrations_internals", - "proc-macro2", - "quote", -] - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "mime_guess" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" -dependencies = [ - "mime", - "unicase", -] - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" -dependencies = [ - "adler", -] - -[[package]] -name = "miniz_oxide" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" -dependencies = [ - "adler", - "simd-adler32", -] - -[[package]] -name = "mio" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" -dependencies = [ - "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.48.0", -] - -[[package]] -name = "moka" -version = "0.12.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32cf62eb4dd975d2dde76432fb1075c49e3ee2331cf36f1f8fd4b66550d32b6f" -dependencies = [ - "async-lock", - "async-trait", - "crossbeam-channel", - "crossbeam-epoch", - "crossbeam-utils", - "event-listener", - "futures-util", - "once_cell", - "parking_lot 0.12.1", - "quanta", - "rustc_version", - "smallvec", - "tagptr", - "thiserror", - "triomphe", - "uuid", -] - -[[package]] -name = "multimap" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" - -[[package]] -name = "murmurhash32" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9380db4c04d219ac5c51d14996bbf2c2e9a15229771b53f8671eb6c83cf44df" - -[[package]] -name = "nanoid" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" -dependencies = [ - "rand 0.8.5", -] - -[[package]] -name = "native-tls" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "ndk" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4" -dependencies = [ - "bitflags 1.3.2", - "jni-sys", - "ndk-sys", - "num_enum", - "thiserror", -] - -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - -[[package]] -name = "ndk-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97" -dependencies = [ - "jni-sys", -] - -[[package]] -name = "new_debug_unreachable" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" - -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "notify" -version = "6.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" -dependencies = [ - "bitflags 2.4.0", - "crossbeam-channel", - "filetime", - "fsevent-sys", - "inotify", - "kqueue", - "libc", - "log", - "mio", - "walkdir", - "windows-sys 0.48.0", -] - -[[package]] -name = "ntapi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" -dependencies = [ - "winapi", -] - -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - -[[package]] -name = "num-bigint" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "num-integer" -version = "0.1.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "num_cpus" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "num_enum" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" -dependencies = [ - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", - "objc_exception", -] - -[[package]] -name = "objc-foundation" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" -dependencies = [ - "block", - "objc", - "objc_id", -] - -[[package]] -name = "objc-sys" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c71324e4180d0899963fc83d9d241ac39e699609fc1025a850aadac8257459" - -[[package]] -name = "objc2" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d" -dependencies = [ - "objc-sys", - "objc2-encode", -] - -[[package]] -name = "objc2-encode" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666" - -[[package]] -name = "objc_exception" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" -dependencies = [ - "cc", -] - -[[package]] -name = "objc_id" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" -dependencies = [ - "objc", -] - -[[package]] -name = "object" -version = "0.30.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "oneshot" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f6640c6bda7731b1fdbab747981a0f896dd1fedaf9f4a53fa237a04a84431f4" -dependencies = [ - "loom", -] - -[[package]] -name = "opaque-debug" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" - -[[package]] -name = "open" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2078c0039e6a54a0c42c28faa984e115fb4c2d5bf2208f77d1961002df8576f8" -dependencies = [ - "pathdiff", - "windows-sys 0.42.0", -] - -[[package]] -name = "openssl" -version = "0.10.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-src" -version = "111.27.0+1.1.1v" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e8f197c82d7511c5b014030c9b1efeda40d7d5f99d23b4ceed3524a5e63f02" -dependencies = [ - "cc", -] - -[[package]] -name = "openssl-sys" -version = "0.9.90" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" -dependencies = [ - "cc", - "libc", - "openssl-src", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "os_pipe" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb233f06c2307e1f5ce2ecad9f8121cffbbee2c95428f44ea85222e460d0d213" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - -[[package]] -name = "ownedbytes" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a059efb063b8f425b948e042e6b9bd85edfe60e913630ed727b23e2dfcc558" -dependencies = [ - "stable_deref_trait", -] - -[[package]] -name = "pango" -version = "0.15.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e4045548659aee5313bde6c582b0d83a627b7904dd20dc2d9ef0895d414e4f" -dependencies = [ - "bitflags 1.3.2", - "glib", - "libc", - "once_cell", - "pango-sys", -] - -[[package]] -name = "pango-sys" -version = "0.15.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2a00081cde4661982ed91d80ef437c20eacaf6aa1a5962c0279ae194662c3aa" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps 6.1.1", -] - -[[package]] -name = "parking" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" - -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.6", -] - -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core 0.9.8", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall 0.2.16", - "smallvec", - "winapi", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.3.5", - "smallvec", - "windows-targets 0.48.0", -] - -[[package]] -name = "parse-zoneinfo" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" -dependencies = [ - "regex", -] - -[[package]] -name = "password-hash" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" -dependencies = [ - "base64ct", - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "pathdiff" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" - -[[package]] -name = "pbkdf2" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" -dependencies = [ - "digest", - "hmac", - "password-hash", - "sha2", -] - -[[package]] -name = "pbkdf2" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" -dependencies = [ - "digest", - "hmac", -] - -[[package]] -name = "pem" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" -dependencies = [ - "base64 0.13.1", -] - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "pest" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f73935e4d55e2abf7f130186537b19e7a4abc886a0252380b59248af473a3fc9" -dependencies = [ - "thiserror", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aef623c9bbfa0eedf5a0efba11a5ee83209c326653ca31ff019bec3a95bfff2b" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e8cba4ec22bada7fc55ffe51e2deb6a0e0db2d0b7ab0b103acc80d2510c190" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "pest_meta" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01f71cb40bd8bb94232df14b946909e14660e33fc05db3e50ae2a82d7ea0ca0" -dependencies = [ - "once_cell", - "pest", - "sha2", -] - -[[package]] -name = "petgraph" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" -dependencies = [ - "fixedbitset", - "indexmap 2.1.0", -] - -[[package]] -name = "phf" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" -dependencies = [ - "phf_macros 0.8.0", - "phf_shared 0.8.0", - "proc-macro-hack", -] - -[[package]] -name = "phf" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" -dependencies = [ - "phf_shared 0.10.0", -] - -[[package]] -name = "phf" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" -dependencies = [ - "phf_macros 0.11.2", - "phf_shared 0.11.2", -] - -[[package]] -name = "phf_codegen" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" -dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", -] - -[[package]] -name = "phf_codegen" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" -dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", -] - -[[package]] -name = "phf_codegen" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" -dependencies = [ - "phf_generator 0.11.2", - "phf_shared 0.11.2", -] - -[[package]] -name = "phf_generator" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" -dependencies = [ - "phf_shared 0.8.0", - "rand 0.7.3", -] - -[[package]] -name = "phf_generator" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" -dependencies = [ - "phf_shared 0.10.0", - "rand 0.8.5", -] - -[[package]] -name = "phf_generator" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" -dependencies = [ - "phf_shared 0.11.2", - "rand 0.8.5", -] - -[[package]] -name = "phf_macros" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" -dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", - "proc-macro-hack", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "phf_macros" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" -dependencies = [ - "phf_generator 0.11.2", - "phf_shared 0.11.2", - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "phf_shared" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" -dependencies = [ - "siphasher", -] - -[[package]] -name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher", - "uncased", -] - -[[package]] -name = "phf_shared" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pin-project" -version = "1.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkg-config" -version = "0.3.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" - -[[package]] -name = "plist" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bd9647b268a3d3e14ff09c23201133a62589c658db02bb7388c7246aafe0590" -dependencies = [ - "base64 0.21.5", - "indexmap 1.9.3", - "line-wrap", - "quick-xml", - "serde", - "time", -] - -[[package]] -name = "png" -version = "0.17.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59871cc5b6cce7eaccca5a802b4173377a1c2ba90654246789a8fa2334426d11" -dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide 0.7.1", -] - -[[package]] -name = "polyval" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" -dependencies = [ - "cfg-if", - "cpufeatures", - "opaque-debug", - "universal-hash", -] - -[[package]] -name = "postgrest" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e66400cb23a379592bc8c8bdc9adda652eef4a969b74ab78454a8e8c11330c2b" -dependencies = [ - "reqwest", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - -[[package]] -name = "prettyplease" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9825a04601d60621feed79c4e6b56d65db77cdca55cef43b46b0de1096d1c282" -dependencies = [ - "proc-macro2", - "syn 2.0.47", -] - -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.11", -] - -[[package]] -name = "proc-macro-crate" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" -dependencies = [ - "toml_datetime", - "toml_edit 0.20.2", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - -[[package]] -name = "proc-macro2" -version = "1.0.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "907a61bd0f64c2f29cd1cf1dc34d05176426a3f504a78010f08416ddb7b13708" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "prost" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" -dependencies = [ - "bytes", - "prost-derive 0.12.3", -] - -[[package]] -name = "prost" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f" -dependencies = [ - "bytes", - "prost-derive 0.13.3", -] - -[[package]] -name = "prost-build" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2" -dependencies = [ - "bytes", - "heck 0.4.1", - "itertools 0.10.5", - "log", - "multimap", - "once_cell", - "petgraph", - "prettyplease", - "prost 0.12.3", - "prost-types", - "regex", - "syn 2.0.47", - "tempfile", - "which", -] - -[[package]] -name = "prost-derive" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" -dependencies = [ - "anyhow", - "itertools 0.10.5", - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "prost-derive" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" -dependencies = [ - "anyhow", - "itertools 0.12.1", - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "prost-types" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" -dependencies = [ - "prost 0.12.3", -] - -[[package]] -name = "protobuf" -version = "2.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" - -[[package]] -name = "protobuf-codegen" -version = "2.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "033460afb75cf755fcfc16dfaed20b86468082a2ea24e05ac35ab4a099a017d6" -dependencies = [ - "protobuf", -] - -[[package]] -name = "protoc" -version = "2.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0218039c514f9e14a5060742ecd50427f8ac4f85a6dc58f2ddb806e318c55ee" -dependencies = [ - "log", - "which", -] - -[[package]] -name = "protoc-bin-vendored" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "005ca8623e5633e298ad1f917d8be0a44bcf406bf3cde3b80e63003e49a3f27d" -dependencies = [ - "protoc-bin-vendored-linux-aarch_64", - "protoc-bin-vendored-linux-ppcle_64", - "protoc-bin-vendored-linux-x86_32", - "protoc-bin-vendored-linux-x86_64", - "protoc-bin-vendored-macos-x86_64", - "protoc-bin-vendored-win32", -] - -[[package]] -name = "protoc-bin-vendored-linux-aarch_64" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb9fc9cce84c8694b6ea01cc6296617b288b703719b725b8c9c65f7c5874435" - -[[package]] -name = "protoc-bin-vendored-linux-ppcle_64" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d2a07dcf7173a04d49974930ccbfb7fd4d74df30ecfc8762cf2f895a094516" - -[[package]] -name = "protoc-bin-vendored-linux-x86_32" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54fef0b04fcacba64d1d80eed74a20356d96847da8497a59b0a0a436c9165b0" - -[[package]] -name = "protoc-bin-vendored-linux-x86_64" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8782f2ce7d43a9a5c74ea4936f001e9e8442205c244f7a3d4286bd4c37bc924" - -[[package]] -name = "protoc-bin-vendored-macos-x86_64" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5de656c7ee83f08e0ae5b81792ccfdc1d04e7876b1d9a38e6876a9e09e02537" - -[[package]] -name = "protoc-bin-vendored-win32" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9653c3ed92974e34c5a6e0a510864dab979760481714c172e0a34e437cb98804" - -[[package]] -name = "protoc-rust" -version = "2.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f8a182bb17c485f20bdc4274a8c39000a61024cfe461c799b50fec77267838" -dependencies = [ - "protobuf", - "protobuf-codegen", - "protoc", - "tempfile", -] - -[[package]] -name = "psl-types" -version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" - -[[package]] -name = "ptr_meta" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" -dependencies = [ - "ptr_meta_derive", -] - -[[package]] -name = "ptr_meta_derive" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "publicsuffix" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96a8c1bda5ae1af7f99a2962e49df150414a43d62404644d98dd5c3a93d07457" -dependencies = [ - "idna 0.3.0", - "psl-types", -] - -[[package]] -name = "quanta" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5167a477619228a0b284fac2674e3c388cba90631d7b7de620e6f1fcd08da5" -dependencies = [ - "crossbeam-utils", - "libc", - "once_cell", - "raw-cpuid", - "wasi 0.11.0+wasi-snapshot-preview1", - "web-sys", - "winapi", -] - -[[package]] -name = "quick-xml" -version = "0.28.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1" -dependencies = [ - "memchr", -] - -[[package]] -name = "quote" -version = "1.0.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r2d2" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93" -dependencies = [ - "log", - "parking_lot 0.12.1", - "scheduled-thread-pool", -] - -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", - "rand_pcg", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.10", -] - -[[package]] -name = "rand_distr" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" -dependencies = [ - "num-traits", - "rand 0.8.5", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_pcg" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "raw-cpuid" -version = "11.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb9ee317cfe3fbd54b36a511efc1edd42e216903c9cd575e686dd68a2ba90d8d" -dependencies = [ - "bitflags 2.4.0", -] - -[[package]] -name = "raw-window-handle" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" - -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "redox_syscall" -version = "0.1.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" - -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_users" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" -dependencies = [ - "getrandom 0.2.10", - "redox_syscall 0.2.16", - "thiserror", -] - -[[package]] -name = "regex" -version = "1.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" -dependencies = [ - "aho-corasick 1.0.2", - "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", -] - -[[package]] -name = "regex-automata" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" -dependencies = [ - "aho-corasick 1.0.2", - "memchr", - "regex-syntax 0.8.4", -] - -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - -[[package]] -name = "regex-syntax" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" - -[[package]] -name = "rend" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581008d2099240d37fb08d77ad713bcaec2c4d89d50b5b21a8bb1996bbab68ab" -dependencies = [ - "bytecheck", -] - -[[package]] -name = "reqwest" -version = "0.11.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" -dependencies = [ - "base64 0.21.5", - "bytes", - "cookie", - "cookie_store", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-rustls", - "hyper-tls", - "ipnet", - "js-sys", - "log", - "mime", - "mime_guess", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls", - "rustls-pemfile", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "system-configuration", - "tokio", - "tokio-native-tls", - "tokio-rustls", - "tokio-util", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", - "webpki-roots", - "winreg 0.50.0", -] - -[[package]] -name = "rfd" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0149778bd99b6959285b0933288206090c50e2327f47a9c463bfdbf45c8823ea" -dependencies = [ - "block", - "dispatch", - "glib-sys", - "gobject-sys", - "gtk-sys", - "js-sys", - "lazy_static", - "log", - "objc", - "objc-foundation", - "objc_id", - "raw-window-handle", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "windows 0.37.0", -] - -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin", - "untrusted", - "web-sys", - "winapi", -] - -[[package]] -name = "rkyv" -version = "0.7.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0200c8230b013893c0b2d6213d6ec64ed2b9be2e0e016682b7224ff82cff5c58" -dependencies = [ - "bitvec", - "bytecheck", - "hashbrown 0.12.3", - "ptr_meta", - "rend", - "rkyv_derive", - "seahash", - "tinyvec", - "uuid", -] - -[[package]] -name = "rkyv_derive" -version = "0.7.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2e06b915b5c230a17d7a736d1e2e63ee753c256a8614ef3f5147b13a4f5541d" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "rocksdb" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bd13e55d6d7b8cd0ea569161127567cd587676c99f4472f779a0279aa60a7a7" -dependencies = [ - "libc", - "librocksdb-sys", -] - -[[package]] -name = "rust-stemmers" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e46a2036019fdb888131db7a4c847a1063a7493f971ed94ea82c67eada63ca54" -dependencies = [ - "serde", - "serde_derive", -] - -[[package]] -name = "rust_decimal" -version = "1.36.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" -dependencies = [ - "arrayvec", - "borsh", - "bytes", - "num-traits", - "rand 0.8.5", - "rkyv", - "serde", - "serde_json", -] - -[[package]] -name = "rust_decimal_macros" -version = "1.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ca5c398d85f83b9a44de754a2048625a8c5eafcf070da7b8f116b685e2f6608" -dependencies = [ - "quote", - "rust_decimal", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - -[[package]] -name = "rustix" -version = "0.38.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" -dependencies = [ - "bitflags 2.4.0", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustls" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" -dependencies = [ - "log", - "ring", - "rustls-webpki", - "sct", -] - -[[package]] -name = "rustls-pemfile" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" -dependencies = [ - "base64 0.21.5", -] - -[[package]] -name = "rustls-webpki" -version = "0.101.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" - -[[package]] -name = "rusty-money" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b28f881005eac7ad8d46b6f075da5f322bd7f4f83a38720fc069694ddadd683" -dependencies = [ - "rust_decimal", - "rust_decimal_macros", -] - -[[package]] -name = "ryu" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" - -[[package]] -name = "safemem" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "sanitize-filename" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ed72fbaf78e6f2d41744923916966c4fbe3d7c74e3037a8ee482f1115572603" -dependencies = [ - "lazy_static", - "regex", -] - -[[package]] -name = "schannel" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" -dependencies = [ - "windows-sys 0.42.0", -] - -[[package]] -name = "scheduled-thread-pool" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" -dependencies = [ - "parking_lot 0.12.1", -] - -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "scraper" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c95a930e03325234c18c7071fd2b60118307e025d6fff3e12745ffbf63a3d29c" -dependencies = [ - "ahash 0.8.6", - "cssparser 0.31.2", - "ego-tree", - "getopts", - "html5ever 0.26.0", - "once_cell", - "selectors 0.25.0", - "smallvec", - "tendril", -] - -[[package]] -name = "scraper" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585480e3719b311b78a573db1c9d9c4c1f8010c2dee4cc59c2efe58ea4dbc3e1" -dependencies = [ - "ahash 0.8.6", - "cssparser 0.31.2", - "ego-tree", - "getopts", - "html5ever 0.26.0", - "once_cell", - "selectors 0.25.0", - "tendril", -] - -[[package]] -name = "sct" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" -dependencies = [ - "ring", - "untrusted", -] - -[[package]] -name = "seahash" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" - -[[package]] -name = "security-framework" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "selectors" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" -dependencies = [ - "bitflags 1.3.2", - "cssparser 0.27.2", - "derive_more", - "fxhash", - "log", - "matches", - "phf 0.8.0", - "phf_codegen 0.8.0", - "precomputed-hash", - "servo_arc 0.1.1", - "smallvec", - "thin-slice", -] - -[[package]] -name = "selectors" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb30575f3638fc8f6815f448d50cb1a2e255b0897985c8c59f4d37b72a07b06" -dependencies = [ - "bitflags 2.4.0", - "cssparser 0.31.2", - "derive_more", - "fxhash", - "log", - "new_debug_unreachable", - "phf 0.10.1", - "phf_codegen 0.10.0", - "precomputed-hash", - "servo_arc 0.3.0", - "smallvec", -] - -[[package]] -name = "semver" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" -dependencies = [ - "serde", -] - -[[package]] -name = "serde" -version = "1.0.210" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.210" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "serde_derive_internals" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e578a843d40b4189a4d66bba51d7684f57da5bd7c304c64e14bd63efbef49509" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "serde_json" -version = "1.0.128" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" -dependencies = [ - "itoa 1.0.6", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "serde_repr" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "serde_spanned" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa 1.0.6", - "ryu", - "serde", -] - -[[package]] -name = "serde_with" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f02d8aa6e3c385bf084924f660ce2a3a6bd333ba55b35e8590b321f35d88513" -dependencies = [ - "base64 0.21.5", - "chrono", - "hex", - "indexmap 1.9.3", - "serde", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc7d5d3932fb12ce722ee5e64dd38c504efba37567f0c402f6ca728c3b8b070" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "serialize-to-javascript" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9823f2d3b6a81d98228151fdeaf848206a7855a7a042bbf9bf870449a66cafb" -dependencies = [ - "serde", - "serde_json", - "serialize-to-javascript-impl", -] - -[[package]] -name = "serialize-to-javascript-impl" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "servo_arc" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" -dependencies = [ - "nodrop", - "stable_deref_trait", -] - -[[package]] -name = "servo_arc" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d036d71a959e00c77a63538b90a6c2390969f9772b096ea837205c6bd0491a44" -dependencies = [ - "stable_deref_trait", -] - -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha1_smol" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" - -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sharded-slab" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shared-entity" -version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ea131f0baab67defe7591067357eced490072372#ea131f0baab67defe7591067357eced490072372" -dependencies = [ - "anyhow", - "app-error", - "appflowy-ai-client", - "bytes", - "chrono", - "collab-entity", - "database-entity", - "futures", - "gotrue-entity", - "infra", - "log", - "pin-project", - "reqwest", - "serde", - "serde_json", - "serde_repr", - "thiserror", - "tracing", - "uuid", - "validator 0.19.0", -] - -[[package]] -name = "shlex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" - -[[package]] -name = "signal-hook-registry" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" -dependencies = [ - "libc", -] - -[[package]] -name = "simd-adler32" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" - -[[package]] -name = "simdutf8" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" - -[[package]] -name = "similar" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad1d488a557b235fc46dae55512ffbfc429d2482b08b4d9435ab07384ca8aec" - -[[package]] -name = "similar" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf" - -[[package]] -name = "simple_asn1" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" -dependencies = [ - "num-bigint", - "num-traits", - "thiserror", - "time", -] - -[[package]] -name = "siphasher" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" - -[[package]] -name = "sketches-ddsketch" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85636c14b73d81f541e525f585c0a2109e6744e1565b5c1668e31c70c10ed65c" -dependencies = [ - "serde", -] - -[[package]] -name = "slab" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" -dependencies = [ - "autocfg", -] - -[[package]] -name = "slug" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373" -dependencies = [ - "deunicode", -] - -[[package]] -name = "smallstr" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63b1aefdf380735ff8ded0b15f31aab05daf1f70216c01c02a12926badd1df9d" -dependencies = [ - "smallvec", -] - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "socket2" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "socket2" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" -dependencies = [ - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "soup2" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b4d76501d8ba387cf0fefbe055c3e0a59891d09f0f995ae4e4b16f6b60f3c0" -dependencies = [ - "bitflags 1.3.2", - "gio", - "glib", - "libc", - "once_cell", - "soup2-sys", -] - -[[package]] -name = "soup2-sys" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "009ef427103fcb17f802871647a7fa6c60cbb654b4c4e4c0ac60a31c5f6dc9cf" -dependencies = [ - "bitflags 1.3.2", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps 5.0.0", -] - -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "state" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b" -dependencies = [ - "loom", -] - -[[package]] -name = "string_cache" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" -dependencies = [ - "new_debug_unreachable", - "once_cell", - "parking_lot 0.12.1", - "phf_shared 0.10.0", - "precomputed-hash", - "serde", -] - -[[package]] -name = "string_cache_codegen" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" -dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", - "proc-macro2", - "quote", -] - -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "strsim" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" - -[[package]] -name = "strum" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" - -[[package]] -name = "strum_macros" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec" -dependencies = [ - "heck 0.3.3", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "strum_macros" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad8d03b598d3d0fff69bf533ee3ef19b8eeb342729596df84bcc7e1f96ec4059" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.47", -] - -[[package]] -name = "strum_macros" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.47", -] - -[[package]] -name = "subtle" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1726efe18f42ae774cc644f330953a5e7b3c3003d3edcecf18850fe9d4dd9afb" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn_derive" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - -[[package]] -name = "synstructure" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "sysinfo" -version = "0.30.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb4f3438c8f6389c864e61221cbc97e9bca98b4daf39a5beb7bea660f528bb2" -dependencies = [ - "cfg-if", - "core-foundation-sys", - "libc", - "ntapi", - "once_cell", - "rayon", - "windows 0.52.0", -] - -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "system-deps" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18db855554db7bd0e73e06cf7ba3df39f97812cb11d3f75e71c39bf45171797e" -dependencies = [ - "cfg-expr 0.9.1", - "heck 0.3.3", - "pkg-config", - "toml 0.5.11", - "version-compare 0.0.11", -] - -[[package]] -name = "system-deps" -version = "6.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30c2de8a4d8f4b823d634affc9cd2a74ec98c53a756f317e529a48046cbf71f3" -dependencies = [ - "cfg-expr 0.15.3", - "heck 0.4.1", - "pkg-config", - "toml 0.7.5", - "version-compare 0.1.1", -] - -[[package]] -name = "tagptr" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" - -[[package]] -name = "tantivy" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8d0582f186c0a6d55655d24543f15e43607299425c5ad8352c242b914b31856" -dependencies = [ - "aho-corasick 1.0.2", - "arc-swap", - "base64 0.22.1", - "bitpacking", - "byteorder", - "census", - "crc32fast", - "crossbeam-channel", - "downcast-rs", - "fastdivide", - "fnv", - "fs4", - "htmlescape", - "itertools 0.12.1", - "levenshtein_automata", - "log", - "lru", - "lz4_flex", - "measure_time", - "memmap2", - "num_cpus", - "once_cell", - "oneshot", - "rayon", - "regex", - "rust-stemmers", - "rustc-hash", - "serde", - "serde_json", - "sketches-ddsketch", - "smallvec", - "tantivy-bitpacker", - "tantivy-columnar", - "tantivy-common", - "tantivy-fst", - "tantivy-query-grammar", - "tantivy-stacker", - "tantivy-tokenizer-api", - "tempfile", - "thiserror", - "time", - "uuid", - "winapi", -] - -[[package]] -name = "tantivy-bitpacker" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284899c2325d6832203ac6ff5891b297fc5239c3dc754c5bc1977855b23c10df" -dependencies = [ - "bitpacking", -] - -[[package]] -name = "tantivy-columnar" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12722224ffbe346c7fec3275c699e508fd0d4710e629e933d5736ec524a1f44e" -dependencies = [ - "downcast-rs", - "fastdivide", - "itertools 0.12.1", - "serde", - "tantivy-bitpacker", - "tantivy-common", - "tantivy-sstable", - "tantivy-stacker", -] - -[[package]] -name = "tantivy-common" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8019e3cabcfd20a1380b491e13ff42f57bb38bf97c3d5fa5c07e50816e0621f4" -dependencies = [ - "async-trait", - "byteorder", - "ownedbytes", - "serde", - "time", -] - -[[package]] -name = "tantivy-fst" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d60769b80ad7953d8a7b2c70cdfe722bbcdcac6bccc8ac934c40c034d866fc18" -dependencies = [ - "byteorder", - "regex-syntax 0.8.4", - "utf8-ranges", -] - -[[package]] -name = "tantivy-query-grammar" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "847434d4af57b32e309f4ab1b4f1707a6c566656264caa427ff4285c4d9d0b82" -dependencies = [ - "nom", -] - -[[package]] -name = "tantivy-sstable" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c69578242e8e9fc989119f522ba5b49a38ac20f576fc778035b96cc94f41f98e" -dependencies = [ - "tantivy-bitpacker", - "tantivy-common", - "tantivy-fst", - "zstd 0.13.2", -] - -[[package]] -name = "tantivy-stacker" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56d6ff5591fc332739b3ce7035b57995a3ce29a93ffd6012660e0949c956ea8" -dependencies = [ - "murmurhash32", - "rand_distr", - "tantivy-common", -] - -[[package]] -name = "tantivy-tokenizer-api" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0dcade25819a89cfe6f17d932c9cedff11989936bf6dd4f336d50392053b04" -dependencies = [ - "serde", -] - -[[package]] -name = "tao" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6d198e01085564cea63e976ad1566c1ba2c2e4cc79578e35d9f05521505e31" -dependencies = [ - "bitflags 1.3.2", - "cairo-rs", - "cc", - "cocoa", - "core-foundation", - "core-graphics", - "crossbeam-channel", - "dispatch", - "gdk", - "gdk-pixbuf", - "gdk-sys", - "gdkwayland-sys", - "gdkx11-sys", - "gio", - "glib", - "glib-sys", - "gtk", - "image", - "instant", - "jni", - "lazy_static", - "libc", - "log", - "ndk", - "ndk-context", - "ndk-sys", - "objc", - "once_cell", - "parking_lot 0.12.1", - "png", - "raw-window-handle", - "scopeguard", - "serde", - "tao-macros", - "unicode-segmentation", - "uuid", - "windows 0.39.0", - "windows-implement", - "x11-dl", -] - -[[package]] -name = "tao-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b27a4bcc5eb524658234589bdffc7e7bfb996dbae6ce9393bfd39cb4159b445" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "tar" -version = "0.4.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" -dependencies = [ - "filetime", - "libc", - "xattr 0.2.3", -] - -[[package]] -name = "target-lexicon" -version = "0.12.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1c7f239eb94671427157bd93b3694320f3668d4e1eff08c7285366fd777fac" - -[[package]] -name = "tauri" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bfe673cf125ef364d6f56b15e8ce7537d9ca7e4dae1cf6fbbdeed2e024db3d9" -dependencies = [ - "anyhow", - "cocoa", - "dirs-next", - "embed_plist", - "encoding_rs", - "flate2", - "futures-util", - "glib", - "glob", - "gtk", - "heck 0.4.1", - "http", - "ignore", - "objc", - "once_cell", - "open", - "percent-encoding", - "rand 0.8.5", - "raw-window-handle", - "regex", - "rfd", - "semver", - "serde", - "serde_json", - "serde_repr", - "serialize-to-javascript", - "state", - "tar", - "tauri-macros", - "tauri-runtime", - "tauri-runtime-wry", - "tauri-utils", - "tempfile", - "thiserror", - "tokio", - "url", - "uuid", - "webkit2gtk", - "webview2-com", - "windows 0.39.0", -] - -[[package]] -name = "tauri-build" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defbfc551bd38ab997e5f8e458f87396d2559d05ce32095076ad6c30f7fc5f9c" -dependencies = [ - "anyhow", - "cargo_toml", - "dirs-next", - "heck 0.4.1", - "json-patch", - "semver", - "serde", - "serde_json", - "tauri-utils", - "tauri-winres", - "walkdir", -] - -[[package]] -name = "tauri-codegen" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b3475e55acec0b4a50fb96435f19631fb58cbcd31923e1a213de5c382536bbb" -dependencies = [ - "base64 0.21.5", - "brotli", - "ico", - "json-patch", - "plist", - "png", - "proc-macro2", - "quote", - "regex", - "semver", - "serde", - "serde_json", - "sha2", - "tauri-utils", - "thiserror", - "time", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-macros" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613740228de92d9196b795ac455091d3a5fbdac2654abb8bb07d010b62ab43af" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "syn 1.0.109", - "tauri-codegen", - "tauri-utils", -] - -[[package]] -name = "tauri-plugin-deep-link" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4536f5f6602e8fdfaa7b3b185076c2a0704f8eb7015f4e58461eb483ec3ed1f8" -dependencies = [ - "dirs", - "interprocess", - "log", - "objc2", - "once_cell", - "tauri-utils", - "windows-sys 0.48.0", - "winreg 0.50.0", -] - -[[package]] -name = "tauri-runtime" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07f8e9e53e00e9f41212c115749e87d5cd2a9eebccafca77a19722eeecd56d43" -dependencies = [ - "gtk", - "http", - "http-range", - "rand 0.8.5", - "raw-window-handle", - "serde", - "serde_json", - "tauri-utils", - "thiserror", - "url", - "uuid", - "webview2-com", - "windows 0.39.0", -] - -[[package]] -name = "tauri-runtime-wry" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8141d72b6b65f2008911e9ef5b98a68d1e3413b7a1464e8f85eb3673bb19a895" -dependencies = [ - "cocoa", - "gtk", - "percent-encoding", - "rand 0.8.5", - "raw-window-handle", - "tauri-runtime", - "tauri-utils", - "uuid", - "webkit2gtk", - "webview2-com", - "windows 0.39.0", - "wry", -] - -[[package]] -name = "tauri-utils" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ece74810b1d3d44f29f732a7ae09a63183d63949bbdd59c61f8ed2a1b70150db" -dependencies = [ - "brotli", - "ctor", - "dunce", - "glob", - "heck 0.4.1", - "html5ever 0.26.0", - "infer", - "json-patch", - "kuchikiki", - "log", - "memchr", - "phf 0.11.2", - "proc-macro2", - "quote", - "semver", - "serde", - "serde_json", - "serde_with", - "thiserror", - "url", - "walkdir", - "windows-version", -] - -[[package]] -name = "tauri-winres" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5993dc129e544393574288923d1ec447c857f3f644187f4fbf7d9a875fbfc4fb" -dependencies = [ - "embed-resource", - "toml 0.7.5", -] - -[[package]] -name = "tempfile" -version = "3.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" -dependencies = [ - "cfg-if", - "fastrand", - "redox_syscall 0.4.1", - "rustix", - "windows-sys 0.52.0", -] - -[[package]] -name = "tendril" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" -dependencies = [ - "futf", - "mac", - "utf-8", -] - -[[package]] -name = "tera" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5ab29bb4f3e256ae6ad5c3e2775aa1f8829f2c0c101fc407bfd3a6df15c60c5" -dependencies = [ - "chrono", - "chrono-tz 0.6.1", - "globwalk", - "humansize", - "lazy_static", - "percent-encoding", - "pest", - "pest_derive", - "rand 0.8.5", - "regex", - "serde", - "serde_json", - "slug", - "thread_local", - "unic-segment", -] - -[[package]] -name = "terminal_size" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "thin-slice" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" - -[[package]] -name = "thiserror" -version = "1.0.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "thread-id" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fbf4c9d56b320106cd64fd024dadfa0be7cb4706725fc44a7d7ce952d820c1" -dependencies = [ - "libc", - "redox_syscall 0.1.57", - "winapi", -] - -[[package]] -name = "thread_local" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" -dependencies = [ - "once_cell", -] - -[[package]] -name = "time" -version = "0.3.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" -dependencies = [ - "deranged", - "itoa 1.0.6", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" - -[[package]] -name = "time-macros" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tinystr" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "to_method" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c4ceeeca15c8384bbc3e011dbd8fccb7f068a440b752b7d9b32ceb0ca0e2e8" - -[[package]] -name = "tokio" -version = "1.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "num_cpus", - "parking_lot 0.12.1", - "pin-project-lite", - "signal-hook-registry", - "socket2 0.5.5", - "tokio-macros", - "tracing", - "windows-sys 0.48.0", -] - -[[package]] -name = "tokio-macros" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-retry" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f57eb36ecbe0fc510036adff84824dd3c24bb781e21bfa67b69d556aa85214f" -dependencies = [ - "pin-project", - "rand 0.8.5", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-stream" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", - "tokio-util", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" -dependencies = [ - "futures-util", - "log", - "native-tls", - "tokio", - "tokio-native-tls", - "tungstenite", -] - -[[package]] -name = "tokio-util" -version = "0.7.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" -dependencies = [ - "bytes", - "futures-core", - "futures-io", - "futures-sink", - "futures-util", - "hashbrown 0.14.3", - "pin-project-lite", - "slab", - "tokio", -] - -[[package]] -name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde", -] - -[[package]] -name = "toml" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebafdf5ad1220cb59e7d17cf4d2c72015297b75b19a10472f99b89225089240" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.19.11", -] - -[[package]] -name = "toml_datetime" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.19.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266f016b7f039eec8a1a80dfe6156b633d208b9fccca5e4db1d6775b0c4e34a7" -dependencies = [ - "indexmap 2.1.0", - "serde", - "serde_spanned", - "toml_datetime", - "winnow 0.4.7", -] - -[[package]] -name = "toml_edit" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" -dependencies = [ - "indexmap 2.1.0", - "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "tower-service" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" - -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-appender" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" -dependencies = [ - "crossbeam-channel", - "thiserror", - "time", - "tracing-subscriber", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "tracing-bunyan-formatter" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d637245a0d8774bd48df6482e086c59a8b5348a910c3b0579354045a9d82411" -dependencies = [ - "ahash 0.8.6", - "gethostname", - "log", - "serde", - "serde_json", - "time", - "tracing", - "tracing-core", - "tracing-log 0.1.3", - "tracing-subscriber", -] - -[[package]] -name = "tracing-core" -version = "0.1.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" -dependencies = [ - "lazy_static", - "log", - "tracing-core", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-serde" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" -dependencies = [ - "serde", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex", - "serde", - "serde_json", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log 0.2.0", - "tracing-serde", -] - -[[package]] -name = "tracing-wasm" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4575c663a174420fa2d78f4108ff68f65bf2fbb7dd89f33749b6e826b3626e07" -dependencies = [ - "tracing", - "tracing-subscriber", - "wasm-bindgen", -] - -[[package]] -name = "treediff" -version = "4.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52984d277bdf2a751072b5df30ec0377febdb02f7696d64c2d7d54630bac4303" -dependencies = [ - "serde_json", -] - -[[package]] -name = "triomphe" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3" - -[[package]] -name = "try-lock" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" - -[[package]] -name = "tsify" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b26cf145f2f3b9ff84e182c448eaf05468e247f148cf3d2a7d67d78ff023a0" -dependencies = [ - "gloo-utils", - "serde", - "serde_json", - "tsify-macros", - "wasm-bindgen", -] - -[[package]] -name = "tsify-macros" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a94b0f0954b3e59bfc2c246b4c8574390d94a4ad4ad246aaf2fb07d7dfd3b47" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 2.0.47", -] - -[[package]] -name = "tungstenite" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" -dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http", - "httparse", - "log", - "native-tls", - "rand 0.8.5", - "sha1", - "thiserror", - "url", - "utf-8", -] - -[[package]] -name = "typenum" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" - -[[package]] -name = "ucd-trie" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" - -[[package]] -name = "uncased" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b9bc53168a4be7402ab86c3aad243a84dd7381d09be0eddc81280c1da95ca68" -dependencies = [ - "version_check", -] - -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-segment" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" -dependencies = [ - "unic-ucd-segment", -] - -[[package]] -name = "unic-ucd-segment" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] - -[[package]] -name = "unicase" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" - -[[package]] -name = "unicode-id" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1b6def86329695390197b82c1e244a54a131ceb66c996f2088a3876e2ae083f" - -[[package]] -name = "unicode-ident" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" - -[[package]] -name = "unicode-normalization" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-segmentation" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" - -[[package]] -name = "unicode-width" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" - -[[package]] -name = "universal-hash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common", - "subtle", -] - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "url" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" -dependencies = [ - "form_urlencoded", - "idna 0.5.0", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - -[[package]] -name = "utf8-ranges" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcfc827f90e53a02eaef5e535ee14266c1d569214c6aa70133a624d8a3164ba" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "uuid" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" -dependencies = [ - "getrandom 0.2.10", - "serde", - "sha1_smol", - "wasm-bindgen", -] - -[[package]] -name = "validator" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db79c75af171630a3148bd3e6d7c4f42b6a9a014c2945bc5ed0020cbb8d9478e" -dependencies = [ - "idna 0.5.0", - "once_cell", - "regex", - "serde", - "serde_derive", - "serde_json", - "url", - "validator_derive 0.18.2", -] - -[[package]] -name = "validator" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0b4a29d8709210980a09379f27ee31549b73292c87ab9899beee1c0d3be6303" -dependencies = [ - "idna 1.0.3", - "once_cell", - "regex", - "serde", - "serde_derive", - "serde_json", - "url", - "validator_derive 0.19.0", -] - -[[package]] -name = "validator_derive" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df0bcf92720c40105ac4b2dda2a4ea3aa717d4d6a862cc217da653a4bd5c6b10" -dependencies = [ - "darling", - "once_cell", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "validator_derive" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bac855a2ce6f843beb229757e6e570a42e837bcb15e5f449dd48d5747d41bf77" -dependencies = [ - "darling", - "once_cell", - "proc-macro-error2", - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version-compare" -version = "0.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c18c859eead79d8b95d09e4678566e8d70105c4e7b251f707a03df32442661b" - -[[package]] -name = "version-compare" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "vswhom" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" -dependencies = [ - "libc", - "vswhom-sys", -] - -[[package]] -name = "vswhom-sys" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3b17ae1f6c8a2b28506cd96d412eebf83b4a0ff2cbefeeb952f2f9dfa44ba18" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.90" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.90" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.47", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.90" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.90" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.47", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.90" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" - -[[package]] -name = "wasm-streams" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "wasm-timer" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" -dependencies = [ - "futures", - "js-sys", - "parking_lot 0.11.2", - "pin-utils", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "web-sys" -version = "0.3.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webkit2gtk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8f859735e4a452aeb28c6c56a852967a8a76c8eb1cc32dbf931ad28a13d6370" -dependencies = [ - "bitflags 1.3.2", - "cairo-rs", - "gdk", - "gdk-sys", - "gio", - "gio-sys", - "glib", - "glib-sys", - "gobject-sys", - "gtk", - "gtk-sys", - "javascriptcore-rs", - "libc", - "once_cell", - "soup2", - "webkit2gtk-sys", -] - -[[package]] -name = "webkit2gtk-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d76ca6ecc47aeba01ec61e480139dda143796abcae6f83bcddf50d6b5b1dcf3" -dependencies = [ - "atk-sys", - "bitflags 1.3.2", - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "gtk-sys", - "javascriptcore-rs-sys", - "libc", - "pango-sys", - "pkg-config", - "soup2-sys", - "system-deps 6.1.1", -] - -[[package]] -name = "webpki-roots" -version = "0.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" - -[[package]] -name = "webview2-com" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4a769c9f1a64a8734bde70caafac2b96cada12cd4aefa49196b3a386b8b4178" -dependencies = [ - "webview2-com-macros", - "webview2-com-sys", - "windows 0.39.0", - "windows-implement", -] - -[[package]] -name = "webview2-com-macros" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaebe196c01691db62e9e4ca52c5ef1e4fd837dcae27dae3ada599b5a8fd05ac" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "webview2-com-sys" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac48ef20ddf657755fdcda8dfed2a7b4fc7e4581acce6fe9b88c3d64f29dee7" -dependencies = [ - "regex", - "serde", - "serde_json", - "thiserror", - "windows 0.39.0", - "windows-bindgen", - "windows-metadata", -] - -[[package]] -name = "which" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" -dependencies = [ - "either", - "libc", - "once_cell", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57b543186b344cc61c85b5aab0d2e3adf4e0f99bc076eff9aa5927bcc0b8a647" -dependencies = [ - "windows_aarch64_msvc 0.37.0", - "windows_i686_gnu 0.37.0", - "windows_i686_msvc 0.37.0", - "windows_x86_64_gnu 0.37.0", - "windows_x86_64_msvc 0.37.0", -] - -[[package]] -name = "windows" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1c4bd0a50ac6020f65184721f758dba47bb9fbc2133df715ec74a237b26794a" -dependencies = [ - "windows-implement", - "windows_aarch64_msvc 0.39.0", - "windows_i686_gnu 0.39.0", - "windows_i686_msvc 0.39.0", - "windows_x86_64_gnu 0.39.0", - "windows_x86_64_msvc 0.39.0", -] - -[[package]] -name = "windows" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" -dependencies = [ - "windows-targets 0.48.0", -] - -[[package]] -name = "windows" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" -dependencies = [ - "windows-core", - "windows-targets 0.52.0", -] - -[[package]] -name = "windows-bindgen" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68003dbd0e38abc0fb85b939240f4bce37c43a5981d3df37ccbaaa981b47cb41" -dependencies = [ - "windows-metadata", - "windows-tokens", -] - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.0", -] - -[[package]] -name = "windows-implement" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba01f98f509cb5dc05f4e5fc95e535f78260f15fea8fe1a8abdd08f774f1cee7" -dependencies = [ - "syn 1.0.109", - "windows-tokens", -] - -[[package]] -name = "windows-metadata" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ee5e275231f07c6e240d14f34e1b635bf1faa1c76c57cfd59a5cdb9848e4278" - -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.0", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.0", -] - -[[package]] -name = "windows-targets" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" -dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", -] - -[[package]] -name = "windows-targets" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" -dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", -] - -[[package]] -name = "windows-tokens" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f838de2fe15fe6bac988e74b798f26499a8b21a9d97edec321e79b28d1d7f597" - -[[package]] -name = "windows-version" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75aa004c988e080ad34aff5739c39d0312f4684699d6d71fc8a198d057b8b9b4" -dependencies = [ - "windows-targets 0.52.0", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" - -[[package]] -name = "windows_i686_gnu" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1" - -[[package]] -name = "windows_i686_gnu" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" - -[[package]] -name = "windows_i686_msvc" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c" - -[[package]] -name = "windows_i686_msvc" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" - -[[package]] -name = "winnow" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca0ace3845f0d96209f0375e6d367e3eb87eb65d27d445bdc9f1843a26f39448" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - -[[package]] -name = "winreg" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a1a57ff50e9b408431e8f97d5456f2807f8eb2a2cd79b06068fc87f8ecf189" -dependencies = [ - "cfg-if", - "winapi", -] - -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - -[[package]] -name = "writeable" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" - -[[package]] -name = "wry" -version = "0.24.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a70547e8f9d85da0f5af609143f7bde3ac7457a6e1073104d9b73d6c5ac744" -dependencies = [ - "base64 0.13.1", - "block", - "cocoa", - "core-graphics", - "crossbeam-channel", - "dunce", - "gdk", - "gio", - "glib", - "gtk", - "html5ever 0.25.2", - "http", - "kuchiki", - "libc", - "log", - "objc", - "objc_id", - "once_cell", - "serde", - "serde_json", - "sha2", - "soup2", - "tao", - "thiserror", - "url", - "webkit2gtk", - "webkit2gtk-sys", - "webview2-com", - "windows 0.39.0", - "windows-implement", -] - -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - -[[package]] -name = "x11" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" -dependencies = [ - "libc", - "pkg-config", -] - -[[package]] -name = "x11-dl" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" -dependencies = [ - "libc", - "once_cell", - "pkg-config", -] - -[[package]] -name = "xattr" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" -dependencies = [ - "libc", -] - -[[package]] -name = "xattr" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" -dependencies = [ - "libc", - "linux-raw-sys", - "rustix", -] - -[[package]] -name = "xz2" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" -dependencies = [ - "lzma-sys", -] - -[[package]] -name = "yoke" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.47", - "synstructure", -] - -[[package]] -name = "yrs" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81de5913bca29f43a1d12ca92a7b39a2945e9420e01602a7563917c7bfc60f70" -dependencies = [ - "arc-swap", - "async-lock", - "async-trait", - "dashmap 6.0.1", - "fastrand", - "serde", - "serde_json", - "smallstr", - "smallvec", - "thiserror", -] - -[[package]] -name = "zerocopy" -version = "0.7.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e97e415490559a91254a2979b4829267a57d2fcd741a98eee8b722fb57289aa0" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd7e48ccf166952882ca8bd778a43502c64f33bf94c12ebe2a7f08e5a0f6689f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "zerofrom" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.47", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "zerovec" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.47", -] - -[[package]] -name = "zip" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" -dependencies = [ - "aes", - "byteorder", - "bzip2", - "constant_time_eq 0.1.5", - "crc32fast", - "crossbeam-utils", - "flate2", - "hmac", - "pbkdf2 0.11.0", - "sha1", - "time", - "zstd 0.11.2+zstd.1.5.2", -] - -[[package]] -name = "zip" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc5e4288ea4057ae23afc69a4472434a87a2495cafce6632fd1c4ec9f5cf3494" -dependencies = [ - "aes", - "arbitrary", - "bzip2", - "constant_time_eq 0.3.0", - "crc32fast", - "crossbeam-utils", - "deflate64", - "displaydoc", - "flate2", - "hmac", - "indexmap 2.1.0", - "lzma-rs", - "memchr", - "pbkdf2 0.12.2", - "rand 0.8.5", - "sha1", - "thiserror", - "time", - "zeroize", - "zopfli", - "zstd 0.13.2", -] - -[[package]] -name = "zip-extensions" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb0a99499b3497d765525c5d05e3ade9ca4a731c184365c19472c3fd6ba86341" -dependencies = [ - "zip 2.2.0", -] - -[[package]] -name = "zopfli" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" -dependencies = [ - "bumpalo", - "crc32fast", - "lockfree-object-pool", - "log", - "once_cell", - "simd-adler32", -] - -[[package]] -name = "zstd" -version = "0.11.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" -dependencies = [ - "zstd-safe 5.0.2+zstd.1.5.2", -] - -[[package]] -name = "zstd" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" -dependencies = [ - "zstd-safe 7.2.0", -] - -[[package]] -name = "zstd-safe" -version = "5.0.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" -dependencies = [ - "libc", - "zstd-sys", -] - -[[package]] -name = "zstd-safe" -version = "7.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa556e971e7b568dc775c136fc9de8c779b1c2fc3a63defaafadffdbd3181afa" -dependencies = [ - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.12+zstd.1.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a4e40c320c3cb459d9a9ff6de98cff88f4751ee9275d140e2be94a2b74e4c13" -dependencies = [ - "cc", - "pkg-config", -] diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.toml b/frontend/appflowy_tauri/src-tauri/Cargo.toml deleted file mode 100644 index 196a93699a..0000000000 --- a/frontend/appflowy_tauri/src-tauri/Cargo.toml +++ /dev/null @@ -1,137 +0,0 @@ -[package] -name = "appflowy_tauri" -version = "0.0.0" -description = "A Tauri App" -authors = ["you"] -license = "" -repository = "" -edition = "2021" -rust-version = "1.57" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[build-dependencies] -tauri-build = { version = "1.5", features = [] } - -[workspace.dependencies] -anyhow = "1.0" -tracing = "0.1.40" -bytes = "1.5.0" -serde = "1.0" -serde_json = "1.0.108" -protobuf = { version = "2.28.0" } -diesel = { version = "2.1.0", features = [ - "sqlite", - "chrono", - "r2d2", - "serde_json", -] } -uuid = { version = "1.5.0", features = ["serde", "v4"] } -serde_repr = "0.1" -parking_lot = "0.12" -futures = "0.3.29" -tokio = "1.34.0" -tokio-stream = "0.1.14" -async-trait = "0.1.74" -chrono = { version = "0.4.31", default-features = false, features = ["clock"] } -zip = "2.2.0" -yrs = "0.19.1" -# Please use the following script to update collab. -# Working directory: frontend -# -# To update the commit ID, run: -# scripts/tool/update_collab_rev.sh new_rev_id -# -# To switch to the local path, run: -# scripts/tool/update_collab_source.sh -# ⚠️⚠️⚠️️ -collab = { version = "0.2" } -collab-entity = { version = "0.2" } -collab-folder = { version = "0.2" } -collab-document = { version = "0.2" } -collab-database = { version = "0.2" } -collab-plugins = { version = "0.2" } -collab-user = { version = "0.2" } -collab-importer = { version = "0.1" } - -# Please using the following command to update the revision id -# Current directory: frontend -# Run the script: -# scripts/tool/update_client_api_rev.sh new_rev_id -# ⚠️⚠️⚠️️ -client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "ea131f0baab67defe7591067357eced490072372" } - -[dependencies] -serde_json.workspace = true -serde.workspace = true -tauri = { version = "1.5", features = [ - "dialog-all", - "clipboard-all", - "fs-all", - "shell-open", -] } -tauri-utils = "1.5.2" -bytes.workspace = true -tracing.workspace = true -lib-dispatch = { path = "../../rust-lib/lib-dispatch", features = [ - "use_serde", -] } -flowy-core = { path = "../../rust-lib/flowy-core", features = ["ts"] } -flowy-user = { path = "../../rust-lib/flowy-user", features = ["tauri_ts"] } -flowy-config = { path = "../../rust-lib/flowy-config", features = ["tauri_ts"] } -flowy-date = { path = "../../rust-lib/flowy-date", features = ["tauri_ts"] } -flowy-ai = { path = "../../rust-lib/flowy-ai", features = ["tauri_ts"] } -flowy-error = { path = "../../rust-lib/flowy-error", features = [ - "impl_from_sqlite", - "impl_from_dispatch_error", - "impl_from_appflowy_cloud", - "impl_from_reqwest", - "impl_from_serde", - "tauri_ts", -] } -flowy-search = { path = "../../rust-lib/flowy-search", features = ["tauri_ts"] } -flowy-document = { path = "../../rust-lib/flowy-document", features = [ - "tauri_ts", -] } -flowy-notification = { path = "../../rust-lib/flowy-notification", features = [ - "tauri_ts", -] } - -uuid = "1.5.0" -tauri-plugin-deep-link = "0.1.2" -dotenv = "0.15.0" -semver = "1.0.23" - -[features] -# by default Tauri runs in production mode -# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL -default = ["custom-protocol"] -# this feature is used used for production builds where `devPath` points to the filesystem -# DO NOT remove this -custom-protocol = ["tauri/custom-protocol"] - -[patch.crates-io] -# Please use the following script to update collab. -# Working directory: frontend -# -# To update the commit ID, run: -# scripts/tool/update_collab_rev.sh new_rev_id -# -# To switch to the local path, run: -# scripts/tool/update_collab_source.sh -# ⚠️⚠️⚠️️ -collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "67b31e4bcc64d81332c37efecb0a8618681f1db9" } -collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "67b31e4bcc64d81332c37efecb0a8618681f1db9" } -collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "67b31e4bcc64d81332c37efecb0a8618681f1db9" } -collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "67b31e4bcc64d81332c37efecb0a8618681f1db9" } -collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "67b31e4bcc64d81332c37efecb0a8618681f1db9" } -collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "67b31e4bcc64d81332c37efecb0a8618681f1db9" } -collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "67b31e4bcc64d81332c37efecb0a8618681f1db9" } -collab-importer = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "67b31e4bcc64d81332c37efecb0a8618681f1db9" } - -# Working directory: frontend -# To update the commit ID, run: -# scripts/tool/update_local_ai_rev.sh new_rev_id -# ⚠️⚠️⚠️️ -appflowy-local-ai = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "6f064efe232268f8d396edbb4b84d57fbb640f13" } -appflowy-plugin = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "6f064efe232268f8d396edbb4b84d57fbb640f13" } diff --git a/frontend/appflowy_tauri/src-tauri/Info.plist b/frontend/appflowy_tauri/src-tauri/Info.plist deleted file mode 100644 index 25b430c049..0000000000 --- a/frontend/appflowy_tauri/src-tauri/Info.plist +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - CFBundleURLTypes - - - CFBundleURLName - - appflowy-flutter - CFBundleURLSchemes - - appflowy-flutter - - - - - \ No newline at end of file diff --git a/frontend/appflowy_tauri/src-tauri/build.rs b/frontend/appflowy_tauri/src-tauri/build.rs deleted file mode 100644 index d860e1e6a7..0000000000 --- a/frontend/appflowy_tauri/src-tauri/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - tauri_build::build() -} diff --git a/frontend/appflowy_tauri/src-tauri/env.development b/frontend/appflowy_tauri/src-tauri/env.development deleted file mode 100644 index 188835e3d0..0000000000 --- a/frontend/appflowy_tauri/src-tauri/env.development +++ /dev/null @@ -1,4 +0,0 @@ -APPFLOWY_CLOUD_ENV_APPFLOWY_CLOUD_BASE_URL=https://test.appflowy.cloud -APPFLOWY_CLOUD_ENV_APPFLOWY_CLOUD_WS_BASE_URL=wss://test.appflowy.cloud/ws/v1 -APPFLOWY_CLOUD_ENV_APPFLOWY_CLOUD_GOTRUE_URL=https://test.appflowy.cloud/gotrue -APPFLOWY_CLOUD_ENV_CLOUD_TYPE=2 diff --git a/frontend/appflowy_tauri/src-tauri/env.production b/frontend/appflowy_tauri/src-tauri/env.production deleted file mode 100644 index b03c328b84..0000000000 --- a/frontend/appflowy_tauri/src-tauri/env.production +++ /dev/null @@ -1,4 +0,0 @@ -APPFLOWY_CLOUD_ENV_APPFLOWY_CLOUD_BASE_URL=https://beta.appflowy.cloud -APPFLOWY_CLOUD_ENV_APPFLOWY_CLOUD_WS_BASE_URL=wss://beta.appflowy.cloud/ws/v1 -APPFLOWY_CLOUD_ENV_APPFLOWY_CLOUD_GOTRUE_URL=https://beta.appflowy.cloud/gotrue -APPFLOWY_CLOUD_ENV_CLOUD_TYPE=2 diff --git a/frontend/appflowy_tauri/src-tauri/icons/128x128.png b/frontend/appflowy_tauri/src-tauri/icons/128x128.png deleted file mode 100644 index 3a51041313..0000000000 Binary files a/frontend/appflowy_tauri/src-tauri/icons/128x128.png and /dev/null differ diff --git a/frontend/appflowy_tauri/src-tauri/icons/128x128@2x.png b/frontend/appflowy_tauri/src-tauri/icons/128x128@2x.png deleted file mode 100644 index 9076de3a4b..0000000000 Binary files a/frontend/appflowy_tauri/src-tauri/icons/128x128@2x.png and /dev/null differ diff --git a/frontend/appflowy_tauri/src-tauri/icons/32x32.png b/frontend/appflowy_tauri/src-tauri/icons/32x32.png deleted file mode 100644 index 6ae6683fef..0000000000 Binary files a/frontend/appflowy_tauri/src-tauri/icons/32x32.png and /dev/null differ diff --git a/frontend/appflowy_tauri/src-tauri/icons/Square107x107Logo.png b/frontend/appflowy_tauri/src-tauri/icons/Square107x107Logo.png deleted file mode 100644 index b08dcf7d21..0000000000 Binary files a/frontend/appflowy_tauri/src-tauri/icons/Square107x107Logo.png and /dev/null differ diff --git a/frontend/appflowy_tauri/src-tauri/icons/Square142x142Logo.png b/frontend/appflowy_tauri/src-tauri/icons/Square142x142Logo.png deleted file mode 100644 index f3e437b76e..0000000000 Binary files a/frontend/appflowy_tauri/src-tauri/icons/Square142x142Logo.png and /dev/null differ diff --git a/frontend/appflowy_tauri/src-tauri/icons/Square150x150Logo.png b/frontend/appflowy_tauri/src-tauri/icons/Square150x150Logo.png deleted file mode 100644 index 6a1dc04864..0000000000 Binary files a/frontend/appflowy_tauri/src-tauri/icons/Square150x150Logo.png and /dev/null differ diff --git a/frontend/appflowy_tauri/src-tauri/icons/Square284x284Logo.png b/frontend/appflowy_tauri/src-tauri/icons/Square284x284Logo.png deleted file mode 100644 index 2f2d9d6fe6..0000000000 Binary files a/frontend/appflowy_tauri/src-tauri/icons/Square284x284Logo.png and /dev/null differ diff --git a/frontend/appflowy_tauri/src-tauri/icons/Square30x30Logo.png b/frontend/appflowy_tauri/src-tauri/icons/Square30x30Logo.png deleted file mode 100644 index 46e3802c0b..0000000000 Binary files a/frontend/appflowy_tauri/src-tauri/icons/Square30x30Logo.png and /dev/null differ diff --git a/frontend/appflowy_tauri/src-tauri/icons/Square310x310Logo.png b/frontend/appflowy_tauri/src-tauri/icons/Square310x310Logo.png deleted file mode 100644 index 230b1abe58..0000000000 Binary files a/frontend/appflowy_tauri/src-tauri/icons/Square310x310Logo.png and /dev/null differ diff --git a/frontend/appflowy_tauri/src-tauri/icons/Square44x44Logo.png b/frontend/appflowy_tauri/src-tauri/icons/Square44x44Logo.png deleted file mode 100644 index ad188037a3..0000000000 Binary files a/frontend/appflowy_tauri/src-tauri/icons/Square44x44Logo.png and /dev/null differ diff --git a/frontend/appflowy_tauri/src-tauri/icons/Square71x71Logo.png b/frontend/appflowy_tauri/src-tauri/icons/Square71x71Logo.png deleted file mode 100644 index ceae9ad1bb..0000000000 Binary files a/frontend/appflowy_tauri/src-tauri/icons/Square71x71Logo.png and /dev/null differ diff --git a/frontend/appflowy_tauri/src-tauri/icons/Square89x89Logo.png b/frontend/appflowy_tauri/src-tauri/icons/Square89x89Logo.png deleted file mode 100644 index 123dcea650..0000000000 Binary files a/frontend/appflowy_tauri/src-tauri/icons/Square89x89Logo.png and /dev/null differ diff --git a/frontend/appflowy_tauri/src-tauri/icons/StoreLogo.png b/frontend/appflowy_tauri/src-tauri/icons/StoreLogo.png deleted file mode 100644 index d7906c3c03..0000000000 Binary files a/frontend/appflowy_tauri/src-tauri/icons/StoreLogo.png and /dev/null differ diff --git a/frontend/appflowy_tauri/src-tauri/icons/icon.icns b/frontend/appflowy_tauri/src-tauri/icons/icon.icns deleted file mode 100644 index 74b585f25d..0000000000 Binary files a/frontend/appflowy_tauri/src-tauri/icons/icon.icns and /dev/null differ diff --git a/frontend/appflowy_tauri/src-tauri/icons/icon.ico b/frontend/appflowy_tauri/src-tauri/icons/icon.ico deleted file mode 100644 index cd9ad402d1..0000000000 Binary files a/frontend/appflowy_tauri/src-tauri/icons/icon.ico and /dev/null differ diff --git a/frontend/appflowy_tauri/src-tauri/icons/icon.png b/frontend/appflowy_tauri/src-tauri/icons/icon.png deleted file mode 100644 index 7cc3853d67..0000000000 Binary files a/frontend/appflowy_tauri/src-tauri/icons/icon.png and /dev/null differ diff --git a/frontend/appflowy_tauri/src-tauri/rust-toolchain.toml b/frontend/appflowy_tauri/src-tauri/rust-toolchain.toml deleted file mode 100644 index 6f14058b2e..0000000000 --- a/frontend/appflowy_tauri/src-tauri/rust-toolchain.toml +++ /dev/null @@ -1,2 +0,0 @@ -[toolchain] -channel = "1.77.2" diff --git a/frontend/appflowy_tauri/src-tauri/rustfmt.toml b/frontend/appflowy_tauri/src-tauri/rustfmt.toml deleted file mode 100644 index 5cb0d67ee5..0000000000 --- a/frontend/appflowy_tauri/src-tauri/rustfmt.toml +++ /dev/null @@ -1,12 +0,0 @@ -# https://rust-lang.github.io/rustfmt/?version=master&search= -max_width = 100 -tab_spaces = 2 -newline_style = "Auto" -match_block_trailing_comma = true -use_field_init_shorthand = true -use_try_shorthand = true -reorder_imports = true -reorder_modules = true -remove_nested_parens = true -merge_derives = true -edition = "2021" \ No newline at end of file diff --git a/frontend/appflowy_tauri/src-tauri/src/init.rs b/frontend/appflowy_tauri/src-tauri/src/init.rs deleted file mode 100644 index 4903e1fe34..0000000000 --- a/frontend/appflowy_tauri/src-tauri/src/init.rs +++ /dev/null @@ -1,78 +0,0 @@ -use dotenv::dotenv; -use flowy_core::config::AppFlowyCoreConfig; -use flowy_core::{AppFlowyCore, DEFAULT_NAME}; -use lib_dispatch::runtime::AFPluginRuntime; -use std::sync::Mutex; - -pub fn read_env() { - dotenv().ok(); - - let env = if cfg!(debug_assertions) { - include_str!("../env.development") - } else { - include_str!("../env.production") - }; - - for line in env.lines() { - if let Some((key, value)) = line.split_once('=') { - // Check if the environment variable is not already set in the system - let current_value = std::env::var(key).unwrap_or_default(); - if current_value.is_empty() { - std::env::set_var(key, value); - } - } - } -} - -pub(crate) fn init_appflowy_core() -> MutexAppFlowyCore { - let config_json = include_str!("../tauri.conf.json"); - let config: tauri_utils::config::Config = serde_json::from_str(config_json).unwrap(); - - let app_version = config - .package - .version - .clone() - .map(|v| v.to_string()) - .unwrap_or_else(|| "0.5.8".to_string()); - let app_version = - semver::Version::parse(&app_version).unwrap_or_else(|_| semver::Version::new(0, 5, 8)); - let mut data_path = tauri::api::path::app_local_data_dir(&config).unwrap(); - if cfg!(debug_assertions) { - data_path.push("data_dev"); - } else { - data_path.push("data"); - } - - let custom_application_path = data_path.to_str().unwrap().to_string(); - let application_path = data_path.to_str().unwrap().to_string(); - let device_id = uuid::Uuid::new_v4().to_string(); - - read_env(); - std::env::set_var("RUST_LOG", "trace"); - - let config = AppFlowyCoreConfig::new( - app_version, - custom_application_path, - application_path, - device_id, - "tauri".to_string(), - DEFAULT_NAME.to_string(), - ) - .log_filter("trace", vec!["appflowy_tauri".to_string()]); - - let runtime = Arc::new(AFPluginRuntime::new().unwrap()); - let cloned_runtime = runtime.clone(); - runtime.block_on(async move { - MutexAppFlowyCore::new(AppFlowyCore::new(config, cloned_runtime, None).await) - }) -} - -pub struct MutexAppFlowyCore(pub Arc>); - -impl MutexAppFlowyCore { - fn new(appflowy_core: AppFlowyCore) -> Self { - Self(Arc::new(Mutex::new(appflowy_core))) - } -} -unsafe impl Sync for MutexAppFlowyCore {} -unsafe impl Send for MutexAppFlowyCore {} diff --git a/frontend/appflowy_tauri/src-tauri/src/main.rs b/frontend/appflowy_tauri/src-tauri/src/main.rs deleted file mode 100644 index 5f12d1be81..0000000000 --- a/frontend/appflowy_tauri/src-tauri/src/main.rs +++ /dev/null @@ -1,72 +0,0 @@ -#![cfg_attr( - all(not(debug_assertions), target_os = "windows"), - windows_subsystem = "windows" -)] - -#[allow(dead_code)] -pub const DEEP_LINK_SCHEME: &str = "appflowy-flutter"; -pub const OPEN_DEEP_LINK: &str = "open_deep_link"; - -mod init; -mod notification; -mod request; - -use crate::init::init_appflowy_core; -use crate::request::invoke_request; -use flowy_notification::{register_notification_sender, unregister_all_notification_sender}; -use notification::*; -use tauri::Manager; - -extern crate dotenv; - -fn main() { - tauri_plugin_deep_link::prepare(DEEP_LINK_SCHEME); - - let flowy_core = init_appflowy_core(); - tauri::Builder::default() - .invoke_handler(tauri::generate_handler![invoke_request]) - .manage(flowy_core) - .on_window_event(|_window_event| {}) - .on_menu_event(|_menu| {}) - .on_page_load(|window, _payload| { - let app_handler = window.app_handle(); - // Make sure hot reload won't register the notification sender twice - unregister_all_notification_sender(); - register_notification_sender(TSNotificationSender::new(app_handler.clone())); - // tauri::async_runtime::spawn(async move {}); - - window.listen_global(AF_EVENT, move |event| { - on_event(app_handler.clone(), event); - }); - }) - .setup(|_app| { - let splashscreen_window = _app.get_window("splashscreen").unwrap(); - let window = _app.get_window("main").unwrap(); - let handle = _app.handle(); - - // we perform the initialization code on a new task so the app doesn't freeze - tauri::async_runtime::spawn(async move { - // initialize your app here instead of sleeping :) - std::thread::sleep(std::time::Duration::from_secs(2)); - - // After it's done, close the splashscreen and display the main window - splashscreen_window.close().unwrap(); - window.show().unwrap(); - // If you need macOS support this must be called in .setup() ! - // Otherwise this could be called right after prepare() but then you don't have access to tauri APIs - // On macOS You still have to install a .app bundle you got from tauri build --debug for this to work! - tauri_plugin_deep_link::register( - DEEP_LINK_SCHEME, - move |request| { - dbg!(&request); - handle.emit_all(OPEN_DEEP_LINK, request).unwrap(); - }, - ) - .unwrap(/* If listening to the scheme is optional for your app, you don't want to unwrap here. */); - }); - - Ok(()) - }) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); -} diff --git a/frontend/appflowy_tauri/src-tauri/src/notification.rs b/frontend/appflowy_tauri/src-tauri/src/notification.rs deleted file mode 100644 index b42541edec..0000000000 --- a/frontend/appflowy_tauri/src-tauri/src/notification.rs +++ /dev/null @@ -1,35 +0,0 @@ -use flowy_notification::entities::SubscribeObject; -use flowy_notification::NotificationSender; -use serde::Serialize; -use tauri::{AppHandle, Event, Manager, Wry}; - -#[allow(dead_code)] -pub const AF_EVENT: &str = "af-event"; -pub const AF_NOTIFICATION: &str = "af-notification"; - -#[tracing::instrument(level = "trace")] -pub fn on_event(app_handler: AppHandle, event: Event) {} - -#[allow(dead_code)] -pub fn send_notification(app_handler: AppHandle, payload: P) { - app_handler.emit_all(AF_NOTIFICATION, payload).unwrap(); -} - -pub struct TSNotificationSender { - handler: AppHandle, -} - -impl TSNotificationSender { - pub fn new(handler: AppHandle) -> Self { - Self { handler } - } -} - -impl NotificationSender for TSNotificationSender { - fn send_subject(&self, subject: SubscribeObject) -> Result<(), String> { - self - .handler - .emit_all(AF_NOTIFICATION, subject) - .map_err(|e| format!("{:?}", e)) - } -} diff --git a/frontend/appflowy_tauri/src-tauri/src/request.rs b/frontend/appflowy_tauri/src-tauri/src/request.rs deleted file mode 100644 index ff69a438c9..0000000000 --- a/frontend/appflowy_tauri/src-tauri/src/request.rs +++ /dev/null @@ -1,45 +0,0 @@ -use crate::init::MutexAppFlowyCore; -use lib_dispatch::prelude::{ - AFPluginDispatcher, AFPluginEventResponse, AFPluginRequest, StatusCode, -}; -use tauri::{AppHandle, Manager, State, Wry}; - -#[derive(Clone, Debug, serde::Deserialize)] -pub struct AFTauriRequest { - ty: String, - payload: Vec, -} - -impl std::convert::From for AFPluginRequest { - fn from(event: AFTauriRequest) -> Self { - AFPluginRequest::new(event.ty).payload(event.payload) - } -} - -#[derive(Clone, serde::Serialize)] -pub struct AFTauriResponse { - code: StatusCode, - payload: Vec, -} - -impl std::convert::From for AFTauriResponse { - fn from(response: AFPluginEventResponse) -> Self { - Self { - code: response.status_code, - payload: response.payload.to_vec(), - } - } -} - -// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command -#[tauri::command] -pub async fn invoke_request( - request: AFTauriRequest, - app_handler: AppHandle, -) -> AFTauriResponse { - let request: AFPluginRequest = request.into(); - let state: State = app_handler.state(); - let dispatcher = state.0.lock().unwrap().dispatcher(); - let response = AFPluginDispatcher::sync_send(dispatcher, request); - response.into() -} diff --git a/frontend/appflowy_tauri/src-tauri/tauri.conf.json b/frontend/appflowy_tauri/src-tauri/tauri.conf.json deleted file mode 100644 index 11dd7c206c..0000000000 --- a/frontend/appflowy_tauri/src-tauri/tauri.conf.json +++ /dev/null @@ -1,113 +0,0 @@ -{ - "build": { - "beforeDevCommand": "npm run dev", - "beforeBuildCommand": "pnpm run build", - "devPath": "http://localhost:1420", - "distDir": "../dist", - "withGlobalTauri": false - }, - "package": { - "productName": "AppFlowy", - "version": "0.0.1" - }, - "tauri": { - "allowlist": { - "all": false, - "shell": { - "all": false, - "open": true - }, - "fs": { - "all": true, - "scope": [ - "$APPLOCALDATA/**" - ], - "readFile": true, - "writeFile": true, - "readDir": true, - "copyFile": true, - "createDir": true, - "removeDir": true, - "removeFile": true, - "renameFile": true, - "exists": true - }, - "clipboard": { - "all": true, - "writeText": true, - "readText": true - }, - "dialog": { - "all": true, - "ask": true, - "confirm": true, - "message": true, - "open": true, - "save": true - } - }, - "bundle": { - "active": true, - "category": "DeveloperTool", - "copyright": "", - "deb": { - "depends": [] - }, - "externalBin": [], - "icon": [ - "icons/32x32.png", - "icons/128x128.png", - "icons/128x128@2x.png", - "icons/icon.icns", - "icons/icon.ico" - ], - "identifier": "com.appflowy.tauri", - "longDescription": "", - "macOS": { - "entitlements": null, - "exceptionDomain": "", - "frameworks": [], - "providerShortName": null, - "signingIdentity": null, - "minimumSystemVersion": "10.15.0" - }, - "resources": [], - "shortDescription": "", - "targets": "all", - "windows": { - "certificateThumbprint": null, - "digestAlgorithm": "sha256", - "timestampUrl": "" - } - }, - "security": { - "csp": null - }, - "updater": { - "active": false - }, - "windows": [ - { - "fileDropEnabled": false, - "fullscreen": false, - "height": 800, - "resizable": true, - "title": "AppFlowy", - "width": 1200, - "minWidth": 800, - "minHeight": 600, - "visible": false, - "label": "main" - }, - { - "height": 300, - "width": 549, - "decorations": false, - "url": "launch_splash.jpg", - "label": "splashscreen", - "center": true, - "visible": true - } - ] - } -} \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/appflowy_app/@types/i18next.d.ts b/frontend/appflowy_tauri/src/appflowy_app/@types/i18next.d.ts deleted file mode 100644 index 6adbb4a512..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/@types/i18next.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -import resources from './resources'; - -declare module 'i18next' { - interface CustomTypeOptions { - defaultNS: 'translation'; - resources: typeof resources; - } -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/@types/resources.ts b/frontend/appflowy_tauri/src/appflowy_app/@types/resources.ts deleted file mode 100644 index 479f05f013..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/@types/resources.ts +++ /dev/null @@ -1,7 +0,0 @@ -import translation from '$app/i18n/translations/en.json'; - -const resources = { - translation, -} as const; - -export default resources; diff --git a/frontend/appflowy_tauri/src/appflowy_app/App.tsx b/frontend/appflowy_tauri/src/appflowy_app/App.tsx deleted file mode 100644 index 9381737341..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/App.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { BrowserRouter } from 'react-router-dom'; - -import { Provider } from 'react-redux'; -import { store } from './stores/store'; - -import { ErrorHandlerPage } from './components/error/ErrorHandlerPage'; -import '$app/i18n/config'; - -import { ErrorBoundary } from 'react-error-boundary'; - -import AppMain from '$app/AppMain'; - -const App = () => { - return ( - - - - - - - - ); -}; - -export default App; diff --git a/frontend/appflowy_tauri/src/appflowy_app/AppMain.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/AppMain.hooks.ts deleted file mode 100644 index 9c46b8ab38..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/AppMain.hooks.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { useAppDispatch, useAppSelector } from '$app/stores/store'; -import { useEffect, useMemo } from 'react'; -import { currentUserActions, LoginState } from '$app_reducers/current-user/slice'; -import { Theme as ThemeType, ThemeMode } from '$app/stores/reducers/current-user/slice'; -import { createTheme } from '@mui/material/styles'; -import { getDesignTokens } from '$app/utils/mui'; -import { useTranslation } from 'react-i18next'; -import { UserService } from '$app/application/user/user.service'; - -export function useUserSetting() { - const dispatch = useAppDispatch(); - const { i18n } = useTranslation(); - const loginState = useAppSelector((state) => state.currentUser.loginState); - - const { themeMode = ThemeMode.System, theme: themeType = ThemeType.Default } = useAppSelector((state) => { - return { - themeMode: state.currentUser.userSetting.themeMode, - theme: state.currentUser.userSetting.theme, - }; - }); - - const isDark = - themeMode === ThemeMode.Dark || - (themeMode === ThemeMode.System && window.matchMedia('(prefers-color-scheme: dark)').matches); - - useEffect(() => { - if (loginState !== LoginState.Success && loginState !== undefined) return; - void (async () => { - const settings = await UserService.getAppearanceSetting(); - - if (!settings) return; - dispatch(currentUserActions.setUserSetting(settings)); - await i18n.changeLanguage(settings.language); - })(); - }, [dispatch, i18n, loginState]); - - useEffect(() => { - const html = document.documentElement; - - html?.setAttribute('data-dark-mode', String(isDark)); - }, [isDark]); - - useEffect(() => { - const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); - - const handleSystemThemeChange = () => { - if (themeMode !== ThemeMode.System) return; - dispatch( - currentUserActions.setUserSetting({ - isDark: mediaQuery.matches, - }) - ); - }; - - mediaQuery.addEventListener('change', handleSystemThemeChange); - - return () => { - mediaQuery.removeEventListener('change', handleSystemThemeChange); - }; - }, [dispatch, themeMode]); - - const muiTheme = useMemo(() => createTheme(getDesignTokens(isDark)), [isDark]); - - return { - muiTheme, - themeMode, - themeType, - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/AppMain.tsx b/frontend/appflowy_tauri/src/appflowy_app/AppMain.tsx deleted file mode 100644 index 76bdb167b0..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/AppMain.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import { Route, Routes } from 'react-router-dom'; -import { ProtectedRoutes } from '$app/components/auth/ProtectedRoutes'; -import { DatabasePage } from '$app/views/DatabasePage'; - -import { ThemeProvider } from '@mui/material'; -import { useUserSetting } from '$app/AppMain.hooks'; -import TrashPage from '$app/views/TrashPage'; -import DocumentPage from '$app/views/DocumentPage'; -import { Toaster } from 'react-hot-toast'; -import AppFlowyDevTool from '$app/components/_shared/devtool/AppFlowyDevTool'; - -function AppMain() { - const { muiTheme } = useUserSetting(); - - return ( - - - }> - } /> - } /> - } /> - - - - {process.env.NODE_ENV === 'development' && } - - ); -} - -export default AppMain; diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/cell/cell_listeners.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/cell/cell_listeners.ts deleted file mode 100644 index c5c94daebc..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/cell/cell_listeners.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Database } from '$app/application/database'; -import { getCell } from './cell_service'; - -export function didDeleteCells({ database, rowId, fieldId }: { database: Database; rowId?: string; fieldId?: string }) { - const ids = Object.keys(database.cells); - - ids.forEach((id) => { - const cell = database.cells[id]; - - if (rowId && cell.rowId !== rowId) return; - if (fieldId && cell.fieldId !== fieldId) return; - - delete database.cells[id]; - }); -} - -export async function didUpdateCells({ - viewId, - database, - rowId, - fieldId, -}: { - viewId: string; - database: Database; - rowId?: string; - fieldId?: string; -}) { - const field = database.fields.find((field) => field.id === fieldId); - - if (!field) { - delete database.cells[`${rowId}:${fieldId}`]; - return; - } - - const ids = Object.keys(database.cells); - - ids.forEach((id) => { - const cell = database.cells[id]; - - if (rowId && cell.rowId !== rowId) return; - if (fieldId && cell.fieldId !== fieldId) return; - - void getCell(viewId, cell.rowId, cell.fieldId, field.type).then((data) => { - // cache cell - database.cells[id] = data; - }); - }); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/cell/cell_service.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/cell/cell_service.ts deleted file mode 100644 index 950f5becb3..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/cell/cell_service.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { - CellIdPB, - CellChangesetPB, - SelectOptionCellChangesetPB, - ChecklistCellDataChangesetPB, - DateCellChangesetPB, - FieldType, -} from '../../../../services/backend'; -import { - DatabaseEventGetCell, - DatabaseEventUpdateCell, - DatabaseEventUpdateSelectOptionCell, - DatabaseEventUpdateChecklistCell, - DatabaseEventUpdateDateCell, -} from '@/services/backend/events/flowy-database2'; -import { SelectOption } from '../field'; -import { Cell, pbToCell } from './cell_types'; - -export async function getCell(viewId: string, rowId: string, fieldId: string, fieldType?: FieldType): Promise { - const payload = CellIdPB.fromObject({ - view_id: viewId, - row_id: rowId, - field_id: fieldId, - }); - - const result = await DatabaseEventGetCell(payload); - - if (result.ok === false) { - return Promise.reject(result.val); - } - - const value = result.val; - - return pbToCell(value, fieldType); -} - -export async function updateCell(viewId: string, rowId: string, fieldId: string, changeset: string): Promise { - const payload = CellChangesetPB.fromObject({ - view_id: viewId, - row_id: rowId, - field_id: fieldId, - cell_changeset: changeset, - }); - - const result = await DatabaseEventUpdateCell(payload); - - return result.unwrap(); -} - -export async function updateSelectCell( - viewId: string, - rowId: string, - fieldId: string, - data: { - insertOptionIds?: string[]; - deleteOptionIds?: string[]; - } -): Promise { - const payload = SelectOptionCellChangesetPB.fromObject({ - cell_identifier: { - view_id: viewId, - row_id: rowId, - field_id: fieldId, - }, - insert_option_ids: data.insertOptionIds, - delete_option_ids: data.deleteOptionIds, - }); - - const result = await DatabaseEventUpdateSelectOptionCell(payload); - - return result.unwrap(); -} - -export async function updateChecklistCell( - viewId: string, - rowId: string, - fieldId: string, - data: { - insertOptions?: string[]; - selectedOptionIds?: string[]; - deleteOptionIds?: string[]; - updateOptions?: Partial[]; - } -): Promise { - const payload = ChecklistCellDataChangesetPB.fromObject({ - view_id: viewId, - row_id: rowId, - field_id: fieldId, - insert_options: data.insertOptions, - selected_option_ids: data.selectedOptionIds, - delete_option_ids: data.deleteOptionIds, - update_options: data.updateOptions, - }); - - const result = await DatabaseEventUpdateChecklistCell(payload); - - return result.unwrap(); -} - -export async function updateDateCell( - viewId: string, - rowId: string, - fieldId: string, - data: { - // 10-digit timestamp - date?: number; - // time string in format HH:mm - time?: string; - // 10-digit timestamp - endDate?: number; - // time string in format HH:mm - endTime?: string; - includeTime?: boolean; - clearFlag?: boolean; - isRange?: boolean; - } -): Promise { - const payload = DateCellChangesetPB.fromObject({ - cell_id: { - view_id: viewId, - row_id: rowId, - field_id: fieldId, - }, - date: data.date, - time: data.time, - include_time: data.includeTime, - clear_flag: data.clearFlag, - end_date: data.endDate, - end_time: data.endTime, - is_range: data.isRange, - }); - - const result = await DatabaseEventUpdateDateCell(payload); - - if (!result.ok) { - return Promise.reject(typeof result.val.msg === 'string' ? result.val.msg : 'Unknown error'); - } - - return result.val; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/cell/cell_types.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/cell/cell_types.ts deleted file mode 100644 index f36f68ad8b..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/cell/cell_types.ts +++ /dev/null @@ -1,163 +0,0 @@ -import { - CellPB, - CheckboxCellDataPB, - ChecklistCellDataPB, - DateCellDataPB, - FieldType, - SelectOptionCellDataPB, - TimestampCellDataPB, - URLCellDataPB, -} from '../../../../services/backend'; -import { SelectOption, pbToSelectOption } from '../field/select_option/select_option_types'; - -export interface Cell { - rowId: string; - fieldId: string; - fieldType: FieldType; - data: unknown; -} - -export interface TextCell extends Cell { - fieldType: FieldType.RichText; - data: string; -} - -export interface NumberCell extends Cell { - fieldType: FieldType.Number; - data: string; -} - -export interface CheckboxCell extends Cell { - fieldType: FieldType.Checkbox; - data: boolean; -} - -export interface UrlCell extends Cell { - fieldType: FieldType.URL; - data: string; -} - -export interface SelectCell extends Cell { - fieldType: FieldType.SingleSelect | FieldType.MultiSelect; - data: SelectCellData; -} - -export interface SelectCellData { - selectedOptionIds?: string[]; -} - -export interface DateTimeCell extends Cell { - fieldType: FieldType.DateTime; - data: DateTimeCellData; -} - -export interface TimeStampCell extends Cell { - fieldType: FieldType.LastEditedTime | FieldType.CreatedTime; - data: TimestampCellData; -} - -export interface DateTimeCellData { - date?: string; - time?: string; - timestamp?: number; - includeTime?: boolean; - endDate?: string; - endTime?: string; - endTimestamp?: number; - isRange?: boolean; -} - -export interface TimestampCellData { - dataTime?: string; - timestamp?: number; -} - -export interface ChecklistCell extends Cell { - fieldType: FieldType.Checklist; - data: ChecklistCellData; -} - -export interface ChecklistCellData { - /** - * link to [SelectOption's id property]{@link SelectOption#id}. - */ - selectedOptions?: string[]; - percentage?: number; - options?: SelectOption[]; -} - -export type UndeterminedCell = - | TextCell - | NumberCell - | DateTimeCell - | SelectCell - | CheckboxCell - | UrlCell - | ChecklistCell; - -const pbToCheckboxCellData = (pb: CheckboxCellDataPB): boolean => ( - pb.is_checked -); - -const pbToDateTimeCellData = (pb: DateCellDataPB): DateTimeCellData => ({ - date: pb.date, - time: pb.time, - timestamp: pb.timestamp, - includeTime: pb.include_time, - endDate: pb.end_date, - endTime: pb.end_time, - endTimestamp: pb.end_timestamp, - isRange: pb.is_range, -}); - -const pbToTimestampCellData = (pb: TimestampCellDataPB): TimestampCellData => ({ - dataTime: pb.date_time, - timestamp: pb.timestamp, -}); - -export const pbToSelectCellData = (pb: SelectOptionCellDataPB): SelectCellData => { - return { - selectedOptionIds: pb.select_options.map((option) => option.id), - }; -}; - -const pbToURLCellData = (pb: URLCellDataPB): string => ( - pb.content -); - -export const pbToChecklistCellData = (pb: ChecklistCellDataPB): ChecklistCellData => ({ - selectedOptions: pb.selected_options.map(({ id }) => id), - percentage: pb.percentage, - options: pb.options.map(pbToSelectOption), -}); - -function bytesToCellData(bytes: Uint8Array, fieldType: FieldType) { - switch (fieldType) { - case FieldType.RichText: - case FieldType.Number: - return new TextDecoder().decode(bytes); - case FieldType.Checkbox: - return pbToCheckboxCellData(CheckboxCellDataPB.deserialize(bytes)); - case FieldType.DateTime: - return pbToDateTimeCellData(DateCellDataPB.deserialize(bytes)); - case FieldType.LastEditedTime: - case FieldType.CreatedTime: - return pbToTimestampCellData(TimestampCellDataPB.deserialize(bytes)); - case FieldType.SingleSelect: - case FieldType.MultiSelect: - return pbToSelectCellData(SelectOptionCellDataPB.deserialize(bytes)); - case FieldType.URL: - return pbToURLCellData(URLCellDataPB.deserialize(bytes)); - case FieldType.Checklist: - return pbToChecklistCellData(ChecklistCellDataPB.deserialize(bytes)); - } -} - -export const pbToCell = (pb: CellPB, fieldType: FieldType = pb.field_type): Cell => { - return { - rowId: pb.row_id, - fieldId: pb.field_id, - fieldType: fieldType, - data: bytesToCellData(pb.data, fieldType), - }; -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/cell/index.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/cell/index.ts deleted file mode 100644 index bc6bdc4417..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/cell/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './cell_types'; -export * as cellService from './cell_service'; -export * as cellListeners from './cell_listeners'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/database/database_service.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/database/database_service.ts deleted file mode 100644 index 74ebfb1df0..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/database/database_service.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { DatabaseViewIdPB } from '@/services/backend'; -import { - DatabaseEventGetDatabase, - DatabaseEventGetDatabaseId, - DatabaseEventGetDatabaseSetting, -} from '@/services/backend/events/flowy-database2'; -import { fieldService } from '../field'; -import { pbToFilter } from '../filter'; -import { groupService, pbToGroupSetting } from '../group'; -import { pbToRowMeta } from '../row'; -import { pbToSort } from '../sort'; -import { Database } from './database_types'; - -export async function getDatabaseId(viewId: string): Promise { - const payload = DatabaseViewIdPB.fromObject({ value: viewId }); - - const result = await DatabaseEventGetDatabaseId(payload); - - return result.map((value) => value.value).unwrap(); -} - -export async function getDatabase(viewId: string) { - const payload = DatabaseViewIdPB.fromObject({ - value: viewId, - }); - - const result = await DatabaseEventGetDatabase(payload); - - if (!result.ok) return Promise.reject('Failed to get database'); - - return result - .map((value) => { - return { - id: value.id, - isLinked: value.is_linked, - layoutType: value.layout_type, - fieldIds: value.fields.map((field) => field.field_id), - rowMetas: value.rows.map(pbToRowMeta), - }; - }) - .unwrap(); -} - -export async function getDatabaseSetting(viewId: string) { - const payload = DatabaseViewIdPB.fromObject({ - value: viewId, - }); - - const result = await DatabaseEventGetDatabaseSetting(payload); - - return result - .map((value) => { - return { - filters: value.filters.items.map(pbToFilter), - sorts: value.sorts.items.map(pbToSort), - groupSettings: value.group_settings.items.map(pbToGroupSetting), - }; - }) - .unwrap(); -} - -export async function openDatabase(viewId: string): Promise { - const { id, isLinked, layoutType, fieldIds, rowMetas } = await getDatabase(viewId); - - const { filters, sorts, groupSettings } = await getDatabaseSetting(viewId); - - const { fields, typeOptions } = await fieldService.getFields(viewId, fieldIds); - - const groups = await groupService.getGroups(viewId); - - return { - id, - isLinked, - layoutType, - fields, - rowMetas, - filters, - sorts, - groups, - groupSettings, - typeOptions, - cells: {}, - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/database/database_types.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/database/database_types.ts deleted file mode 100644 index 627cd94013..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/database/database_types.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { DatabaseLayoutPB } from '@/services/backend'; -import { Field, UndeterminedTypeOptionData } from '../field'; -import { Filter } from '../filter'; -import { GroupSetting, Group } from '../group'; -import { RowMeta } from '../row'; -import { Sort } from '../sort'; -import { Cell } from '../cell'; - -export interface Database { - id: string; - isLinked: boolean; - layoutType: DatabaseLayoutPB; - fields: Field[]; - rowMetas: RowMeta[]; - filters: Filter[]; - sorts: Sort[]; - groupSettings: GroupSetting[]; - groups: Group[]; - typeOptions: Record; - cells: Record; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/database/index.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/database/index.ts deleted file mode 100644 index e656d98287..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/database/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './database_types'; -export * as databaseService from './database_service'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/database_view/database_view_service.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/database_view/database_view_service.ts deleted file mode 100644 index 87d99d9b75..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/database_view/database_view_service.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { CreateViewPayloadPB, RepeatedViewIdPB, UpdateViewPayloadPB, ViewIdPB, ViewLayoutPB } from '@/services/backend'; -import { - FolderEventCreateView, - FolderEventDeleteView, - FolderEventGetView, - FolderEventUpdateView, -} from '@/services/backend/events/flowy-folder'; -import { databaseService } from '../database'; -import { Page, parserViewPBToPage } from '$app_reducers/pages/slice'; - -export async function getDatabaseViews(viewId: string): Promise { - const payload = ViewIdPB.fromObject({ value: viewId }); - - const result = await FolderEventGetView(payload); - - if (result.ok) { - return [parserViewPBToPage(result.val), ...result.val.child_views.map(parserViewPBToPage)]; - } - - return Promise.reject(result.val); -} - -export async function createDatabaseView( - viewId: string, - layout: ViewLayoutPB, - name: string, - databaseId?: string -): Promise { - const payload = CreateViewPayloadPB.fromObject({ - parent_view_id: viewId, - name, - layout, - meta: { - database_id: databaseId || (await databaseService.getDatabaseId(viewId)), - }, - }); - - const result = await FolderEventCreateView(payload); - - if (result.ok) { - return parserViewPBToPage(result.val); - } - - return Promise.reject(result.err); -} - -export async function updateView(viewId: string, view: { name?: string; layout?: ViewLayoutPB }): Promise { - const payload = UpdateViewPayloadPB.fromObject({ - view_id: viewId, - name: view.name, - layout: view.layout, - }); - - const result = await FolderEventUpdateView(payload); - - if (result.ok) { - return parserViewPBToPage(result.val); - } - - return Promise.reject(result.err); -} - -export async function deleteView(viewId: string): Promise { - const payload = RepeatedViewIdPB.fromObject({ - items: [viewId], - }); - - const result = await FolderEventDeleteView(payload); - - if (result.ok) { - return; - } - - return Promise.reject(result.err); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/database_view/index.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/database_view/index.ts deleted file mode 100644 index b2a6e1a5f1..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/database_view/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * as databaseViewService from './database_view_service'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/field/field_listeners.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/field/field_listeners.ts deleted file mode 100644 index ef36daa20c..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/field/field_listeners.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { DatabaseFieldChangesetPB, FieldSettingsPB, FieldVisibility } from '@/services/backend'; -import { Database, fieldService } from '$app/application/database'; -import { didDeleteCells, didUpdateCells } from '$app/application/database/cell/cell_listeners'; - -export function didUpdateFieldSettings(database: Database, settings: FieldSettingsPB) { - const { field_id: fieldId, visibility, width } = settings; - const field = database.fields.find((field) => field.id === fieldId); - - if (!field) return; - field.visibility = visibility; - field.width = width; - // delete cells if field is hidden - if (visibility === FieldVisibility.AlwaysHidden) { - didDeleteCells({ database, fieldId }); - } -} - -export async function didUpdateFields(viewId: string, database: Database, changeset: DatabaseFieldChangesetPB) { - const { fields, typeOptions } = await fieldService.getFields(viewId); - - database.fields = fields; - const deletedFieldIds = Object.keys(changeset.deleted_fields); - const updatedFieldIds = changeset.updated_fields.map((field) => field.id); - - Object.assign(database.typeOptions, typeOptions); - deletedFieldIds.forEach( - (fieldId) => { - // delete cache cells - didDeleteCells({ database, fieldId }); - // delete cache type options - delete database.typeOptions[fieldId]; - }, - [database.typeOptions] - ); - - updatedFieldIds.forEach((fieldId) => { - // delete cache cells - void didUpdateCells({ viewId, database, fieldId }); - }); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/field/field_service.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/field/field_service.ts deleted file mode 100644 index 219aeb3ea5..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/field/field_service.ts +++ /dev/null @@ -1,215 +0,0 @@ -import { - CreateFieldPayloadPB, - DeleteFieldPayloadPB, - DuplicateFieldPayloadPB, - FieldChangesetPB, - FieldType, - GetFieldPayloadPB, - MoveFieldPayloadPB, - RepeatedFieldIdPB, - UpdateFieldTypePayloadPB, - FieldSettingsChangesetPB, - FieldVisibility, - DatabaseViewIdPB, - OrderObjectPositionTypePB, -} from '@/services/backend'; -import { - DatabaseEventDuplicateField, - DatabaseEventUpdateField, - DatabaseEventUpdateFieldType, - DatabaseEventMoveField, - DatabaseEventGetFields, - DatabaseEventDeleteField, - DatabaseEventCreateField, - DatabaseEventUpdateFieldSettings, - DatabaseEventGetAllFieldSettings, -} from '@/services/backend/events/flowy-database2'; -import { Field, pbToField } from './field_types'; -import { bytesToTypeOption } from './type_option'; -import { Database } from '$app/application/database'; - -export async function getFields( - viewId: string, - fieldIds?: string[] -): Promise<{ - fields: Field[]; - typeOptions: Database['typeOptions']; -}> { - const payload = GetFieldPayloadPB.fromObject({ - view_id: viewId, - field_ids: fieldIds - ? RepeatedFieldIdPB.fromObject({ - items: fieldIds.map((fieldId) => ({ field_id: fieldId })), - }) - : undefined, - }); - - const result = await DatabaseEventGetFields(payload); - - const getSettingsPayload = DatabaseViewIdPB.fromObject({ - value: viewId, - }); - - const settings = await DatabaseEventGetAllFieldSettings(getSettingsPayload); - - if (settings.ok === false || result.ok === false) { - return Promise.reject('Failed to get fields'); - } - - const typeOptions: Database['typeOptions'] = {}; - - const fields = await Promise.all( - result.val.items.map(async (item) => { - const setting = settings.val.items.find((setting) => setting.field_id === item.id); - - const field = pbToField(item); - - const typeOption = bytesToTypeOption(item.type_option_data, item.field_type); - - if (typeOption) { - typeOptions[item.id] = typeOption; - } - - return { - ...field, - visibility: setting?.visibility, - width: setting?.width, - }; - }) - ); - - return { fields, typeOptions }; -} - -export async function createField({ - viewId, - targetFieldId, - fieldPosition, - fieldType, - data, -}: { - viewId: string; - targetFieldId?: string; - fieldPosition?: OrderObjectPositionTypePB; - fieldType?: FieldType; - data?: Uint8Array; -}): Promise { - const payload = CreateFieldPayloadPB.fromObject({ - view_id: viewId, - field_type: fieldType, - type_option_data: data, - field_position: { - position: fieldPosition, - object_id: targetFieldId, - }, - }); - - const result = await DatabaseEventCreateField(payload); - - if (result.ok === false) { - return Promise.reject('Failed to create field'); - } - - return pbToField(result.val); -} - -export async function duplicateField(viewId: string, fieldId: string): Promise { - const payload = DuplicateFieldPayloadPB.fromObject({ - view_id: viewId, - field_id: fieldId, - }); - - const result = await DatabaseEventDuplicateField(payload); - - if (result.ok === false) { - return Promise.reject('Failed to duplicate field'); - } - - return result.val; -} - -export async function updateField( - viewId: string, - fieldId: string, - data: { - name?: string; - desc?: string; - } -): Promise { - const payload = FieldChangesetPB.fromObject({ - view_id: viewId, - field_id: fieldId, - ...data, - }); - - const result = await DatabaseEventUpdateField(payload); - - return result.unwrap(); -} - -export async function updateFieldType(viewId: string, fieldId: string, fieldType: FieldType): Promise { - const payload = UpdateFieldTypePayloadPB.fromObject({ - view_id: viewId, - field_id: fieldId, - field_type: fieldType, - }); - - const result = await DatabaseEventUpdateFieldType(payload); - - return result.unwrap(); -} - -export async function moveField(viewId: string, fromFieldId: string, toFieldId: string): Promise { - const payload = MoveFieldPayloadPB.fromObject({ - view_id: viewId, - from_field_id: fromFieldId, - to_field_id: toFieldId, - }); - - const result = await DatabaseEventMoveField(payload); - - return result.unwrap(); -} - -export async function deleteField(viewId: string, fieldId: string): Promise { - const payload = DeleteFieldPayloadPB.fromObject({ - view_id: viewId, - field_id: fieldId, - }); - - const result = await DatabaseEventDeleteField(payload); - - return result.unwrap(); -} - -export async function updateFieldSetting( - viewId: string, - fieldId: string, - settings: { - visibility?: FieldVisibility; - width?: number; - } -): Promise { - const payload = FieldSettingsChangesetPB.fromObject({ - view_id: viewId, - field_id: fieldId, - ...settings, - }); - - const result = await DatabaseEventUpdateFieldSettings(payload); - - if (result.ok === false) { - return Promise.reject('Failed to update field settings'); - } - - return result.val; -} - -export const reorderFields = (list: Field[], startIndex: number, endIndex: number) => { - const result = Array.from(list); - const [removed] = result.splice(startIndex, 1); - - result.splice(endIndex, 0, removed); - - return result; -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/field/field_types.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/field/field_types.ts deleted file mode 100644 index 00e7e02d4e..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/field/field_types.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { FieldPB, FieldType, FieldVisibility } from '@/services/backend'; - -export interface Field { - id: string; - name: string; - type: FieldType; - visibility?: FieldVisibility; - width?: number; - isPrimary: boolean; -} - -export interface NumberField extends Field { - type: FieldType.Number; -} - -export interface DateTimeField extends Field { - type: FieldType.DateTime; -} - -export interface LastEditedTimeField extends Field { - type: FieldType.LastEditedTime; -} - -export interface CreatedTimeField extends Field { - type: FieldType.CreatedTime; -} - -export type UndeterminedDateField = DateTimeField | CreatedTimeField | LastEditedTimeField; - -export interface SelectField extends Field { - type: FieldType.SingleSelect | FieldType.MultiSelect; -} - -export interface ChecklistField extends Field { - type: FieldType.Checklist; -} - -export interface DateTimeField extends Field { - type: FieldType.DateTime; -} - -export type UndeterminedField = NumberField | DateTimeField | SelectField | Field; - -export const pbToField = (pb: FieldPB): Field => { - return { - id: pb.id, - name: pb.name, - type: pb.field_type, - isPrimary: pb.is_primary, - }; -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/field/index.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/field/index.ts deleted file mode 100644 index fa993023e1..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/field/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './select_option'; -export * from './type_option'; -export * from './field_types'; -export * as fieldService from './field_service'; -export * as fieldListeners from './field_listeners'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/field/select_option/index.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/field/select_option/index.ts deleted file mode 100644 index f0b9e58852..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/field/select_option/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './select_option_types'; -export * as selectOptionService from './select_option_service'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/field/select_option/select_option_service.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/field/select_option/select_option_service.ts deleted file mode 100644 index 5757b8185d..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/field/select_option/select_option_service.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { CreateSelectOptionPayloadPB, RepeatedSelectOptionPayload } from '@/services/backend'; -import { - DatabaseEventCreateSelectOption, - DatabaseEventInsertOrUpdateSelectOption, - DatabaseEventDeleteSelectOption, -} from '@/services/backend/events/flowy-database2'; -import { pbToSelectOption, SelectOption } from './select_option_types'; - -export async function createSelectOption(viewId: string, fieldId: string, optionName: string): Promise { - const payload = CreateSelectOptionPayloadPB.fromObject({ - view_id: viewId, - field_id: fieldId, - option_name: optionName, - }); - - const result = await DatabaseEventCreateSelectOption(payload); - - return result.map(pbToSelectOption).unwrap(); -} - -/** - * @param [rowId] If pass the rowId, the cell will select this option after insert or update. - */ -export async function insertOrUpdateSelectOption( - viewId: string, - fieldId: string, - items: Partial[], - rowId?: string -): Promise { - const payload = RepeatedSelectOptionPayload.fromObject({ - view_id: viewId, - field_id: fieldId, - row_id: rowId, - items: items, - }); - - const result = await DatabaseEventInsertOrUpdateSelectOption(payload); - - return result.unwrap(); -} - -export async function deleteSelectOption( - viewId: string, - fieldId: string, - items: Partial[], - rowId?: string -): Promise { - const payload = RepeatedSelectOptionPayload.fromObject({ - view_id: viewId, - field_id: fieldId, - row_id: rowId, - items, - }); - - const result = await DatabaseEventDeleteSelectOption(payload); - - return result.unwrap(); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/field/select_option/select_option_types.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/field/select_option/select_option_types.ts deleted file mode 100644 index ec36639a7c..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/field/select_option/select_option_types.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { SelectOptionColorPB, SelectOptionPB } from '@/services/backend'; - -export interface SelectOption { - id: string; - name: string; - color: SelectOptionColorPB; -} - -export function pbToSelectOption(pb: SelectOptionPB): SelectOption { - return { - id: pb.id, - name: pb.name, - color: pb.color, - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/field/type_option/index.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/field/type_option/index.ts deleted file mode 100644 index d0b9122d90..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/field/type_option/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './type_option_types'; -export * from './type_option_service'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/field/type_option/type_option_service.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/field/type_option/type_option_service.ts deleted file mode 100644 index 90a0dd3106..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/field/type_option/type_option_service.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { FieldType, TypeOptionChangesetPB } from '@/services/backend'; -import { - DatabaseEventUpdateFieldTypeOption, -} from '@/services/backend/events/flowy-database2'; -import { UndeterminedTypeOptionData, typeOptionDataToPB } from './type_option_types'; - -export async function updateTypeOption( - viewId: string, - fieldId: string, - fieldType: FieldType, - data: UndeterminedTypeOptionData -) { - const payload = TypeOptionChangesetPB.fromObject({ - view_id: viewId, - field_id: fieldId, - type_option_data: typeOptionDataToPB(data, fieldType)?.serialize(), - }); - - const result = await DatabaseEventUpdateFieldTypeOption(payload); - - if (!result.ok) { - return Promise.reject(result.val); - } - - return; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/field/type_option/type_option_types.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/field/type_option/type_option_types.ts deleted file mode 100644 index 57de7b828c..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/field/type_option/type_option_types.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { - CheckboxTypeOptionPB, - DateFormatPB, - FieldType, - MultiSelectTypeOptionPB, - NumberFormatPB, - NumberTypeOptionPB, - RichTextTypeOptionPB, - SingleSelectTypeOptionPB, - TimeFormatPB, - ChecklistTypeOptionPB, - DateTypeOptionPB, - TimestampTypeOptionPB, -} from '@/services/backend'; -import { pbToSelectOption, SelectOption } from '../select_option'; - -export interface TextTypeOption { - data?: string; -} - -export interface NumberTypeOption { - format?: NumberFormatPB; - scale?: number; - symbol?: string; - name?: string; -} - -export interface DateTimeTypeOption { - dateFormat?: DateFormatPB; - timeFormat?: TimeFormatPB; - timezoneId?: string; -} -export interface TimeStampTypeOption extends DateTimeTypeOption { - includeTime?: boolean; - fieldType?: FieldType; -} - -export interface SelectTypeOption { - options?: SelectOption[]; - disableColor?: boolean; -} - -export interface CheckboxTypeOption { - isSelected?: boolean; -} - -export interface ChecklistTypeOption { - config?: string; -} - -export type UndeterminedTypeOptionData = - | TextTypeOption - | NumberTypeOption - | SelectTypeOption - | CheckboxTypeOption - | ChecklistTypeOption - | DateTimeTypeOption - | TimeStampTypeOption; - -export function typeOptionDataToPB(data: UndeterminedTypeOptionData, fieldType: FieldType) { - switch (fieldType) { - case FieldType.Number: - return NumberTypeOptionPB.fromObject(data as NumberTypeOption); - case FieldType.DateTime: - return dateTimeTypeOptionToPB(data as DateTimeTypeOption); - case FieldType.CreatedTime: - case FieldType.LastEditedTime: - return timestampTypeOptionToPB(data as TimeStampTypeOption); - - default: - return null; - } -} - -function dateTimeTypeOptionToPB(data: DateTimeTypeOption): DateTypeOptionPB { - return DateTypeOptionPB.fromObject({ - time_format: data.timeFormat, - date_format: data.dateFormat, - timezone_id: data.timezoneId, - }); -} - -function timestampTypeOptionToPB(data: TimeStampTypeOption): TimestampTypeOptionPB { - return TimestampTypeOptionPB.fromObject({ - include_time: data.includeTime, - date_format: data.dateFormat, - time_format: data.timeFormat, - field_type: data.fieldType, - }); -} - -function pbToSelectTypeOption(pb: SingleSelectTypeOptionPB | MultiSelectTypeOptionPB): SelectTypeOption { - return { - options: pb.options?.map(pbToSelectOption), - disableColor: pb.disable_color, - }; -} - -function pbToCheckboxTypeOption(pb: CheckboxTypeOptionPB): CheckboxTypeOption { - return { - isSelected: pb.dummy_field, - }; -} - -function pbToChecklistTypeOption(pb: ChecklistTypeOptionPB): ChecklistTypeOption { - return { - config: pb.config, - }; -} - -function pbToDateTypeOption(pb: DateTypeOptionPB): DateTimeTypeOption { - return { - dateFormat: pb.date_format, - timezoneId: pb.timezone_id, - timeFormat: pb.time_format, - }; -} - -function pbToTimeStampTypeOption(pb: TimestampTypeOptionPB): TimeStampTypeOption { - return { - includeTime: pb.include_time, - dateFormat: pb.date_format, - timeFormat: pb.time_format, - fieldType: pb.field_type, - }; -} - -export function bytesToTypeOption(data: Uint8Array, fieldType: FieldType) { - switch (fieldType) { - case FieldType.RichText: - return RichTextTypeOptionPB.deserialize(data).toObject() as TextTypeOption; - case FieldType.Number: - return NumberTypeOptionPB.deserialize(data).toObject() as NumberTypeOption; - case FieldType.SingleSelect: - return pbToSelectTypeOption(SingleSelectTypeOptionPB.deserialize(data)); - case FieldType.MultiSelect: - return pbToSelectTypeOption(MultiSelectTypeOptionPB.deserialize(data)); - case FieldType.Checkbox: - return pbToCheckboxTypeOption(CheckboxTypeOptionPB.deserialize(data)); - case FieldType.Checklist: - return pbToChecklistTypeOption(ChecklistTypeOptionPB.deserialize(data)); - case FieldType.DateTime: - return pbToDateTypeOption(DateTypeOptionPB.deserialize(data)); - case FieldType.CreatedTime: - case FieldType.LastEditedTime: - return pbToTimeStampTypeOption(TimestampTypeOptionPB.deserialize(data)); - } -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_data.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_data.ts deleted file mode 100644 index 72526b577f..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_data.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { - CheckboxFilterConditionPB, - ChecklistFilterConditionPB, - FieldType, - NumberFilterConditionPB, - SelectOptionFilterConditionPB, - TextFilterConditionPB, -} from '@/services/backend'; -import { UndeterminedFilter } from '$app/application/database'; - -export function getDefaultFilter(fieldType: FieldType): UndeterminedFilter['data'] | undefined { - switch (fieldType) { - case FieldType.RichText: - case FieldType.URL: - return { - condition: TextFilterConditionPB.TextContains, - content: '', - }; - case FieldType.Number: - return { - condition: NumberFilterConditionPB.NumberIsNotEmpty, - }; - case FieldType.Checkbox: - return { - condition: CheckboxFilterConditionPB.IsUnChecked, - }; - case FieldType.Checklist: - return { - condition: ChecklistFilterConditionPB.IsIncomplete, - }; - case FieldType.SingleSelect: - return { - condition: SelectOptionFilterConditionPB.OptionIs, - }; - case FieldType.MultiSelect: - return { - condition: SelectOptionFilterConditionPB.OptionContains, - }; - default: - return; - } -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_listeners.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_listeners.ts deleted file mode 100644 index 323f8dac82..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_listeners.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Database, pbToFilter } from '$app/application/database'; -import { FilterChangesetNotificationPB } from '@/services/backend'; - -export const didUpdateFilter = (database: Database, changeset: FilterChangesetNotificationPB) => { - const filters = changeset.filters.items.map((pb) => pbToFilter(pb)); - - database.filters = filters; -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_service.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_service.ts deleted file mode 100644 index 6283763d28..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_service.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { - DatabaseEventGetAllFilters, - DatabaseEventUpdateDatabaseSetting, - DatabaseSettingChangesetPB, - DatabaseViewIdPB, - FieldType, - FilterPB, -} from '@/services/backend/events/flowy-database2'; -import { Filter, filterDataToPB, UndeterminedFilter } from './filter_types'; - -export async function getAllFilters(viewId: string): Promise { - const payload = DatabaseViewIdPB.fromObject({ value: viewId }); - - const result = await DatabaseEventGetAllFilters(payload); - - return result.map((value) => value.items).unwrap(); -} - -export async function insertFilter({ - viewId, - fieldId, - fieldType, - data, -}: { - viewId: string; - fieldId: string; - fieldType: FieldType; - data?: UndeterminedFilter['data']; -}): Promise { - const payload = DatabaseSettingChangesetPB.fromObject({ - view_id: viewId, - insert_filter: { - data: { - field_id: fieldId, - field_type: fieldType, - data: data ? filterDataToPB(data, fieldType)?.serialize() : undefined, - }, - }, - }); - - const result = await DatabaseEventUpdateDatabaseSetting(payload); - - if (!result.ok) { - return Promise.reject(result.val); - } - - return result.val; -} - -export async function updateFilter(viewId: string, filter: UndeterminedFilter): Promise { - const payload = DatabaseSettingChangesetPB.fromObject({ - view_id: viewId, - update_filter_data: { - filter_id: filter.id, - data: { - field_id: filter.fieldId, - field_type: filter.fieldType, - data: filterDataToPB(filter.data, filter.fieldType)?.serialize(), - }, - }, - }); - - const result = await DatabaseEventUpdateDatabaseSetting(payload); - - if (!result.ok) { - return Promise.reject(result.val); - } - - return result.val; -} - -export async function deleteFilter(viewId: string, filter: Omit): Promise { - const payload = DatabaseSettingChangesetPB.fromObject({ - view_id: viewId, - delete_filter: { - filter_id: filter.id, - field_id: filter.fieldId, - }, - }); - - const result = await DatabaseEventUpdateDatabaseSetting(payload); - - if (!result.ok) { - return Promise.reject(result.val); - } - - return result.val; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_types.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_types.ts deleted file mode 100644 index f9f80985e5..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_types.ts +++ /dev/null @@ -1,202 +0,0 @@ -import { - CheckboxFilterConditionPB, - CheckboxFilterPB, - FieldType, - FilterPB, - NumberFilterConditionPB, - NumberFilterPB, - SelectOptionFilterConditionPB, - SelectOptionFilterPB, - TextFilterConditionPB, - TextFilterPB, - ChecklistFilterConditionPB, - ChecklistFilterPB, - DateFilterConditionPB, - DateFilterPB, -} from '@/services/backend'; - -export interface Filter { - id: string; - fieldId: string; - fieldType: FieldType; - data: unknown; -} - -export interface TextFilter extends Filter { - fieldType: FieldType.RichText; - data: TextFilterData; -} - -export interface TextFilterData { - condition: TextFilterConditionPB; - content?: string; -} - -export interface SelectFilter extends Filter { - fieldType: FieldType.SingleSelect | FieldType.MultiSelect; - data: SelectFilterData; -} - -export interface NumberFilter extends Filter { - fieldType: FieldType.Number; - data: NumberFilterData; -} - -export interface CheckboxFilter extends Filter { - fieldType: FieldType.Checkbox; - data: CheckboxFilterData; -} - -export interface CheckboxFilterData { - condition?: CheckboxFilterConditionPB; -} - -export interface ChecklistFilter extends Filter { - fieldType: FieldType.Checklist; - data: ChecklistFilterData; -} - -export interface DateFilter extends Filter { - fieldType: FieldType.DateTime | FieldType.CreatedTime | FieldType.LastEditedTime; - data: DateFilterData; -} - -export interface ChecklistFilterData { - condition?: ChecklistFilterConditionPB; -} - -export interface SelectFilterData { - condition?: SelectOptionFilterConditionPB; - optionIds?: string[]; -} - -export interface NumberFilterData { - condition: NumberFilterConditionPB; - content?: string; -} - -export interface DateFilterData { - condition: DateFilterConditionPB; - start?: number; - end?: number; - timestamp?: number; -} - -export type UndeterminedFilter = - | TextFilter - | SelectFilter - | NumberFilter - | CheckboxFilter - | ChecklistFilter - | DateFilter; - -export function filterDataToPB(data: UndeterminedFilter['data'], fieldType: FieldType) { - switch (fieldType) { - case FieldType.RichText: - case FieldType.URL: - return TextFilterPB.fromObject({ - condition: (data as TextFilterData).condition, - content: (data as TextFilterData).content, - }); - case FieldType.SingleSelect: - case FieldType.MultiSelect: - return SelectOptionFilterPB.fromObject({ - condition: (data as SelectFilterData).condition, - option_ids: (data as SelectFilterData).optionIds, - }); - case FieldType.Number: - return NumberFilterPB.fromObject({ - condition: (data as NumberFilterData).condition, - content: (data as NumberFilterData).content, - }); - case FieldType.Checkbox: - return CheckboxFilterPB.fromObject({ - condition: (data as CheckboxFilterData).condition, - }); - case FieldType.Checklist: - return ChecklistFilterPB.fromObject({ - condition: (data as ChecklistFilterData).condition, - }); - case FieldType.DateTime: - case FieldType.CreatedTime: - case FieldType.LastEditedTime: - return DateFilterPB.fromObject({ - condition: (data as DateFilterData).condition, - start: (data as DateFilterData).start, - end: (data as DateFilterData).end, - timestamp: (data as DateFilterData).timestamp, - }); - } -} - -export function pbToTextFilterData(pb: TextFilterPB): TextFilterData { - return { - condition: pb.condition, - content: pb.content, - }; -} - -export function pbToSelectFilterData(pb: SelectOptionFilterPB): SelectFilterData { - return { - condition: pb.condition, - optionIds: pb.option_ids, - }; -} - -export function pbToNumberFilterData(pb: NumberFilterPB): NumberFilterData { - return { - condition: pb.condition, - content: pb.content, - }; -} - -export function pbToCheckboxFilterData(pb: CheckboxFilterPB): CheckboxFilterData { - return { - condition: pb.condition, - }; -} - -export function pbToChecklistFilterData(pb: ChecklistFilterPB): ChecklistFilterData { - return { - condition: pb.condition, - }; -} - -export function pbToDateFilterData(pb: DateFilterPB): DateFilterData { - return { - condition: pb.condition, - start: pb.start, - end: pb.end, - timestamp: pb.timestamp, - }; -} - -export function bytesToFilterData(bytes: Uint8Array, fieldType: FieldType) { - switch (fieldType) { - case FieldType.RichText: - case FieldType.URL: - return pbToTextFilterData(TextFilterPB.deserialize(bytes)); - case FieldType.SingleSelect: - case FieldType.MultiSelect: - return pbToSelectFilterData(SelectOptionFilterPB.deserialize(bytes)); - case FieldType.Number: - return pbToNumberFilterData(NumberFilterPB.deserialize(bytes)); - case FieldType.Checkbox: - return pbToCheckboxFilterData(CheckboxFilterPB.deserialize(bytes)); - case FieldType.Checklist: - return pbToChecklistFilterData(ChecklistFilterPB.deserialize(bytes)); - case FieldType.DateTime: - case FieldType.CreatedTime: - case FieldType.LastEditedTime: - return pbToDateFilterData(DateFilterPB.deserialize(bytes)); - } -} - -export function pbToFilter(pb: FilterPB): Filter { - return { - id: pb.id, - fieldId: pb.data.field_id, - fieldType: pb.data.field_type, - data: bytesToFilterData(pb.data.data, pb.data.field_type), - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/index.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/index.ts deleted file mode 100644 index ac10d27d0a..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './filter_types'; -export * as filterService from './filter_service'; -export * as filterListeners from './filter_listeners'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/group/group_service.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/group/group_service.ts deleted file mode 100644 index 24f24d65ec..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/group/group_service.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { - DatabaseViewIdPB, - GroupByFieldPayloadPB, - MoveGroupPayloadPB, - UpdateGroupPB, -} from '@/services/backend'; -import { - DatabaseEventGetGroups, - DatabaseEventMoveGroup, - DatabaseEventSetGroupByField, - DatabaseEventUpdateGroup, -} from '@/services/backend/events/flowy-database2'; -import { Group, pbToGroup } from './group_types'; - -export async function getGroups(viewId: string): Promise { - const payload = DatabaseViewIdPB.fromObject({ value: viewId }); - - const result = await DatabaseEventGetGroups(payload); - - return result.map(value => value.items.map(pbToGroup)).unwrap(); -} - -export async function setGroupByField(viewId: string, fieldId: string): Promise { - const payload = GroupByFieldPayloadPB.fromObject({ - view_id: viewId, - field_id: fieldId, - }); - - const result = await DatabaseEventSetGroupByField(payload); - - return result.unwrap(); -} - -export async function updateGroup( - viewId: string, - group: { - id: string, - name?: string, - visible?: boolean, - }, -): Promise { - const payload = UpdateGroupPB.fromObject({ - view_id: viewId, - group_id: group.id, - name: group.name, - visible: group.visible, - }); - - const result = await DatabaseEventUpdateGroup(payload); - - return result.unwrap(); -} - -export async function moveGroup(viewId: string, fromGroupId: string, toGroupId: string): Promise { - const payload = MoveGroupPayloadPB.fromObject({ - view_id: viewId, - from_group_id: fromGroupId, - to_group_id: toGroupId, - }); - - const result = await DatabaseEventMoveGroup(payload); - - return result.unwrap(); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/group/group_types.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/group/group_types.ts deleted file mode 100644 index b75ecc0bd4..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/group/group_types.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { GroupPB, GroupSettingPB } from '@/services/backend'; -import { pbToRowMeta, RowMeta } from '../row'; - -export interface GroupSetting { - id: string; - fieldId: string; -} - -export interface Group { - id: string; - isDefault: boolean; - isVisible: boolean; - fieldId: string; - rows: RowMeta[]; -} - -export function pbToGroup(pb: GroupPB): Group { - return { - id: pb.group_id, - isDefault: pb.is_default, - isVisible: pb.is_visible, - fieldId: pb.field_id, - rows: pb.rows.map(pbToRowMeta), - }; -} - -export function pbToGroupSetting(pb: GroupSettingPB): GroupSetting { - return { - id: pb.id, - fieldId: pb.field_id, - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/group/index.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/group/index.ts deleted file mode 100644 index bb872d6677..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/group/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './group_types'; -export * as groupService from './group_service'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/index.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/index.ts deleted file mode 100644 index f44da5b857..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export * from './cell'; -export * from './database'; -export * from './database_view'; -export * from './field'; -export * from './filter'; -export * from './group'; -export * from './row'; -export * from './sort'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/row/index.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/row/index.ts deleted file mode 100644 index 69260223ef..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/row/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './row_types'; -export * as rowService from './row_service'; -export * as rowListeners from './row_listeners'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/row/row_listeners.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/row/row_listeners.ts deleted file mode 100644 index e8a638403e..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/row/row_listeners.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { ReorderAllRowsPB, ReorderSingleRowPB, RowsChangePB, RowsVisibilityChangePB } from '@/services/backend'; -import { Database } from '../database'; -import { pbToRowMeta, RowMeta } from './row_types'; -import { didDeleteCells } from '$app/application/database/cell/cell_listeners'; -import { getDatabase } from '$app/application/database/database/database_service'; - -const deleteRowsFromChangeset = (database: Database, changeset: RowsChangePB) => { - changeset.deleted_rows.forEach((rowId) => { - const index = database.rowMetas.findIndex((row) => row.id === rowId); - - if (index !== -1) { - database.rowMetas.splice(index, 1); - // delete cells - didDeleteCells({ database, rowId }); - } - }); -}; - -const updateRowsFromChangeset = (database: Database, changeset: RowsChangePB) => { - changeset.updated_rows.forEach(({ row_id: rowId, row_meta: rowMetaPB }) => { - const found = database.rowMetas.find((rowMeta) => rowMeta.id === rowId); - - if (found) { - Object.assign(found, rowMetaPB ? pbToRowMeta(rowMetaPB) : {}); - } - }); -}; - -export const didUpdateViewRows = async (viewId: string, database: Database, changeset: RowsChangePB) => { - if (changeset.inserted_rows.length > 0) { - const { rowMetas } = await getDatabase(viewId); - - database.rowMetas = rowMetas; - return; - } - - deleteRowsFromChangeset(database, changeset); - updateRowsFromChangeset(database, changeset); -}; - -export const didReorderRows = (database: Database, changeset: ReorderAllRowsPB) => { - const rowById = database.rowMetas.reduce>((prev, cur) => { - prev[cur.id] = cur; - return prev; - }, {}); - - database.rowMetas = changeset.row_orders.map((rowId) => rowById[rowId]); -}; - -export const didReorderSingleRow = (database: Database, changeset: ReorderSingleRowPB) => { - const { row_id: rowId, new_index: newIndex } = changeset; - - const oldIndex = database.rowMetas.findIndex((rowMeta) => rowMeta.id === rowId); - - if (oldIndex !== -1) { - database.rowMetas.splice(newIndex, 0, database.rowMetas.splice(oldIndex, 1)[0]); - } -}; - -export const didUpdateViewRowsVisibility = async ( - viewId: string, - database: Database, - changeset: RowsVisibilityChangePB -) => { - const { invisible_rows, visible_rows } = changeset; - - let reFetchRows = false; - - for (const rowId of invisible_rows) { - const rowMeta = database.rowMetas.find((rowMeta) => rowMeta.id === rowId); - - if (rowMeta) { - rowMeta.isHidden = true; - } - } - - for (const insertedRow of visible_rows) { - const rowMeta = database.rowMetas.find((rowMeta) => rowMeta.id === insertedRow.row_meta.id); - - if (rowMeta) { - rowMeta.isHidden = false; - } else { - reFetchRows = true; - break; - } - } - - if (reFetchRows) { - const { rowMetas } = await getDatabase(viewId); - - database.rowMetas = rowMetas; - - await didUpdateViewRowsVisibility(viewId, database, changeset); - } -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/row/row_service.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/row/row_service.ts deleted file mode 100644 index 7993f709b7..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/row/row_service.ts +++ /dev/null @@ -1,129 +0,0 @@ -import { - CreateRowPayloadPB, - MoveGroupRowPayloadPB, - MoveRowPayloadPB, - OrderObjectPositionTypePB, - RepeatedRowIdPB, - RowIdPB, - UpdateRowMetaChangesetPB, -} from '@/services/backend'; -import { - DatabaseEventCreateRow, - DatabaseEventDeleteRows, - DatabaseEventDuplicateRow, - DatabaseEventGetRowMeta, - DatabaseEventMoveGroupRow, - DatabaseEventMoveRow, - DatabaseEventUpdateRowMeta, -} from '@/services/backend/events/flowy-database2'; -import { pbToRowMeta, RowMeta } from './row_types'; - -export async function createRow(viewId: string, params?: { - position?: OrderObjectPositionTypePB; - rowId?: string; - groupId?: string; - data?: Record; -}): Promise { - const payload = CreateRowPayloadPB.fromObject({ - view_id: viewId, - row_position: { - position: params?.position, - object_id: params?.rowId, - }, - group_id: params?.groupId, - data: params?.data, - }); - - const result = await DatabaseEventCreateRow(payload); - - return result.map(pbToRowMeta).unwrap(); -} - -export async function duplicateRow(viewId: string, rowId: string, groupId?: string): Promise { - const payload = RowIdPB.fromObject({ - view_id: viewId, - row_id: rowId, - group_id: groupId, - }); - - const result = await DatabaseEventDuplicateRow(payload); - - return result.unwrap(); -} - -export async function deleteRow(viewId: string, rowId: string, groupId?: string): Promise { - const payload = RepeatedRowIdPB.fromObject({ - view_id: viewId, - row_ids: [rowId], - }); - - const result = await DatabaseEventDeleteRows(payload); - - return result.unwrap(); -} - -export async function moveRow(viewId: string, fromRowId: string, toRowId: string): Promise { - const payload = MoveRowPayloadPB.fromObject({ - view_id: viewId, - from_row_id: fromRowId, - to_row_id: toRowId, - }); - - const result = await DatabaseEventMoveRow(payload); - - return result.unwrap(); -} - -/** - * Move the row from one group to another group - * - * @param fromRowId - * @param toGroupId - * @param toRowId used to locate the moving row location. - * @returns - */ -export async function moveGroupRow(viewId: string, fromRowId: string, toGroupId: string, toRowId?: string): Promise { - const payload = MoveGroupRowPayloadPB.fromObject({ - view_id: viewId, - from_row_id: fromRowId, - to_group_id: toGroupId, - to_row_id: toRowId, - }); - - const result = await DatabaseEventMoveGroupRow(payload); - - return result.unwrap(); -} - - -export async function getRowMeta(viewId: string, rowId: string, groupId?: string): Promise { - const payload = RowIdPB.fromObject({ - view_id: viewId, - row_id: rowId, - group_id: groupId, - }); - - const result = await DatabaseEventGetRowMeta(payload); - - return result.map(pbToRowMeta).unwrap(); -} - -export async function updateRowMeta( - viewId: string, - rowId: string, - meta: { - iconUrl?: string; - coverUrl?: string; - }, -): Promise { - const payload = UpdateRowMetaChangesetPB.fromObject({ - view_id: viewId, - id: rowId, - icon_url: meta.iconUrl, - cover_url: meta.coverUrl, - }); - - const result = await DatabaseEventUpdateRowMeta(payload); - - return result.unwrap(); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/row/row_types.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/row/row_types.ts deleted file mode 100644 index 1b964a6bb5..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/row/row_types.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { RowMetaPB } from '@/services/backend'; - -export interface RowMeta { - id: string; - documentId?: string; - icon?: string; - cover?: string; - isHidden?: boolean; -} - -export function pbToRowMeta(pb: RowMetaPB): RowMeta { - const rowMeta: RowMeta = { - id: pb.id, - }; - - if (pb.document_id) { - rowMeta.documentId = pb.document_id; - } - - if (pb.icon) { - rowMeta.icon = pb.icon; - } - - if (pb.cover) { - rowMeta.cover = pb.cover; - } - - return rowMeta; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/sort/index.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/sort/index.ts deleted file mode 100644 index 6c7d4bd60a..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/sort/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './sort_types'; -export * as sortService from './sort_service'; -export * as sortListeners from './sort_listeners'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/sort/sort_listeners.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/sort/sort_listeners.ts deleted file mode 100644 index 808c62e0d2..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/sort/sort_listeners.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { SortChangesetNotificationPB } from '@/services/backend'; -import { Database } from '../database'; -import { pbToSort } from './sort_types'; - -const deleteSortsFromChange = (database: Database, changeset: SortChangesetNotificationPB) => { - const deleteIds = changeset.delete_sorts.map(sort => sort.id); - - if (deleteIds.length) { - database.sorts = database.sorts.filter(sort => !deleteIds.includes(sort.id)); - } -}; - -const insertSortsFromChange = (database: Database, changeset: SortChangesetNotificationPB) => { - changeset.insert_sorts.forEach(sortPB => { - database.sorts.push(pbToSort(sortPB.sort)); - }); -}; - -const updateSortsFromChange = (database: Database, changeset: SortChangesetNotificationPB) => { - changeset.update_sorts.forEach(sortPB => { - const found = database.sorts.find(sort => sort.id === sortPB.id); - - if (found) { - const newSort = pbToSort(sortPB); - - Object.assign(found, newSort); - } - }); -}; - -export const didUpdateSort = (database: Database, changeset: SortChangesetNotificationPB) => { - deleteSortsFromChange(database, changeset); - insertSortsFromChange(database, changeset); - updateSortsFromChange(database, changeset); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/sort/sort_service.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/sort/sort_service.ts deleted file mode 100644 index 2546ec780c..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/sort/sort_service.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { - DatabaseViewIdPB, - DatabaseSettingChangesetPB, -} from '@/services/backend'; -import { - DatabaseEventDeleteAllSorts, - DatabaseEventGetAllSorts, DatabaseEventUpdateDatabaseSetting, -} from '@/services/backend/events/flowy-database2'; -import { pbToSort, Sort } from './sort_types'; - -export async function getAllSorts(viewId: string): Promise { - const payload = DatabaseViewIdPB.fromObject({ - value: viewId, - }); - - const result = await DatabaseEventGetAllSorts(payload); - - return result.map(value => value.items.map(pbToSort)).unwrap(); -} - -export async function insertSort(viewId: string, sort: Omit): Promise { - const payload = DatabaseSettingChangesetPB.fromObject({ - view_id: viewId, - update_sort: { - view_id: viewId, - field_id: sort.fieldId, - condition: sort.condition, - }, - }); - - const result = await DatabaseEventUpdateDatabaseSetting(payload); - - return result.unwrap(); -} - -export async function updateSort(viewId: string, sort: Sort): Promise { - const payload = DatabaseSettingChangesetPB.fromObject({ - view_id: viewId, - update_sort: { - view_id: viewId, - sort_id: sort.id, - field_id: sort.fieldId, - condition: sort.condition, - }, - }); - - const result = await DatabaseEventUpdateDatabaseSetting(payload); - - return result.unwrap(); -} - -export async function deleteSort(viewId: string, sort: Sort): Promise { - const payload = DatabaseSettingChangesetPB.fromObject({ - view_id: viewId, - delete_sort: { - view_id: viewId, - sort_id: sort.id, - }, - }); - - const result = await DatabaseEventUpdateDatabaseSetting(payload); - - return result.unwrap(); -} - -export async function deleteAllSorts(viewId: string): Promise { - const payload = DatabaseViewIdPB.fromObject({ - value: viewId, - }); - const result = await DatabaseEventDeleteAllSorts(payload); - - return result.unwrap(); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/sort/sort_types.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/sort/sort_types.ts deleted file mode 100644 index a8089878d1..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/sort/sort_types.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { SortConditionPB, SortPB } from '@/services/backend'; - -export interface Sort { - id: string; - fieldId: string; - condition: SortConditionPB; -} - -export function pbToSort(pb: SortPB): Sort { - return { - id: pb.id, - fieldId: pb.field_id, - condition: pb.condition, - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/document/document.service.ts b/frontend/appflowy_tauri/src/appflowy_app/application/document/document.service.ts deleted file mode 100644 index 0db128ec7a..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/document/document.service.ts +++ /dev/null @@ -1,284 +0,0 @@ -import { - ApplyActionPayloadPB, - BlockActionPB, - BlockPB, - CloseDocumentPayloadPB, - ConvertDataToJsonPayloadPB, - ConvertDocumentPayloadPB, - InputType, - OpenDocumentPayloadPB, - TextDeltaPayloadPB, -} from '@/services/backend'; -import { - DocumentEventApplyAction, - DocumentEventApplyTextDeltaEvent, - DocumentEventCloseDocument, - DocumentEventConvertDataToJSON, - DocumentEventConvertDocument, - DocumentEventOpenDocument, -} from '@/services/backend/events/flowy-document'; -import get from 'lodash-es/get'; -import { EditorData, EditorNodeType } from '$app/application/document/document.types'; -import { Log } from '$app/utils/log'; -import { Op } from 'quill-delta'; -import { Element, Text } from 'slate'; -import { generateId, getInlinesWithDelta } from '$app/components/editor/provider/utils/convert'; -import { CustomEditor } from '$app/components/editor/command'; -import { LIST_TYPES } from '$app/components/editor/command/tab'; - -export function blockPB2Node(block: BlockPB) { - let data = {}; - - try { - data = JSON.parse(block.data); - } catch { - Log.error('[Document Open] json parse error', block.data); - } - - return { - id: block.id, - type: block.ty as EditorNodeType, - parent: block.parent_id, - children: block.children_id, - data, - externalId: block.external_id, - externalType: block.external_type, - }; -} - -export const BLOCK_MAP_NAME = 'blocks'; -export const META_NAME = 'meta'; -export const CHILDREN_MAP_NAME = 'children_map'; - -export const TEXT_MAP_NAME = 'text_map'; -export async function openDocument(docId: string): Promise { - const payload = OpenDocumentPayloadPB.fromObject({ - document_id: docId, - }); - - const result = await DocumentEventOpenDocument(payload); - - if (!result.ok) { - return Promise.reject(result.val); - } - - const documentDataPB = result.val; - - if (!documentDataPB) { - return Promise.reject('documentDataPB is null'); - } - - const data: EditorData = { - viewId: docId, - rootId: documentDataPB.page_id, - nodeMap: {}, - childrenMap: {}, - relativeMap: {}, - deltaMap: {}, - externalIdMap: {}, - }; - - get(documentDataPB, BLOCK_MAP_NAME).forEach((block) => { - Object.assign(data.nodeMap, { - [block.id]: blockPB2Node(block), - }); - data.relativeMap[block.children_id] = block.id; - if (block.external_id) { - data.externalIdMap[block.external_id] = block.id; - } - }); - - get(documentDataPB, [META_NAME, CHILDREN_MAP_NAME]).forEach((child, key) => { - const blockId = data.relativeMap[key]; - - data.childrenMap[blockId] = child.children; - }); - - get(documentDataPB, [META_NAME, TEXT_MAP_NAME]).forEach((delta, key) => { - const blockId = data.externalIdMap[key]; - - data.deltaMap[blockId] = delta ? JSON.parse(delta) : []; - }); - - return data; -} - -export async function closeDocument(docId: string) { - const payload = CloseDocumentPayloadPB.fromObject({ - document_id: docId, - }); - - const result = await DocumentEventCloseDocument(payload); - - if (!result.ok) { - return Promise.reject(result.val); - } - - return result.val; -} - -export async function applyActions(docId: string, actions: ReturnType[]) { - if (actions.length === 0) return; - const payload = ApplyActionPayloadPB.fromObject({ - document_id: docId, - actions: actions, - }); - - const result = await DocumentEventApplyAction(payload); - - if (!result.ok) { - return Promise.reject(result.val); - } - - return result.val; -} - -export async function applyText(docId: string, textId: string, delta: string) { - const payload = TextDeltaPayloadPB.fromObject({ - document_id: docId, - text_id: textId, - delta: delta, - }); - - const res = await DocumentEventApplyTextDeltaEvent(payload); - - if (!res.ok) { - return Promise.reject(res.val); - } - - return res.val; -} - -export async function getClipboardData( - docId: string, - range: { - start: { - blockId: string; - index: number; - length: number; - }; - end?: { - blockId: string; - index: number; - length: number; - }; - } -) { - const payload = ConvertDocumentPayloadPB.fromObject({ - range: { - start: { - block_id: range.start.blockId, - index: range.start.index, - length: range.start.length, - }, - end: range.end - ? { - block_id: range.end.blockId, - index: range.end.index, - length: range.end.length, - } - : undefined, - }, - document_id: docId, - parse_types: { - json: true, - html: true, - text: true, - }, - }); - - const result = await DocumentEventConvertDocument(payload); - - if (!result.ok) { - return Promise.reject(result.val); - } - - return { - html: result.val.html, - text: result.val.text, - json: result.val.json, - }; -} - -export async function convertBlockToJson(data: string, type: InputType) { - const payload = ConvertDataToJsonPayloadPB.fromObject({ - data, - input_type: type, - }); - - const result = await DocumentEventConvertDataToJSON(payload); - - if (!result.ok) { - return Promise.reject(result.val); - } - - try { - const block = JSON.parse(result.val.json); - - return flattenBlockJson(block); - } catch (e) { - return Promise.reject(e); - } -} - -interface BlockJSON { - type: string; - children: BlockJSON[]; - data: { - [key: string]: boolean | string | number | undefined; - } & { - delta?: Op[]; - }; -} - -function flattenBlockJson(block: BlockJSON) { - const traverse = (block: BlockJSON) => { - const { delta, ...data } = block.data; - - const slateNode: Element = { - type: block.type, - data: data, - children: [], - blockId: generateId(), - }; - const isEmbed = CustomEditor.isEmbedNode(slateNode); - - const textNode: { - type: EditorNodeType.Text; - children: (Text | Element)[]; - textId: string; - } | null = !isEmbed - ? { - type: EditorNodeType.Text, - children: [{ text: '' }], - textId: generateId(), - } - : null; - - if (delta && textNode) { - textNode.children = getInlinesWithDelta(delta); - } - - slateNode.children = block.children.map((child) => traverse(child)); - - if (textNode) { - const texts = CustomEditor.getNodeTextContent(textNode); - - if (texts && !LIST_TYPES.includes(block.type as EditorNodeType) && slateNode.type !== EditorNodeType.Page) { - slateNode.children.unshift(textNode); - } else if (texts) { - slateNode.children.unshift({ - type: EditorNodeType.Paragraph, - children: [textNode], - blockId: generateId(), - }); - } - } - - return slateNode; - }; - - const root = traverse(block); - - return root.children; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/document/document.types.ts b/frontend/appflowy_tauri/src/appflowy_app/application/document/document.types.ts deleted file mode 100644 index e6eb1d6923..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/document/document.types.ts +++ /dev/null @@ -1,242 +0,0 @@ -import { Op } from 'quill-delta'; -import { HTMLAttributes } from 'react'; -import { Element } from 'slate'; -import { ViewIconTypePB, ViewLayoutPB } from '@/services/backend'; -import { PageCover } from '$app_reducers/pages/slice'; -import * as Y from 'yjs'; - -export interface EditorNode { - id: string; - type: EditorNodeType; - parent?: string | null; - data?: BlockData; - children?: string; - externalId?: string; - externalType?: string; -} - -export interface TextNode extends Element { - type: EditorNodeType.Text; - textId: string; - blockId: string; -} - -export interface PageNode extends Element { - type: EditorNodeType.Page; -} -export interface ParagraphNode extends Element { - type: EditorNodeType.Paragraph; -} - -export type BlockData = { - [key: string]: string | boolean | number | undefined; - font_color?: string; - bg_color?: string; -}; - -export interface HeadingNode extends Element { - blockId: string; - type: EditorNodeType.HeadingBlock; - data: { - level: number; - } & BlockData; -} - -export interface GridNode extends Element { - blockId: string; - type: EditorNodeType.GridBlock; - data: { - viewId?: string; - } & BlockData; -} - -export interface TodoListNode extends Element { - blockId: string; - type: EditorNodeType.TodoListBlock; - data: { - checked: boolean; - } & BlockData; -} - -export interface CodeNode extends Element { - blockId: string; - type: EditorNodeType.CodeBlock; - data: { - language: string; - } & BlockData; -} - -export interface QuoteNode extends Element { - blockId: string; - type: EditorNodeType.QuoteBlock; -} - -export interface NumberedListNode extends Element { - type: EditorNodeType.NumberedListBlock; - blockId: string; - data: { - number?: number; - } & BlockData; -} - -export interface BulletedListNode extends Element { - type: EditorNodeType.BulletedListBlock; - blockId: string; -} - -export interface ToggleListNode extends Element { - type: EditorNodeType.ToggleListBlock; - blockId: string; - data: { - collapsed: boolean; - } & BlockData; -} - -export interface DividerNode extends Element { - type: EditorNodeType.DividerBlock; - blockId: string; -} - -export interface CalloutNode extends Element { - type: EditorNodeType.CalloutBlock; - blockId: string; - data: { - icon: string; - } & BlockData; -} - -export interface MathEquationNode extends Element { - type: EditorNodeType.EquationBlock; - blockId: string; - data: { - formula?: string; - } & BlockData; -} - -export enum ImageType { - Local = 0, - Internal = 1, - External = 2, -} - -export interface ImageNode extends Element { - type: EditorNodeType.ImageBlock; - blockId: string; - data: { - url?: string; - width?: number; - image_type?: ImageType; - height?: number; - } & BlockData; -} - -export interface FormulaNode extends Element { - type: EditorInlineNodeType.Formula; - data: string; -} - -export interface MentionNode extends Element { - type: EditorInlineNodeType.Mention; - data: Mention; -} - -export interface EditorData { - viewId: string; - rootId: string; - // key: block's id, value: block - nodeMap: Record; - // key: block's children id, value: block's id - childrenMap: Record; - // key: block's children id, value: block's id - relativeMap: Record; - // key: block's externalId, value: delta - deltaMap: Record; - // key: block's externalId, value: block's id - externalIdMap: Record; -} - -export interface MentionPage { - id: string; - name: string; - layout: ViewLayoutPB; - parentId: string; - icon?: { - ty: ViewIconTypePB; - value: string; - }; -} - -export interface EditorProps { - title?: string; - cover?: PageCover; - onTitleChange?: (title: string) => void; - onCoverChange?: (cover?: PageCover) => void; - showTitle?: boolean; - id: string; - disableFocus?: boolean; -} - -export interface LocalEditorProps { - disableFocus?: boolean; - sharedType: Y.XmlText; - id: string; - caretColor?: string; -} - -export enum EditorNodeType { - Text = 'text', - Paragraph = 'paragraph', - Page = 'page', - HeadingBlock = 'heading', - TodoListBlock = 'todo_list', - BulletedListBlock = 'bulleted_list', - NumberedListBlock = 'numbered_list', - ToggleListBlock = 'toggle_list', - CodeBlock = 'code', - EquationBlock = 'math_equation', - QuoteBlock = 'quote', - CalloutBlock = 'callout', - DividerBlock = 'divider', - ImageBlock = 'image', - GridBlock = 'grid', -} - -export enum EditorInlineNodeType { - Mention = 'mention', - Formula = 'formula', -} - -export const inlineNodeTypes: (string | EditorInlineNodeType)[] = [ - EditorInlineNodeType.Mention, - EditorInlineNodeType.Formula, -]; - -export interface EditorElementProps extends HTMLAttributes { - node: T; -} - -export enum EditorMarkFormat { - Bold = 'bold', - Italic = 'italic', - Underline = 'underline', - StrikeThrough = 'strikethrough', - Code = 'code', - Href = 'href', - FontColor = 'font_color', - BgColor = 'bg_color', - Align = 'align', -} - -export enum MentionType { - PageRef = 'page', - Date = 'date', -} - -export interface Mention { - // inline page ref id - page_id?: string; - // reminder date ref id - date?: string; - - type: MentionType; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/folder/page.service.ts b/frontend/appflowy_tauri/src/appflowy_app/application/folder/page.service.ts deleted file mode 100644 index 7d988b9866..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/folder/page.service.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { Page, PageIcon, parserViewPBToPage } from '$app_reducers/pages/slice'; -import { - CreateOrphanViewPayloadPB, - CreateViewPayloadPB, - MoveNestedViewPayloadPB, - RepeatedViewIdPB, - UpdateViewIconPayloadPB, - UpdateViewPayloadPB, - ViewIconPB, - ViewIdPB, - ViewPB, -} from '@/services/backend'; -import { - FolderEventCreateOrphanView, - FolderEventCreateView, - FolderEventDeleteView, - FolderEventDuplicateView, - FolderEventGetView, - FolderEventMoveNestedView, - FolderEventUpdateView, - FolderEventUpdateViewIcon, - FolderEventSetLatestView, -} from '@/services/backend/events/flowy-folder'; - -export async function getPage(id: string) { - const payload = new ViewIdPB({ - value: id, - }); - - const result = await FolderEventGetView(payload); - - if (result.ok) { - return parserViewPBToPage(result.val); - } - - return Promise.reject(result.val); -} - -export const createOrphanPage = async ( - params: ReturnType -): Promise => { - const payload = CreateOrphanViewPayloadPB.fromObject(params); - - const result = await FolderEventCreateOrphanView(payload); - - if (result.ok) { - return parserViewPBToPage(result.val); - } - - return Promise.reject(result.val); -}; - -export const duplicatePage = async (page: Page) => { - const payload = ViewPB.fromObject(page); - - const result = await FolderEventDuplicateView(payload); - - if (result.ok) { - return result.val; - } - - return Promise.reject(result.err); -}; - -export const deletePage = async (id: string) => { - const payload = new RepeatedViewIdPB({ - items: [id], - }); - - const result = await FolderEventDeleteView(payload); - - if (result.ok) { - return result.val; - } - - return Promise.reject(result.err); -}; - -export const createPage = async (params: ReturnType): Promise => { - const payload = CreateViewPayloadPB.fromObject(params); - - const result = await FolderEventCreateView(payload); - - if (result.ok) { - return result.val.id; - } - - return Promise.reject(result.err); -}; - -export const movePage = async (params: ReturnType) => { - const payload = new MoveNestedViewPayloadPB(params); - - const result = await FolderEventMoveNestedView(payload); - - if (result.ok) { - return result.val; - } - - return Promise.reject(result.err); -}; - -export const getChildPages = async (id: string): Promise => { - const payload = new ViewIdPB({ - value: id, - }); - - const result = await FolderEventGetView(payload); - - if (result.ok) { - return result.val.child_views.map(parserViewPBToPage); - } - - return []; -}; - -export const updatePage = async (page: { id: string } & Partial) => { - const payload = new UpdateViewPayloadPB(); - - payload.view_id = page.id; - if (page.name !== undefined) { - payload.name = page.name; - } - - const result = await FolderEventUpdateView(payload); - - if (result.ok) { - return result.val.toObject(); - } - - return Promise.reject(result.err); -}; - -export const updatePageIcon = async (viewId: string, icon?: PageIcon) => { - const payload = new UpdateViewIconPayloadPB({ - view_id: viewId, - icon: icon - ? new ViewIconPB({ - ty: icon.ty, - value: icon.value, - }) - : undefined, - }); - - const result = await FolderEventUpdateViewIcon(payload); - - if (result.ok) { - return result.val; - } - - return Promise.reject(result.err); -}; - -export async function setLatestOpenedPage(id: string) { - const payload = new ViewIdPB({ - value: id, - }); - - const res = await FolderEventSetLatestView(payload); - - if (res.ok) { - return res.val; - } - - return Promise.reject(res.err); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/folder/trash.service.ts b/frontend/appflowy_tauri/src/appflowy_app/application/folder/trash.service.ts deleted file mode 100644 index dfbe742ca0..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/folder/trash.service.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { - FolderEventListTrashItems, - FolderEventPermanentlyDeleteAllTrashItem, - FolderEventPermanentlyDeleteTrashItem, - FolderEventRecoverAllTrashItems, - FolderEventRestoreTrashItem, - RepeatedTrashIdPB, - TrashIdPB, -} from '@/services/backend/events/flowy-folder'; - -export const getTrash = async () => { - const res = await FolderEventListTrashItems(); - - if (res.ok) { - return res.val.items; - } - - return []; -}; - -export const putback = async (id: string) => { - const payload = new TrashIdPB({ - id, - }); - - const res = await FolderEventRestoreTrashItem(payload); - - if (res.ok) { - return res.val; - } - - return Promise.reject(res.err); -}; - -export const deleteTrashItem = async (ids: string[]) => { - const items = ids.map((id) => new TrashIdPB({ id })); - const payload = new RepeatedTrashIdPB({ - items, - }); - - const res = await FolderEventPermanentlyDeleteTrashItem(payload); - - if (res.ok) { - return res.val; - } - - return Promise.reject(res.err); -}; - -export const deleteAll = async () => { - const res = await FolderEventPermanentlyDeleteAllTrashItem(); - - if (res.ok) { - return res.val; - } - - return Promise.reject(res.err); -}; - -export const restoreAll = async () => { - const res = await FolderEventRecoverAllTrashItems(); - - if (res.ok) { - return res.val; - } - - return Promise.reject(res.err); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/folder/workspace.service.ts b/frontend/appflowy_tauri/src/appflowy_app/application/folder/workspace.service.ts deleted file mode 100644 index fe066b7377..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/folder/workspace.service.ts +++ /dev/null @@ -1,141 +0,0 @@ -import { parserViewPBToPage } from '$app_reducers/pages/slice'; -import { - ChangeWorkspaceIconPB, - CreateViewPayloadPB, - GetWorkspaceViewPB, - RenameWorkspacePB, - UserWorkspaceIdPB, - WorkspaceIdPB, -} from '@/services/backend'; -import { - FolderEventCreateView, - FolderEventDeleteWorkspace, - FolderEventGetCurrentWorkspaceSetting, - FolderEventReadCurrentWorkspace, - FolderEventReadWorkspaceViews, -} from '@/services/backend/events/flowy-folder'; -import { - UserEventChangeWorkspaceIcon, - UserEventGetAllWorkspace, - UserEventOpenWorkspace, - UserEventRenameWorkspace, -} from '@/services/backend/events/flowy-user'; - -export async function openWorkspace(id: string) { - const payload = new UserWorkspaceIdPB({ - workspace_id: id, - }); - - const result = await UserEventOpenWorkspace(payload); - - if (result.ok) { - return result.val; - } - - return Promise.reject(result.err); -} - -export async function deleteWorkspace(id: string) { - const payload = new WorkspaceIdPB({ - value: id, - }); - - const result = await FolderEventDeleteWorkspace(payload); - - if (result.ok) { - return result.val; - } - - return Promise.reject(result.err); -} - -export async function getWorkspaceChildViews(id: string) { - const payload = new GetWorkspaceViewPB({ - value: id, - }); - - const result = await FolderEventReadWorkspaceViews(payload); - - if (result.ok) { - return result.val.items.map(parserViewPBToPage); - } - - return []; -} - -export async function getWorkspaces() { - const result = await UserEventGetAllWorkspace(); - - if (result.ok) { - return result.val.items.map((workspace) => ({ - id: workspace.workspace_id, - name: workspace.name, - })); - } - - return []; -} - -export async function getCurrentWorkspaceSetting() { - const res = await FolderEventGetCurrentWorkspaceSetting(); - - if (res.ok) { - return res.val; - } - - return; -} - -export async function getCurrentWorkspace() { - const result = await FolderEventReadCurrentWorkspace(); - - if (result.ok) { - return result.val.id; - } - - return null; -} - -export async function createCurrentWorkspaceChildView( - params: ReturnType -) { - const payload = CreateViewPayloadPB.fromObject(params); - - const result = await FolderEventCreateView(payload); - - if (result.ok) { - return result.val; - } - - return Promise.reject(result.err); -} - -export async function renameWorkspace(id: string, name: string) { - const payload = new RenameWorkspacePB({ - workspace_id: id, - new_name: name, - }); - - const result = await UserEventRenameWorkspace(payload); - - if (result.ok) { - return result.val; - } - - return Promise.reject(result.err); -} - -export async function changeWorkspaceIcon(id: string, icon: string) { - const payload = new ChangeWorkspaceIconPB({ - workspace_id: id, - new_icon: icon, - }); - - const result = await UserEventChangeWorkspaceIcon(payload); - - if (result.ok) { - return result.val; - } - - return Promise.reject(result.err); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/notification.ts b/frontend/appflowy_tauri/src/appflowy_app/application/notification.ts deleted file mode 100644 index c63a5d9823..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/notification.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { listen } from '@tauri-apps/api/event'; -import { SubscribeObject } from '@/services/backend/models/flowy-notification'; -import { - DatabaseFieldChangesetPB, - DatabaseNotification, - DocEventPB, - DocumentNotification, - FieldPB, - FieldSettingsPB, - FilterChangesetNotificationPB, - GroupChangesPB, - GroupRowsNotificationPB, - ReorderAllRowsPB, - ReorderSingleRowPB, - RowsChangePB, - RowsVisibilityChangePB, - SortChangesetNotificationPB, - UserNotification, - UserProfilePB, - FolderNotification, - RepeatedViewPB, - ViewPB, - RepeatedTrashPB, - ChildViewUpdatePB, - WorkspacePB, -} from '@/services/backend'; -import { AsyncQueue } from '$app/utils/async_queue'; - -const Notification = { - [DatabaseNotification.DidUpdateViewRowsVisibility]: RowsVisibilityChangePB, - [DatabaseNotification.DidUpdateViewRows]: RowsChangePB, - [DatabaseNotification.DidReorderRows]: ReorderAllRowsPB, - [DatabaseNotification.DidReorderSingleRow]: ReorderSingleRowPB, - [DatabaseNotification.DidUpdateFields]: DatabaseFieldChangesetPB, - [DatabaseNotification.DidGroupByField]: GroupChangesPB, - [DatabaseNotification.DidUpdateNumOfGroups]: GroupChangesPB, - [DatabaseNotification.DidUpdateGroupRow]: GroupRowsNotificationPB, - [DatabaseNotification.DidUpdateField]: FieldPB, - [DatabaseNotification.DidUpdateCell]: null, - [DatabaseNotification.DidUpdateSort]: SortChangesetNotificationPB, - [DatabaseNotification.DidUpdateFieldSettings]: FieldSettingsPB, - [DatabaseNotification.DidUpdateFilter]: FilterChangesetNotificationPB, - [DocumentNotification.DidReceiveUpdate]: DocEventPB, - [FolderNotification.DidUpdateWorkspace]: WorkspacePB, - [FolderNotification.DidUpdateWorkspaceViews]: RepeatedViewPB, - [FolderNotification.DidUpdateView]: ViewPB, - [FolderNotification.DidUpdateChildViews]: ChildViewUpdatePB, - [FolderNotification.DidUpdateTrash]: RepeatedTrashPB, - [UserNotification.DidUpdateUserProfile]: UserProfilePB, -}; - -type NotificationMap = typeof Notification; -export type NotificationEnum = keyof NotificationMap; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -type NullableInstanceType any) | null> = K extends abstract new ( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - ...args: any -) => // eslint-disable-next-line @typescript-eslint/no-explicit-any -any - ? InstanceType - : void; -export type NotificationHandler = ( - result: NullableInstanceType -) => void | Promise; - -/** - * Subscribes to a set of notifications. - * - * This function subscribes to notifications defined by the `NotificationEnum` and - * calls the appropriate `NotificationHandler` when each type of notification is received. - * - * @param {Object} callbacks - An object containing handlers for various notification types. - * Each key is a `NotificationEnum` value, and the corresponding value is a `NotificationHandler` function. - * - * @param {Object} [options] - Optional settings for the subscription. - * @param {string} [options.id] - An optional ID. If provided, only notifications with a matching ID will be processed. - * - * @returns {Promise<() => void>} A Promise that resolves to an unsubscribe function. - * - * @example - * subscribeNotifications({ - * [DatabaseNotification.DidUpdateField]: (result) => { - * if (result.err) { - * // process error - * return; - * } - * - * console.log(result.val); // result.val is FieldPB - * }, - * [DatabaseNotification.DidReorderRows]: (result) => { - * if (result.err) { - * // process error - * return; - * } - * - * console.log(result.val); // result.val is ReorderAllRowsPB - * }, - * }, { id: '123' }) - * .then(unsubscribe => { - * // Do something - * // ... - * // To unsubscribe, call `unsubscribe()` - * }); - * - * @throws {Error} Throws an error if unable to subscribe. - */ -export function subscribeNotifications( - callbacks: { - [K in NotificationEnum]?: NotificationHandler; - }, - options?: { id?: string | number } -): Promise<() => void> { - const handler = async (subject: SubscribeObject) => { - const { id, ty } = subject; - - if (options?.id !== undefined && id !== options.id) { - return; - } - - const notification = ty as NotificationEnum; - const pb = Notification[notification]; - const callback = callbacks[notification] as NotificationHandler; - - if (pb === undefined || !callback) { - return; - } - - if (subject.has_error) { - // const error = FlowyError.deserialize(subject.error); - return; - } else { - const { payload } = subject; - - if (pb) { - await callback(pb.deserialize(payload)); - } else { - await callback(); - } - } - }; - - const queue = new AsyncQueue(handler); - - return listen>('af-notification', (event) => { - const subject = SubscribeObject.fromObject(event.payload); - - queue.enqueue(subject); - }); -} - -export function subscribeNotification( - notification: K, - callback: NotificationHandler, - options?: { id?: string } -): Promise<() => void> { - return subscribeNotifications({ [notification]: callback }, options); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/user/auth.service.ts b/frontend/appflowy_tauri/src/appflowy_app/application/user/auth.service.ts deleted file mode 100644 index ec258abc87..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/user/auth.service.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { - SignUpPayloadPB, - OauthProviderPB, - ProviderTypePB, - OauthSignInPB, - AuthenticatorPB, - SignInPayloadPB, -} from '@/services/backend'; -import { - UserEventSignOut, - UserEventSignUp, - UserEventGetOauthURLWithProvider, - UserEventOauthSignIn, - UserEventSignInWithEmailPassword, -} from '@/services/backend/events/flowy-user'; -import { Log } from '$app/utils/log'; - -export const AuthService = { - getOAuthURL: async (provider: ProviderTypePB) => { - const providerDataRes = await UserEventGetOauthURLWithProvider( - OauthProviderPB.fromObject({ - provider, - }) - ); - - if (!providerDataRes.ok) { - Log.error(providerDataRes.val.msg); - throw new Error(providerDataRes.val.msg); - } - - const providerData = providerDataRes.val; - - return providerData.oauth_url; - }, - - signInWithOAuth: async ({ uri, deviceId }: { uri: string; deviceId: string }) => { - const payload = OauthSignInPB.fromObject({ - authenticator: AuthenticatorPB.AppFlowyCloud, - map: { - sign_in_url: uri, - device_id: deviceId, - }, - }); - - const res = await UserEventOauthSignIn(payload); - - if (!res.ok) { - Log.error(res.val.msg); - throw new Error(res.val.msg); - } - - return res.val; - }, - - signUp: async (params: { deviceId: string; name: string; email: string; password: string }) => { - const payload = SignUpPayloadPB.fromObject({ - name: params.name, - email: params.email, - password: params.password, - device_id: params.deviceId, - }); - - const res = await UserEventSignUp(payload); - - if (!res.ok) { - Log.error(res.val.msg); - throw new Error(res.val.msg); - } - - return res.val; - }, - - signOut: () => { - return UserEventSignOut(); - }, - - signIn: async (email: string, password: string) => { - const payload = SignInPayloadPB.fromObject({ - email, - password, - }); - - const res = await UserEventSignInWithEmailPassword(payload); - - if (!res.ok) { - Log.error(res.val.msg); - throw new Error(res.val.msg); - } - - return res.val; - }, -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/user/user.service.ts b/frontend/appflowy_tauri/src/appflowy_app/application/user/user.service.ts deleted file mode 100644 index ec64fb810c..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/application/user/user.service.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { Theme, ThemeMode, UserSetting } from '$app_reducers/current-user/slice'; -import { AppearanceSettingsPB, UpdateUserProfilePayloadPB } from '@/services/backend'; -import { - UserEventGetAppearanceSetting, - UserEventGetUserProfile, - UserEventSetAppearanceSetting, - UserEventUpdateUserProfile, -} from '@/services/backend/events/flowy-user'; - -export const UserService = { - getAppearanceSetting: async (): Promise | undefined> => { - const appearanceSetting = await UserEventGetAppearanceSetting(); - - if (appearanceSetting.ok) { - const res = appearanceSetting.val; - const { locale, theme = Theme.Default, theme_mode = ThemeMode.Light } = res; - let language = 'en'; - - if (locale.language_code && locale.country_code) { - language = `${locale.language_code}-${locale.country_code}`; - } else if (locale.language_code) { - language = locale.language_code; - } - - return { - themeMode: theme_mode, - theme: theme as Theme, - language: language, - }; - } - - return; - }, - - setAppearanceSetting: async (params: ReturnType) => { - const payload = AppearanceSettingsPB.fromObject(params); - - const res = await UserEventSetAppearanceSetting(payload); - - if (res.ok) { - return res.val; - } - - return Promise.reject(res.err); - }, - - getUserProfile: async () => { - const res = await UserEventGetUserProfile(); - - if (res.ok) { - return res.val; - } - - return; - }, - - updateUserProfile: async (params: ReturnType) => { - const payload = UpdateUserProfilePayloadPB.fromObject(params); - - const res = await UserEventUpdateUserProfile(payload); - - if (res.ok) { - return res.val; - } - - return Promise.reject(res.err); - }, -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/add.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/add.svg deleted file mode 100644 index 049be05cec..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/add.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/align-center.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/align-center.svg deleted file mode 100644 index f4f4999514..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/align-center.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/align-left.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/align-left.svg deleted file mode 100644 index 23957285c7..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/align-left.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/align-right.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/align-right.svg deleted file mode 100644 index bca2d14fc7..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/align-right.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/arrow-left.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/arrow-left.svg deleted file mode 100644 index e4ab9068be..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/arrow-left.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/arrow-right.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/arrow-right.svg deleted file mode 100644 index dc40ae52a6..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/arrow-right.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/board.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/board.svg deleted file mode 100644 index 0bb0e3fabe..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/board.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/bold.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/bold.svg deleted file mode 100644 index 878b6329b3..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/bold.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/close.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/close.svg deleted file mode 100644 index b519b419c0..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/close.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/copy.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/copy.svg deleted file mode 100644 index e21e6cb082..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/copy.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/dark-logo.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/dark-logo.svg deleted file mode 100644 index 80d8c4132e..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/dark-logo.svg +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/database/checkbox-check.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/database/checkbox-check.svg deleted file mode 100644 index 15632e4ea6..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/database/checkbox-check.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/database/checkbox-uncheck.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/database/checkbox-uncheck.svg deleted file mode 100644 index 6c487795c6..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/database/checkbox-uncheck.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-attach.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-attach.svg deleted file mode 100644 index f00f5c7aa2..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-attach.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-checkbox.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-checkbox.svg deleted file mode 100644 index 37f52c47ed..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-checkbox.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-checklist.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-checklist.svg deleted file mode 100644 index 3a88d236a1..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-checklist.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-date.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-date.svg deleted file mode 100644 index 78243f1e75..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-date.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-last-edited-time.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-last-edited-time.svg deleted file mode 100644 index 634af3e361..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-last-edited-time.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-multi-select.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-multi-select.svg deleted file mode 100644 index 97a2e9c434..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-multi-select.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-number.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-number.svg deleted file mode 100644 index 9d8b98d10d..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-number.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-person.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-person.svg deleted file mode 100644 index 2fc04be065..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-person.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-relation.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-relation.svg deleted file mode 100644 index f82a41d226..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-relation.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-single-select.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-single-select.svg deleted file mode 100644 index 8ccbc9a2e3..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-single-select.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-text.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-text.svg deleted file mode 100644 index 7befa5080f..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-text.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-url.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-url.svg deleted file mode 100644 index f00f5c7aa2..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/database/field-type-url.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/date.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/date.svg deleted file mode 100644 index 78243f1e75..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/date.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/delete.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/delete.svg deleted file mode 100644 index 9e51636798..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/delete.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/details.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/details.svg deleted file mode 100644 index 22c6830916..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/details.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/document.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/document.svg deleted file mode 100644 index b00e1cfb38..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/document.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/drag.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/drag.svg deleted file mode 100644 index 627c959f9f..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/drag.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/dropdown.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/dropdown.svg deleted file mode 100644 index 95e4964b53..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/dropdown.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/edit.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/edit.svg deleted file mode 100644 index ae93287114..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/edit.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/eye_close.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/eye_close.svg deleted file mode 100644 index 116c715ca8..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/eye_close.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/eye_open.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/eye_open.svg deleted file mode 100644 index fa3017c04d..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/eye_open.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/grid.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/grid.svg deleted file mode 100644 index c397af8130..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/grid.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/h1.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/h1.svg deleted file mode 100644 index b33bd52135..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/h1.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/h2.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/h2.svg deleted file mode 100644 index 7449c57391..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/h2.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/h3.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/h3.svg deleted file mode 100644 index 0976945974..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/h3.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/hide-menu.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/hide-menu.svg deleted file mode 100644 index ce88af8ea7..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/hide-menu.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/hide.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/hide.svg deleted file mode 100644 index 22001ef65d..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/hide.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/image.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/image.svg deleted file mode 100644 index 0739605066..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/image.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/images/default_cover.jpg b/frontend/appflowy_tauri/src/appflowy_app/assets/images/default_cover.jpg deleted file mode 100644 index aeaa6a0f29..0000000000 Binary files a/frontend/appflowy_tauri/src/appflowy_app/assets/images/default_cover.jpg and /dev/null differ diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/information.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/information.svg deleted file mode 100644 index 37ca4d5837..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/information.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/inline-code.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/inline-code.svg deleted file mode 100644 index 3585603096..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/inline-code.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/italic.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/italic.svg deleted file mode 100644 index b295c230f0..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/italic.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/left.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/left.svg deleted file mode 100644 index 0f771a3858..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/left.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/light-logo.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/light-logo.svg deleted file mode 100644 index f5cd761ba7..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/light-logo.svg +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/link.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/link.svg deleted file mode 100644 index 5fbcc8d787..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/link.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/list-dropdown.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/list-dropdown.svg deleted file mode 100644 index 4a8424c5f8..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/list-dropdown.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/list.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/list.svg deleted file mode 100644 index 97a2e9c434..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/list.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/logo.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/logo.svg deleted file mode 100644 index b1ac8d66fb..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/logo.svg +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/mention.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/mention.svg deleted file mode 100644 index b98318132c..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/mention.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/more.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/more.svg deleted file mode 100644 index b191e64a10..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/more.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/numbers.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/numbers.svg deleted file mode 100644 index 9d8b98d10d..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/numbers.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/open.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/open.svg deleted file mode 100644 index b443c8b993..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/open.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/quote.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/quote.svg deleted file mode 100644 index 57839231ff..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/quote.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/react.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/react.svg deleted file mode 100644 index 6c87de9bb3..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/right.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/right.svg deleted file mode 100644 index 7d738f4e69..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/right.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/search.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/search.svg deleted file mode 100644 index a8a92df509..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/search.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/select-check.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/select-check.svg deleted file mode 100644 index 05caec861a..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/select-check.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/settings.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/settings.svg deleted file mode 100644 index 92140a3c23..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/settings.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/settings/account.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/settings/account.svg deleted file mode 100644 index fddfca7575..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/settings/account.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/settings/check_circle.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/settings/check_circle.svg deleted file mode 100644 index c6fa56067b..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/settings/check_circle.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/settings/dark.png b/frontend/appflowy_tauri/src/appflowy_app/assets/settings/dark.png deleted file mode 100644 index 15a2db5eb8..0000000000 Binary files a/frontend/appflowy_tauri/src/appflowy_app/assets/settings/dark.png and /dev/null differ diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/settings/discord.png b/frontend/appflowy_tauri/src/appflowy_app/assets/settings/discord.png deleted file mode 100644 index f71e68c6ed..0000000000 Binary files a/frontend/appflowy_tauri/src/appflowy_app/assets/settings/discord.png and /dev/null differ diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/settings/github.png b/frontend/appflowy_tauri/src/appflowy_app/assets/settings/github.png deleted file mode 100644 index 597883b7a3..0000000000 Binary files a/frontend/appflowy_tauri/src/appflowy_app/assets/settings/github.png and /dev/null differ diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/settings/google.png b/frontend/appflowy_tauri/src/appflowy_app/assets/settings/google.png deleted file mode 100644 index 60032628a8..0000000000 Binary files a/frontend/appflowy_tauri/src/appflowy_app/assets/settings/google.png and /dev/null differ diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/settings/light.png b/frontend/appflowy_tauri/src/appflowy_app/assets/settings/light.png deleted file mode 100644 index 09b2d9c475..0000000000 Binary files a/frontend/appflowy_tauri/src/appflowy_app/assets/settings/light.png and /dev/null differ diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/settings/workplace.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/settings/workplace.svg deleted file mode 100644 index 2076ea3e2c..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/settings/workplace.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/show-menu.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/show-menu.svg deleted file mode 100644 index 8baf55bffd..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/show-menu.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/sort.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/sort.svg deleted file mode 100644 index e3b6a49a56..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/sort.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/strikethrough.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/strikethrough.svg deleted file mode 100644 index c118422a15..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/strikethrough.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/text.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/text.svg deleted file mode 100644 index 7befa5080f..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/text.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/todo-list.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/todo-list.svg deleted file mode 100644 index 37f52c47ed..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/todo-list.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/underline.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/underline.svg deleted file mode 100644 index f5d53f0ec2..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/underline.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/assets/up.svg b/frontend/appflowy_tauri/src/appflowy_app/assets/up.svg deleted file mode 100644 index bd8f3067d3..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/assets/up.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/avatar/ProfileAvatar.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/avatar/ProfileAvatar.tsx deleted file mode 100644 index 1248882238..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/avatar/ProfileAvatar.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { stringToColor, stringToShortName } from '$app/utils/avatar'; -import { Avatar } from '@mui/material'; -import { useAppSelector } from '$app/stores/store'; - -export const ProfileAvatar = ({ - onClick, - className, - width, - height, -}: { - onClick?: (e: React.MouseEvent) => void; - width?: number; - height?: number; - className?: string; -}) => { - const { displayName = 'Me', iconUrl } = useAppSelector((state) => state.currentUser); - - return ( - - {iconUrl ? iconUrl : stringToShortName(displayName)} - - ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/avatar/WorkplaceAvatar.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/avatar/WorkplaceAvatar.tsx deleted file mode 100644 index 079342b528..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/avatar/WorkplaceAvatar.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { Avatar } from '@mui/material'; -import { stringToColor, stringToShortName } from '$app/utils/avatar'; - -export const WorkplaceAvatar = ({ - workplaceName, - icon, - onClick, - width, - height, - className, -}: { - workplaceName: string; - width: number; - height: number; - className?: string; - icon?: string; - onClick?: (e: React.MouseEvent) => void; -}) => { - return ( - - {icon ? icon : stringToShortName(workplaceName)} - - ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/avatar/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/avatar/index.ts deleted file mode 100644 index 772056737a..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/avatar/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './WorkplaceAvatar'; -export * from './ProfileAvatar'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/confirm_dialog/DeleteConfirmDialog.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/confirm_dialog/DeleteConfirmDialog.tsx deleted file mode 100644 index 058335d30c..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/confirm_dialog/DeleteConfirmDialog.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import React, { useCallback } from 'react'; -import DialogContent from '@mui/material/DialogContent'; -import { Button, DialogProps } from '@mui/material'; -import Dialog from '@mui/material/Dialog'; -import { useTranslation } from 'react-i18next'; -import { Log } from '$app/utils/log'; - -interface Props extends DialogProps { - open: boolean; - title: string; - subtitle?: string; - onOk?: () => Promise; - onClose: () => void; - onCancel?: () => void; - okText?: string; - cancelText?: string; - container?: HTMLElement | null; -} - -function DeleteConfirmDialog({ open, title, onOk, onCancel, onClose, okText, cancelText, container, ...props }: Props) { - const { t } = useTranslation(); - - const onDone = useCallback(async () => { - try { - await onOk?.(); - onClose(); - } catch (e) { - Log.error(e); - } - }, [onClose, onOk]); - - return ( - { - if (e.key === 'Escape') { - e.preventDefault(); - e.stopPropagation(); - onClose(); - } - - if (e.key === 'Enter') { - e.preventDefault(); - e.stopPropagation(); - void onDone(); - } - }} - onMouseDown={(e) => e.stopPropagation()} - open={open} - onClose={onClose} - {...props} - > - - {title} -
- - -
-
-
- ); -} - -export default DeleteConfirmDialog; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/confirm_dialog/RenameDialog.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/confirm_dialog/RenameDialog.tsx deleted file mode 100644 index cb8b7a80ed..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/confirm_dialog/RenameDialog.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import React, { useCallback, useEffect, useState } from 'react'; -import DialogTitle from '@mui/material/DialogTitle'; -import DialogContent from '@mui/material/DialogContent'; -import Dialog from '@mui/material/Dialog'; -import { useTranslation } from 'react-i18next'; -import TextField from '@mui/material/TextField'; -import { Button, DialogActions, Divider } from '@mui/material'; - -function RenameDialog({ - defaultValue, - open, - onClose, - onOk, -}: { - defaultValue: string; - open: boolean; - onClose: () => void; - onOk: (val: string) => Promise; -}) { - const { t } = useTranslation(); - const [value, setValue] = useState(defaultValue); - const [error, setError] = useState(false); - - useEffect(() => { - setValue(defaultValue); - setError(false); - }, [defaultValue]); - - const onDone = useCallback(async () => { - try { - await onOk(value); - onClose(); - } catch (e) { - setError(true); - } - }, [onClose, onOk, value]); - - return ( - e.stopPropagation()} open={open} onClose={onClose}> - {t('menuAppHeader.renameDialog')} - - { - e.stopPropagation(); - if (e.key === 'Enter') { - e.preventDefault(); - void onDone(); - } - - if (e.key === 'Escape') { - e.preventDefault(); - onClose(); - } - }} - onChange={(e) => { - setValue(e.target.value); - }} - margin='dense' - fullWidth - variant='standard' - /> - - - - - - - - ); -} - -export default RenameDialog; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/devtool/AppFlowyDevTool.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/devtool/AppFlowyDevTool.tsx deleted file mode 100644 index 5d3ed1e3de..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/devtool/AppFlowyDevTool.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import * as React from 'react'; -import SpeedDial from '@mui/material/SpeedDial'; -import SpeedDialIcon from '@mui/material/SpeedDialIcon'; -import SpeedDialAction from '@mui/material/SpeedDialAction'; -import { useMemo } from 'react'; -import { CloseOutlined, BuildOutlined, LoginOutlined, VisibilityOff } from '@mui/icons-material'; -import ManualSignInDialog from '$app/components/_shared/devtool/ManualSignInDialog'; -import { Portal } from '@mui/material'; - -function AppFlowyDevTool() { - const [openManualSignIn, setOpenManualSignIn] = React.useState(false); - const [hidden, setHidden] = React.useState(false); - const actions = useMemo( - () => [ - { - icon: , - name: 'Manual SignIn', - onClick: () => { - setOpenManualSignIn(true); - }, - }, - { - icon: , - name: 'Hide Dev Tool', - onClick: () => { - setHidden(true); - }, - }, - ], - [] - ); - - return ( - - - - ); -} - -export default AppFlowyDevTool; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/devtool/ManualSignInDialog.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/devtool/ManualSignInDialog.tsx deleted file mode 100644 index 364b334a07..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/devtool/ManualSignInDialog.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import React from 'react'; -import { CircularProgress, DialogActions, DialogProps, Tab, Tabs, TextareaAutosize } from '@mui/material'; -import Dialog from '@mui/material/Dialog'; -import DialogContent from '@mui/material/DialogContent'; -import Button from '@mui/material/Button'; -import { useAuth } from '$app/components/auth/auth.hooks'; -import TextField from '@mui/material/TextField'; - -function ManualSignInDialog(props: DialogProps) { - const [uri, setUri] = React.useState(''); - const [loading, setLoading] = React.useState(false); - const { signInWithOAuth, signInWithEmailPassword } = useAuth(); - const [tab, setTab] = React.useState(0); - const [email, setEmail] = React.useState(''); - const [password, setPassword] = React.useState(''); - const [domain, setDomain] = React.useState(''); - const handleSignIn = async () => { - setLoading(true); - try { - if (tab === 1) { - if (!email || !password) return; - await signInWithEmailPassword(email, password, domain); - } else { - await signInWithOAuth(uri); - } - } finally { - setLoading(false); - } - - props?.onClose?.({}, 'backdropClick'); - }; - - return ( - { - if (e.key === 'Enter') { - e.preventDefault(); - void handleSignIn(); - } - }} - > - - { - setTab(value); - }} - > - - - - {tab === 1 ? ( -
- setEmail(e.target.value)} - /> - setPassword(e.target.value)} - /> - setDomain(e.target.value)} - /> -
- ) : ( - { - setUri(e.target.value); - }} - /> - )} -
- - - - -
- ); -} - -export default ManualSignInDialog; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/drag_block/drag.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/drag_block/drag.hooks.ts deleted file mode 100644 index 85f0507fff..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/drag_block/drag.hooks.ts +++ /dev/null @@ -1,87 +0,0 @@ -import React from 'react'; - -interface Props { - dragId?: string; - onEnd?: (result: { dragId: string; position: 'before' | 'after' | 'inside' }) => void; -} - -function calcPosition(targetRect: DOMRect, clientY: number) { - const top = targetRect.top + targetRect.height / 3; - const bottom = targetRect.bottom - targetRect.height / 3; - - if (clientY < top) return 'before'; - if (clientY > bottom) return 'after'; - return 'inside'; -} - -export function useDrag(props: Props) { - const { dragId, onEnd } = props; - const [isDraggingOver, setIsDraggingOver] = React.useState(false); - const [isDragging, setIsDragging] = React.useState(false); - const [dropPosition, setDropPosition] = React.useState<'before' | 'after' | 'inside'>(); - const onDrop = (e: React.DragEvent) => { - e.stopPropagation(); - setIsDraggingOver(false); - setIsDragging(false); - setDropPosition(undefined); - const currentTarget = e.currentTarget; - - if (currentTarget.parentElement?.closest(`[data-drop-enabled="false"]`)) return; - if (currentTarget.closest(`[data-dragging="true"]`)) return; - const dragId = e.dataTransfer.getData('dragId'); - const targetRect = currentTarget.getBoundingClientRect(); - const { clientY } = e; - - const position = calcPosition(targetRect, clientY); - - onEnd && onEnd({ dragId, position }); - }; - - const onDragOver = (e: React.DragEvent) => { - e.stopPropagation(); - e.preventDefault(); - if (isDragging) return; - const currentTarget = e.currentTarget; - - if (currentTarget.parentElement?.closest(`[data-drop-enabled="false"]`)) return; - if (currentTarget.closest(`[data-dragging="true"]`)) return; - setIsDraggingOver(true); - const targetRect = currentTarget.getBoundingClientRect(); - const { clientY } = e; - const position = calcPosition(targetRect, clientY); - - setDropPosition(position); - }; - - const onDragLeave = (e: React.DragEvent) => { - e.stopPropagation(); - setIsDraggingOver(false); - setDropPosition(undefined); - }; - - const onDragStart = (e: React.DragEvent) => { - if (!dragId) return; - e.stopPropagation(); - e.dataTransfer.setData('dragId', dragId); - e.dataTransfer.effectAllowed = 'move'; - setIsDragging(true); - }; - - const onDragEnd = (e: React.DragEvent) => { - e.stopPropagation(); - setIsDragging(false); - setIsDraggingOver(false); - setDropPosition(undefined); - }; - - return { - onDrop, - onDragOver, - onDragLeave, - onDragStart, - isDraggingOver, - isDragging, - onDragEnd, - dropPosition, - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/drag_block/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/drag_block/index.ts deleted file mode 100644 index e0cb540f75..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/drag_block/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './drag.hooks'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/emoji_picker/EmojiPicker.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/emoji_picker/EmojiPicker.hooks.ts deleted file mode 100644 index af82c82df5..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/emoji_picker/EmojiPicker.hooks.ts +++ /dev/null @@ -1,165 +0,0 @@ -import React, { useCallback, useEffect, useState } from 'react'; -import emojiData, { EmojiMartData } from '@emoji-mart/data'; -import { init, FrequentlyUsed, getEmojiDataFromNative, Store } from 'emoji-mart'; - -import { PopoverProps } from '@mui/material/Popover'; -import { PopoverOrigin } from '@mui/material/Popover/Popover'; -import chunk from 'lodash-es/chunk'; - -export const EMOJI_SIZE = 32; - -export const PER_ROW_EMOJI_COUNT = 13; - -export const MAX_FREQUENTLY_ROW_COUNT = 2; - -export interface EmojiCategory { - id: string; - emojis: Emoji[]; -} - -interface Emoji { - id: string; - name: string; - native: string; -} - -export function useLoadEmojiData({ onEmojiSelect }: { onEmojiSelect: (emoji: string) => void }) { - const [searchValue, setSearchValue] = useState(''); - const [emojiCategories, setEmojiCategories] = useState([]); - const [skin, setSkin] = useState(() => { - return Number(Store.get('skin')) || 0; - }); - - const onSkinChange = useCallback((val: number) => { - setSkin(val); - Store.set('skin', String(val)); - }, []); - - const loadEmojiData = useCallback( - async (searchVal?: string) => { - const { emojis, categories } = emojiData as EmojiMartData; - - const filteredCategories = categories - .map((category) => { - const { id, emojis: categoryEmojis } = category; - - return { - id, - emojis: categoryEmojis - .filter((emojiId) => { - const emoji = emojis[emojiId]; - - if (!searchVal) return true; - return filterSearchValue(emoji, searchVal); - }) - .map((emojiId) => { - const emoji = emojis[emojiId]; - const { name, skins } = emoji; - - return { - id: emojiId, - name, - native: skins[skin] ? skins[skin].native : skins[0].native, - }; - }), - }; - }) - .filter((category) => category.emojis.length > 0); - - setEmojiCategories(filteredCategories); - }, - [skin] - ); - - useEffect(() => { - void (async () => { - await init({ data: emojiData, maxFrequentRows: MAX_FREQUENTLY_ROW_COUNT, perLine: PER_ROW_EMOJI_COUNT }); - await loadEmojiData(); - })(); - }, [loadEmojiData]); - - useEffect(() => { - void loadEmojiData(searchValue); - }, [loadEmojiData, searchValue]); - - const onSelect = useCallback( - async (native: string) => { - onEmojiSelect(native); - if (!native) { - return; - } - - const data = await getEmojiDataFromNative(native); - - FrequentlyUsed.add(data); - }, - [onEmojiSelect] - ); - - return { - emojiCategories, - setSearchValue, - searchValue, - onSelect, - onSkinChange, - skin, - }; -} - -export function useSelectSkinPopoverProps(): PopoverProps & { - onOpen: (event: React.MouseEvent) => void; - onClose: () => void; -} { - const [anchorEl, setAnchorEl] = useState(undefined); - const onOpen = useCallback((event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); - }, []); - const onClose = useCallback(() => { - setAnchorEl(undefined); - }, []); - const open = Boolean(anchorEl); - const anchorOrigin = { vertical: 'bottom', horizontal: 'center' } as PopoverOrigin; - const transformOrigin = { vertical: 'top', horizontal: 'center' } as PopoverOrigin; - - return { - anchorEl, - onOpen, - onClose, - open, - anchorOrigin, - transformOrigin, - }; -} - -function filterSearchValue(emoji: emojiData.Emoji, searchValue: string) { - const { name, keywords } = emoji; - const searchValueLowerCase = searchValue.toLowerCase(); - - return ( - name.toLowerCase().includes(searchValueLowerCase) || - (keywords && keywords.some((keyword) => keyword.toLowerCase().includes(searchValueLowerCase))) - ); -} - -export function getRowsWithCategories(emojiCategories: EmojiCategory[], rowSize: number) { - const rows: { - id: string; - type: 'category' | 'emojis'; - emojis?: Emoji[]; - }[] = []; - - emojiCategories.forEach((category) => { - rows.push({ - id: category.id, - type: 'category', - }); - chunk(category.emojis, rowSize).forEach((chunk, index) => { - rows.push({ - type: 'emojis', - emojis: chunk, - id: `${category.id}-${index}`, - }); - }); - }); - return rows; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/emoji_picker/EmojiPicker.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/emoji_picker/EmojiPicker.tsx deleted file mode 100644 index b8dcb3f6c7..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/emoji_picker/EmojiPicker.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; - -import { useLoadEmojiData } from './EmojiPicker.hooks'; -import EmojiPickerHeader from './EmojiPickerHeader'; -import EmojiPickerCategories from './EmojiPickerCategories'; - -interface Props { - onEmojiSelect: (emoji: string) => void; - onEscape?: () => void; - defaultEmoji?: string; -} - -function EmojiPicker({ defaultEmoji, onEscape, ...props }: Props) { - const { skin, onSkinChange, emojiCategories, setSearchValue, searchValue, onSelect } = useLoadEmojiData(props); - - return ( -
- - -
- ); -} - -export default EmojiPicker; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/emoji_picker/EmojiPickerCategories.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/emoji_picker/EmojiPickerCategories.tsx deleted file mode 100644 index eefea8db11..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/emoji_picker/EmojiPickerCategories.tsx +++ /dev/null @@ -1,354 +0,0 @@ -import React, { useCallback, useEffect, useMemo, useRef } from 'react'; -import { - EMOJI_SIZE, - EmojiCategory, - getRowsWithCategories, - PER_ROW_EMOJI_COUNT, -} from '$app/components/_shared/emoji_picker/EmojiPicker.hooks'; -import { FixedSizeList } from 'react-window'; -import { useTranslation } from 'react-i18next'; -import AutoSizer from 'react-virtualized-auto-sizer'; -import { getDistanceEdge, inView } from '$app/components/_shared/keyboard_navigation/utils'; - -function EmojiPickerCategories({ - emojiCategories, - onEmojiSelect, - onEscape, - defaultEmoji, -}: { - emojiCategories: EmojiCategory[]; - onEmojiSelect: (emoji: string) => void; - onEscape?: () => void; - defaultEmoji?: string; -}) { - const scrollRef = React.useRef(null); - const { t } = useTranslation(); - const [selectCell, setSelectCell] = React.useState({ - row: 1, - column: 0, - }); - const rows = useMemo(() => { - return getRowsWithCategories(emojiCategories, PER_ROW_EMOJI_COUNT); - }, [emojiCategories]); - const mouseY = useRef(null); - const mouseX = useRef(null); - - const ref = React.useRef(null); - - const getCategoryName = useCallback( - (id: string) => { - const i18nName: Record = { - frequent: t('emoji.categories.frequentlyUsed'), - people: t('emoji.categories.people'), - nature: t('emoji.categories.nature'), - foods: t('emoji.categories.food'), - activity: t('emoji.categories.activities'), - places: t('emoji.categories.places'), - objects: t('emoji.categories.objects'), - symbols: t('emoji.categories.symbols'), - flags: t('emoji.categories.flags'), - }; - - return i18nName[id]; - }, - [t] - ); - - useEffect(() => { - scrollRef.current?.scrollTo({ - top: 0, - }); - - setSelectCell({ - row: 1, - column: 0, - }); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [rows]); - - const renderRow = useCallback( - ({ index, style }: { index: number; style: React.CSSProperties }) => { - const item = rows[index]; - - return ( -
- {item.type === 'category' ? ( -
{getCategoryName(item.id)}
- ) : null} -
- {item.emojis?.map((emoji, columnIndex) => { - const isSelected = selectCell.row === index && selectCell.column === columnIndex; - - const isDefaultEmoji = defaultEmoji === emoji.native; - - return ( -
{ - onEmojiSelect(emoji.native); - }} - onMouseMove={(e) => { - mouseY.current = e.clientY; - mouseX.current = e.clientX; - }} - onMouseEnter={(e) => { - if (mouseY.current === null || mouseY.current !== e.clientY || mouseX.current !== e.clientX) { - setSelectCell({ - row: index, - column: columnIndex, - }); - } - - mouseX.current = e.clientX; - mouseY.current = e.clientY; - }} - className={`flex cursor-pointer items-center justify-center rounded hover:bg-fill-list-hover ${ - isSelected ? 'bg-fill-list-hover' : 'hover:bg-transparent' - } ${isDefaultEmoji ? 'bg-fill-list-active' : ''}`} - > - {emoji.native} -
- ); - })} -
-
- ); - }, - [defaultEmoji, getCategoryName, onEmojiSelect, rows, selectCell.column, selectCell.row] - ); - - const getNewColumnIndex = useCallback( - (rowIndex: number, columnIndex: number): number => { - const row = rows[rowIndex]; - const length = row.emojis?.length; - let newColumnIndex = columnIndex; - - if (length && length <= columnIndex) { - newColumnIndex = length - 1 || 0; - } - - return newColumnIndex; - }, - [rows] - ); - - const findNextRow = useCallback( - (rowIndex: number, columnIndex: number): { row: number; column: number } => { - const rowLength = rows.length; - let nextRowIndex = rowIndex + 1; - - if (nextRowIndex >= rowLength - 1) { - nextRowIndex = rowLength - 1; - } else if (rows[nextRowIndex].type === 'category') { - nextRowIndex = findNextRow(nextRowIndex, columnIndex).row; - } - - const newColumnIndex = getNewColumnIndex(nextRowIndex, columnIndex); - - return { - row: nextRowIndex, - column: newColumnIndex, - }; - }, - [getNewColumnIndex, rows] - ); - - const findPrevRow = useCallback( - (rowIndex: number, columnIndex: number): { row: number; column: number } => { - let prevRowIndex = rowIndex - 1; - - if (prevRowIndex < 1) { - prevRowIndex = 1; - } else if (rows[prevRowIndex].type === 'category') { - prevRowIndex = findPrevRow(prevRowIndex, columnIndex).row; - } - - const newColumnIndex = getNewColumnIndex(prevRowIndex, columnIndex); - - return { - row: prevRowIndex, - column: newColumnIndex, - }; - }, - [getNewColumnIndex, rows] - ); - - const findPrevCell = useCallback( - (row: number, column: number): { row: number; column: number } => { - const prevColumn = column - 1; - - if (prevColumn < 0) { - const prevRow = findPrevRow(row, column).row; - - if (prevRow === row) return { row, column }; - const length = rows[prevRow].emojis?.length || 0; - - return { - row: prevRow, - column: length > 0 ? length - 1 : 0, - }; - } - - return { - row, - column: prevColumn, - }; - }, - [findPrevRow, rows] - ); - - const findNextCell = useCallback( - (row: number, column: number): { row: number; column: number } => { - const nextColumn = column + 1; - - const rowLength = rows[row].emojis?.length || 0; - - if (nextColumn >= rowLength) { - const nextRow = findNextRow(row, column).row; - - if (nextRow === row) return { row, column }; - return { - row: nextRow, - column: 0, - }; - } - - return { - row, - column: nextColumn, - }; - }, - [findNextRow, rows] - ); - - useEffect(() => { - if (!selectCell || !scrollRef.current) return; - const emojiKey = rows[selectCell.row]?.emojis?.[selectCell.column]?.id; - const emojiDom = document.querySelector(`[data-key="${emojiKey}"]`); - - if (emojiDom && !inView(emojiDom as HTMLElement, scrollRef.current as HTMLElement)) { - const distance = getDistanceEdge(emojiDom as HTMLElement, scrollRef.current as HTMLElement); - - scrollRef.current?.scrollTo({ - top: scrollRef.current?.scrollTop + distance, - }); - } - }, [selectCell, rows]); - - const handleKeyDown = useCallback( - (e: KeyboardEvent) => { - e.stopPropagation(); - - switch (e.key) { - case 'Escape': - e.preventDefault(); - onEscape?.(); - break; - case 'ArrowUp': { - e.preventDefault(); - - setSelectCell(findPrevRow(selectCell.row, selectCell.column)); - - break; - } - - case 'ArrowDown': { - e.preventDefault(); - - setSelectCell(findNextRow(selectCell.row, selectCell.column)); - - break; - } - - case 'ArrowLeft': { - e.preventDefault(); - - const prevCell = findPrevCell(selectCell.row, selectCell.column); - - setSelectCell(prevCell); - break; - } - - case 'ArrowRight': { - e.preventDefault(); - - const nextCell = findNextCell(selectCell.row, selectCell.column); - - setSelectCell(nextCell); - break; - } - - case 'Enter': { - e.preventDefault(); - const currentRow = rows[selectCell.row]; - const emoji = currentRow.emojis?.[selectCell.column]; - - if (emoji) { - onEmojiSelect(emoji.native); - } - - break; - } - - default: - break; - } - }, - [ - findNextCell, - findPrevCell, - findPrevRow, - findNextRow, - onEmojiSelect, - onEscape, - rows, - selectCell.column, - selectCell.row, - ] - ); - - useEffect(() => { - const focusElement = document.querySelector('.emoji-picker .search-emoji-input') as HTMLInputElement; - - const parentElement = ref.current?.parentElement; - - focusElement?.addEventListener('keydown', handleKeyDown); - parentElement?.addEventListener('keydown', handleKeyDown); - return () => { - focusElement?.removeEventListener('keydown', handleKeyDown); - parentElement?.removeEventListener('keydown', handleKeyDown); - }; - }, [handleKeyDown]); - - return ( -
- - {({ height, width }: { height: number; width: number }) => ( - - {renderRow} - - )} - -
- ); -} - -export default EmojiPickerCategories; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/emoji_picker/EmojiPickerHeader.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/emoji_picker/EmojiPickerHeader.tsx deleted file mode 100644 index 177ac2e7a0..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/emoji_picker/EmojiPickerHeader.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import React from 'react'; -import { Box, IconButton } from '@mui/material'; -import { Circle, DeleteOutlineRounded, SearchOutlined } from '@mui/icons-material'; -import TextField from '@mui/material/TextField'; -import Tooltip from '@mui/material/Tooltip'; -import { randomEmoji } from '$app/utils/emoji'; -import ShuffleIcon from '@mui/icons-material/Shuffle'; -import Popover from '@mui/material/Popover'; -import { useSelectSkinPopoverProps } from '$app/components/_shared/emoji_picker/EmojiPicker.hooks'; -import { useTranslation } from 'react-i18next'; - -const skinTones = [ - { - value: 0, - color: '#ffc93a', - }, - { - color: '#ffdab7', - value: 1, - }, - { - color: '#e7b98f', - value: 2, - }, - { - color: '#c88c61', - value: 3, - }, - { - color: '#a46134', - value: 4, - }, - { - color: '#5d4437', - value: 5, - }, -]; - -interface Props { - onEmojiSelect: (emoji: string) => void; - skin: number; - onSkinSelect: (skin: number) => void; - searchValue: string; - onSearchChange: (value: string) => void; -} - -function EmojiPickerHeader({ onEmojiSelect, onSkinSelect, searchValue, onSearchChange, skin }: Props) { - const { onOpen, ...popoverProps } = useSelectSkinPopoverProps(); - const { t } = useTranslation(); - - return ( -
-
- - - { - onSearchChange(e.target.value); - }} - autoFocus={true} - autoCorrect={'off'} - autoComplete={'off'} - spellCheck={false} - className={'search-emoji-input'} - placeholder={t('search.label')} - variant='standard' - /> - - -
- { - const emoji = randomEmoji(); - - onEmojiSelect(emoji); - }} - > - - -
-
- -
- - - -
-
- -
- { - onEmojiSelect(''); - }} - > - - -
-
-
- -
- {skinTones.map((skinTone) => ( -
- { - onSkinSelect(skinTone.value); - popoverProps.onClose?.(); - }} - > - - -
- ))} -
-
-
- ); -} - -export default EmojiPickerHeader; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/error_boundary/withError.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/error_boundary/withError.tsx deleted file mode 100644 index 9b9ba159fb..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/error_boundary/withError.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { ErrorBoundary } from 'react-error-boundary'; -import { Log } from '$app/utils/log'; -import { Alert } from '@mui/material'; - -export default function withErrorBoundary(WrappedComponent: React.ComponentType) { - return (props: T) => ( - { - Log.error(e); - }} - fallback={Something went wrong} - > - - - ); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/image_upload/EmbedLink.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/image_upload/EmbedLink.tsx deleted file mode 100644 index 34a99007ad..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/image_upload/EmbedLink.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React, { useCallback, useState } from 'react'; -import TextField from '@mui/material/TextField'; -import { useTranslation } from 'react-i18next'; -import Button from '@mui/material/Button'; - -const urlPattern = /^(https?:\/\/)([^\s(["<,>/]*)(\/)[^\s[",><]*(.png|.jpg|.gif|.webm|.webp|.svg)(\?[^\s[",><]*)?$/; - -export function EmbedLink({ - onDone, - onEscape, - defaultLink, -}: { - defaultLink?: string; - onDone?: (value: string) => void; - onEscape?: () => void; -}) { - const { t } = useTranslation(); - - const [value, setValue] = useState(defaultLink ?? ''); - const [error, setError] = useState(false); - - const handleChange = useCallback( - (e: React.ChangeEvent) => { - const value = e.target.value; - - setValue(value); - setError(!urlPattern.test(value)); - }, - [setValue, setError] - ); - - const handleKeyDown = useCallback( - (e: React.KeyboardEvent) => { - if (e.key === 'Enter' && !error && value) { - e.preventDefault(); - e.stopPropagation(); - onDone?.(value); - } - - if (e.key === 'Escape') { - e.preventDefault(); - e.stopPropagation(); - onEscape?.(); - } - }, - [error, onDone, onEscape, value] - ); - - return ( -
- - -
- ); -} - -export default EmbedLink; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/image_upload/LocalImage.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/image_upload/LocalImage.tsx deleted file mode 100644 index d94e5f2889..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/image_upload/LocalImage.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import React, { forwardRef, useCallback, useEffect, useRef, useState } from 'react'; -import { CircularProgress } from '@mui/material'; -import { useTranslation } from 'react-i18next'; -import { ErrorOutline } from '@mui/icons-material'; - -export const LocalImage = forwardRef< - HTMLImageElement, - { - renderErrorNode?: () => React.ReactElement | null; - } & React.ImgHTMLAttributes ->((localImageProps, ref) => { - const { src, renderErrorNode, ...props } = localImageProps; - const imageRef = useRef(null); - const { t } = useTranslation(); - const [imageURL, setImageURL] = useState(''); - const [loading, setLoading] = useState(true); - const [isError, setIsError] = useState(false); - const loadLocalImage = useCallback(async () => { - if (!src) return; - setLoading(true); - setIsError(false); - const { readBinaryFile, BaseDirectory } = await import('@tauri-apps/api/fs'); - - try { - const svg = src.endsWith('.svg'); - - const buffer = await readBinaryFile(src, { dir: BaseDirectory.AppLocalData }); - const blob = new Blob([buffer], { type: svg ? 'image/svg+xml' : 'image' }); - - setImageURL(URL.createObjectURL(blob)); - } catch (e) { - setIsError(true); - } - - setLoading(false); - }, [src]); - - useEffect(() => { - void loadLocalImage(); - }, [loadLocalImage]); - - if (loading) { - return ( -
- - {t('editor.loading')}... -
- ); - } - - if (isError) { - if (renderErrorNode) return renderErrorNode(); - return ( -
- -
{t('editor.imageLoadFailed')}
-
- ); - } - - return {'local; -}); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/image_upload/Unsplash.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/image_upload/Unsplash.tsx deleted file mode 100644 index 01da8323b9..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/image_upload/Unsplash.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { createApi } from 'unsplash-js'; -import TextField from '@mui/material/TextField'; -import { useTranslation } from 'react-i18next'; -import Typography from '@mui/material/Typography'; -import debounce from 'lodash-es/debounce'; -import { CircularProgress } from '@mui/material'; -import { open } from '@tauri-apps/api/shell'; - -const unsplash = createApi({ - accessKey: '1WxD1JpMOUX86lZKKob4Ca0LMZPyO2rUmAgjpWm9Ids', -}); - -const SEARCH_DEBOUNCE_TIME = 500; - -export function Unsplash({ onDone, onEscape }: { onDone?: (value: string) => void; onEscape?: () => void }) { - const { t } = useTranslation(); - - const [loading, setLoading] = useState(true); - const [error, setError] = useState(''); - const [photos, setPhotos] = useState< - { - thumb: string; - regular: string; - alt: string | null; - id: string; - user: { - name: string; - link: string; - }; - }[] - >([]); - const [searchValue, setSearchValue] = useState(''); - - const handleChange = useCallback((e: React.ChangeEvent) => { - const value = e.target.value; - - setSearchValue(value); - }, []); - - const handleKeyDown = useCallback( - (e: React.KeyboardEvent) => { - if (e.key === 'Escape') { - e.preventDefault(); - e.stopPropagation(); - onEscape?.(); - } - }, - [onEscape] - ); - - const debounceSearchPhotos = useMemo(() => { - return debounce(async (searchValue: string) => { - const request = searchValue - ? unsplash.search.getPhotos({ query: searchValue ?? undefined, perPage: 32 }) - : unsplash.photos.list({ perPage: 32 }); - - setError(''); - setLoading(true); - await request.then((result) => { - if (result.errors) { - setError(result.errors[0]); - } else { - setPhotos( - result.response.results.map((photo) => ({ - id: photo.id, - thumb: photo.urls.thumb, - regular: photo.urls.regular, - alt: photo.alt_description, - user: { - name: photo.user.name, - link: photo.user.links.html, - }, - })) - ); - } - - setLoading(false); - }); - }, SEARCH_DEBOUNCE_TIME); - }, []); - - useEffect(() => { - void debounceSearchPhotos(searchValue); - return () => { - debounceSearchPhotos.cancel(); - }; - }, [debounceSearchPhotos, searchValue]); - - return ( -
- - - {loading ? ( -
- -
{t('editor.loading')}
-
- ) : error ? ( - - {error} - - ) : ( -
- {photos.length > 0 ? ( - <> -
- {photos.map((photo) => ( -
- { - onDone?.(photo.regular); - }} - src={photo.thumb} - alt={photo.alt ?? ''} - className={'h-20 w-32 rounded object-cover hover:opacity-80'} - /> -
- by{' '} - { - void open(photo.user.link); - }} - className={'underline hover:text-function-info'} - > - {photo.user.name} - -
-
- ))} -
- - {t('findAndReplace.searchMore')} - - - ) : ( - - {t('findAndReplace.noResult')} - - )} -
- )} -
- ); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/image_upload/UploadImage.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/image_upload/UploadImage.tsx deleted file mode 100644 index d39da68caf..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/image_upload/UploadImage.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import React, { useCallback } from 'react'; -import Button from '@mui/material/Button'; -import { useTranslation } from 'react-i18next'; -import CloudUploadIcon from '@mui/icons-material/CloudUploadOutlined'; -import { notify } from '$app/components/_shared/notify'; -import { isTauri } from '$app/utils/env'; -import { getFileName, IMAGE_DIR, ALLOWED_IMAGE_EXTENSIONS, MAX_IMAGE_SIZE } from '$app/utils/upload_image'; - -export function UploadImage({ onDone }: { onDone?: (url: string) => void }) { - const { t } = useTranslation(); - - const checkTauriFile = useCallback( - async (url: string) => { - // const { readBinaryFile } = await import('@tauri-apps/api/fs'); - // const buffer = await readBinaryFile(url); - // const blob = new Blob([buffer]); - // if (blob.size > MAX_IMAGE_SIZE) { - // notify.error(t('document.imageBlock.error.invalidImageSize')); - // return false; - // } - - return true; - }, - [t] - ); - - const uploadTauriLocalImage = useCallback( - async (url: string) => { - const { copyFile, BaseDirectory, exists, createDir } = await import('@tauri-apps/api/fs'); - - const checked = await checkTauriFile(url); - - if (!checked) return; - - try { - const existDir = await exists(IMAGE_DIR, { dir: BaseDirectory.AppLocalData }); - - if (!existDir) { - await createDir(IMAGE_DIR, { dir: BaseDirectory.AppLocalData }); - } - - const filename = getFileName(url); - - await copyFile(url, `${IMAGE_DIR}/${filename}`, { dir: BaseDirectory.AppLocalData }); - const newUrl = `${IMAGE_DIR}/${filename}`; - - onDone?.(newUrl); - } catch (e) { - notify.error(t('document.plugins.image.imageUploadFailed')); - } - }, - [checkTauriFile, onDone, t] - ); - - const handleClickUpload = useCallback(async () => { - if (!isTauri()) return; - const { open } = await import('@tauri-apps/api/dialog'); - - const url = await open({ - multiple: false, - directory: false, - filters: [ - { - name: 'Image', - extensions: ALLOWED_IMAGE_EXTENSIONS, - }, - ], - }); - - if (!url || typeof url !== 'string') return; - - await uploadTauriLocalImage(url); - }, [uploadTauriLocalImage]); - - return ( -
- -
- ); -} - -export default UploadImage; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/image_upload/UploadTabs.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/image_upload/UploadTabs.tsx deleted file mode 100644 index fb65c709ce..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/image_upload/UploadTabs.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import React, { SyntheticEvent, useCallback, useState } from 'react'; -import Popover, { PopoverProps } from '@mui/material/Popover'; -import { TabPanel, ViewTab, ViewTabs } from '$app/components/database/components/tab_bar/ViewTabs'; -import SwipeableViews from 'react-swipeable-views'; -import { PopoverCommonProps } from '$app/components/editor/components/tools/popover'; - -export enum TAB_KEY { - Colors = 'colors', - UPLOAD = 'upload', - EMBED_LINK = 'embed_link', - UNSPLASH = 'unsplash', -} - -export type TabOption = { - key: TAB_KEY; - label: string; - Component: React.ComponentType<{ - onDone?: (value: string) => void; - onEscape?: () => void; - }>; - onDone?: (value: string) => void; -}; - -export function UploadTabs({ - tabOptions, - popoverProps, - containerStyle, - extra, -}: { - containerStyle?: React.CSSProperties; - tabOptions: TabOption[]; - popoverProps?: PopoverProps; - extra?: React.ReactNode; -}) { - const [tabValue, setTabValue] = useState(() => { - return tabOptions[0].key; - }); - - const handleTabChange = useCallback((_: SyntheticEvent, newValue: string) => { - setTabValue(newValue as TAB_KEY); - }, []); - - const selectedIndex = tabOptions.findIndex((tab) => tab.key === tabValue); - - const onKeyDown = useCallback( - (e: React.KeyboardEvent) => { - e.stopPropagation(); - - if (e.key === 'Escape') { - e.preventDefault(); - e.stopPropagation(); - popoverProps?.onClose?.({}, 'escapeKeyDown'); - } - - if (e.key === 'Tab') { - e.preventDefault(); - e.stopPropagation(); - setTabValue((prev) => { - const currentIndex = tabOptions.findIndex((tab) => tab.key === prev); - let nextIndex = currentIndex + 1; - - if (e.shiftKey) { - nextIndex = currentIndex - 1; - } - - return tabOptions[nextIndex % tabOptions.length]?.key ?? tabOptions[0].key; - }); - } - }, - [popoverProps, tabOptions] - ); - - return ( - -
-
- - {tabOptions.map((tab) => { - const { key, label } = tab; - - return ; - })} - - {extra} -
- -
- - {tabOptions.map((tab, index) => { - const { key, Component, onDone } = tab; - - return ( - - popoverProps?.onClose?.({}, 'escapeKeyDown')} /> - - ); - })} - -
-
-
- ); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/image_upload/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/image_upload/index.ts deleted file mode 100644 index 28673cae5f..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/image_upload/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './Unsplash'; -export * from './UploadImage'; -export * from './EmbedLink'; -export * from './UploadTabs'; -export * from './LocalImage'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/katex_math/KatexMath.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/katex_math/KatexMath.tsx deleted file mode 100644 index e6c7cac5ed..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/katex_math/KatexMath.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import 'katex/dist/katex.min.css'; -import { BlockMath, InlineMath } from 'react-katex'; -import './index.css'; - -function KatexMath({ latex, isInline = false }: { latex: string; isInline?: boolean }) { - return isInline ? ( - - ) : ( - { - return ( -
- {error.name}: {error.message} -
- ); - }} - > - {latex} -
- ); -} - -export default KatexMath; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/katex_math/index.css b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/katex_math/index.css deleted file mode 100644 index d127dc343b..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/katex_math/index.css +++ /dev/null @@ -1,4 +0,0 @@ - -.katex-html { - white-space: normal; -} \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/keyboard_navigation/KeyboardNavigation.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/keyboard_navigation/KeyboardNavigation.tsx deleted file mode 100644 index 7db90c4e8f..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/keyboard_navigation/KeyboardNavigation.tsx +++ /dev/null @@ -1,317 +0,0 @@ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { MenuItem, Typography } from '@mui/material'; -import { scrollIntoView } from '$app/components/_shared/keyboard_navigation/utils'; -import { useTranslation } from 'react-i18next'; - -/** - * The option of the keyboard navigation - * the options will be flattened - * - key: the key of the option - * - content: the content of the option - * - children: the children of the option - */ -export interface KeyboardNavigationOption { - key: T; - content?: React.ReactNode; - children?: KeyboardNavigationOption[]; - disabled?: boolean; -} - -/** - * - scrollRef: the scrollable element - * - focusRef: the element to focus when the keyboard navigation is disabled - * - options: the options to navigate - * - onSelected: called when an option is selected(hovered) - * - onConfirm: called when an option is confirmed - * - onEscape: called when the escape key is pressed - * - onPressRight: called when the right arrow is pressed - * - onPressLeft: called when the left arrow key is pressed - * - disableFocus: disable the focus on the keyboard navigation - * - disableSelect: disable selecting an option when the options are initialized - * - onKeyDown: called when a key is pressed - * - defaultFocusedKey: the default focused key - * - onFocus: called when the keyboard navigation is focused - * - onBlur: called when the keyboard navigation is blurred - */ -export interface KeyboardNavigationProps { - scrollRef?: React.RefObject; - focusRef?: React.RefObject; - options: KeyboardNavigationOption[]; - onSelected?: (optionKey: T) => void; - onConfirm?: (optionKey: T) => void; - onEscape?: () => void; - onPressRight?: (optionKey: T) => void; - onPressLeft?: (optionKey: T) => void; - disableFocus?: boolean; - disableSelect?: boolean; - onKeyDown?: (e: KeyboardEvent) => void; - defaultFocusedKey?: T; - onFocus?: () => void; - onBlur?: () => void; - itemClassName?: string; - itemStyle?: React.CSSProperties; - renderNoResult?: () => React.ReactNode; -} - -function KeyboardNavigation({ - defaultFocusedKey, - onPressRight, - onPressLeft, - onEscape, - onConfirm, - scrollRef, - options, - onSelected, - focusRef, - disableFocus = false, - onKeyDown: onPropsKeyDown, - disableSelect = false, - onBlur, - onFocus, - itemClassName, - itemStyle, - renderNoResult, -}: KeyboardNavigationProps) { - const { t } = useTranslation(); - const ref = useRef(null); - const mouseY = useRef(null); - const defaultKeyRef = useRef(defaultFocusedKey); - // flatten the options - const flattenOptions = useMemo(() => { - return options.flatMap((group) => { - if (group.children) { - return group.children; - } - - return [group]; - }); - }, [options]); - - const [focusedKey, setFocusedKey] = useState(); - - const firstOptionKey = useMemo(() => { - if (disableSelect) return; - const firstOption = flattenOptions.find((option) => !option.disabled); - - return firstOption?.key; - }, [flattenOptions, disableSelect]); - - // set the default focused key when the options are initialized - useEffect(() => { - if (defaultKeyRef.current) { - setFocusedKey(defaultKeyRef.current); - defaultKeyRef.current = undefined; - return; - } - - setFocusedKey(firstOptionKey); - }, [firstOptionKey]); - - // call the onSelected callback when the focused key is changed - useEffect(() => { - if (focusedKey === undefined) return; - onSelected?.(focusedKey); - - const scrollElement = scrollRef?.current; - - if (!scrollElement) return; - - const dom = ref.current?.querySelector(`[data-key="${focusedKey}"]`); - - if (!dom) return; - // scroll the focused option into view - requestAnimationFrame(() => { - scrollIntoView(dom as HTMLDivElement, scrollElement); - }); - }, [focusedKey, onSelected, scrollRef]); - - const onKeyDown = useCallback( - (e: KeyboardEvent) => { - onPropsKeyDown?.(e); - e.stopPropagation(); - const key = e.key; - - if (key === 'Tab') { - e.preventDefault(); - return; - } - - if (key === 'Escape') { - e.preventDefault(); - onEscape?.(); - return; - } - - if (focusedKey === undefined) return; - const focusedIndex = flattenOptions.findIndex((option) => option?.key === focusedKey); - const nextIndex = (focusedIndex + 1) % flattenOptions.length; - const prevIndex = (focusedIndex - 1 + flattenOptions.length) % flattenOptions.length; - - switch (key) { - // move the focus to the previous option - case 'ArrowUp': { - e.preventDefault(); - - const prevKey = flattenOptions[prevIndex]?.key; - - setFocusedKey(prevKey); - - break; - } - - // move the focus to the next option - case 'ArrowDown': { - e.preventDefault(); - const nextKey = flattenOptions[nextIndex]?.key; - - setFocusedKey(nextKey); - break; - } - - case 'ArrowRight': - if (onPressRight) { - e.preventDefault(); - onPressRight(focusedKey); - } - - break; - case 'ArrowLeft': - if (onPressLeft) { - e.preventDefault(); - onPressLeft(focusedKey); - } - - break; - // confirm the focused option - case 'Enter': { - e.preventDefault(); - const disabled = flattenOptions[focusedIndex]?.disabled; - - if (!disabled) { - onConfirm?.(focusedKey); - } - - break; - } - - default: - break; - } - }, - [flattenOptions, focusedKey, onConfirm, onEscape, onPressLeft, onPressRight, onPropsKeyDown] - ); - - const renderOption = useCallback( - (option: KeyboardNavigationOption, index: number) => { - const hasChildren = option.children; - - const isFocused = focusedKey === option.key; - - return ( -
- {hasChildren ? ( - // render the group name - option.content &&
{option.content}
- ) : ( - // render the option - { - mouseY.current = e.clientY; - }} - onMouseEnter={(e) => { - onFocus?.(); - if (mouseY.current === null || mouseY.current !== e.clientY) { - setFocusedKey(option.key); - } - - mouseY.current = e.clientY; - }} - onClick={() => { - setFocusedKey(option.key); - if (!option.disabled) { - onConfirm?.(option.key); - } - }} - selected={isFocused} - style={itemStyle} - className={`ml-0 flex w-full items-center justify-start rounded-none px-2 py-1 text-xs ${ - !isFocused ? 'hover:bg-transparent' : '' - } ${itemClassName ?? ''}`} - > - {option.content} - - )} - - {option.children?.map((child, childIndex) => { - return renderOption(child, index + childIndex); - })} -
- ); - }, - [itemClassName, focusedKey, onConfirm, onFocus, itemStyle] - ); - - useEffect(() => { - const element = ref.current; - - if (!disableFocus && element) { - element.focus(); - element.addEventListener('keydown', onKeyDown); - - return () => { - element.removeEventListener('keydown', onKeyDown); - }; - } else { - let element: HTMLElement | null | undefined = focusRef?.current; - - if (!element) { - element = document.activeElement as HTMLElement; - } - - element?.addEventListener('keydown', onKeyDown); - return () => { - element?.removeEventListener('keydown', onKeyDown); - }; - } - }, [disableFocus, onKeyDown, focusRef]); - - return ( -
{ - e.stopPropagation(); - - onFocus?.(); - }} - onBlur={(e) => { - e.stopPropagation(); - - const target = e.relatedTarget as HTMLElement; - - if (target?.closest('.keyboard-navigation')) { - return; - } - - onBlur?.(); - }} - autoFocus={!disableFocus} - className={'keyboard-navigation flex w-full flex-col gap-1 outline-none'} - ref={ref} - > - {options.length > 0 ? ( - options.map(renderOption) - ) : renderNoResult ? ( - renderNoResult() - ) : ( - - {t('findAndReplace.noResult')} - - )} -
- ); -} - -export default KeyboardNavigation; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/keyboard_navigation/utils.ts b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/keyboard_navigation/utils.ts deleted file mode 100644 index 621143869b..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/keyboard_navigation/utils.ts +++ /dev/null @@ -1,32 +0,0 @@ -export function inView(dom: HTMLElement, container: HTMLElement) { - const domRect = dom.getBoundingClientRect(); - const containerRect = container.getBoundingClientRect(); - - if (!domRect || !containerRect) return true; - - return domRect?.bottom <= containerRect?.bottom && domRect?.top >= containerRect?.top; -} - -export function getDistanceEdge(dom: HTMLElement, container: HTMLElement) { - const domRect = dom.getBoundingClientRect(); - const containerRect = container.getBoundingClientRect(); - - if (!domRect || !containerRect) return 0; - - const distanceTop = domRect?.top - containerRect?.top; - const distanceBottom = domRect?.bottom - containerRect?.bottom; - - return Math.abs(distanceTop) < Math.abs(distanceBottom) ? distanceTop : distanceBottom; -} - -export function scrollIntoView(dom: HTMLElement, container: HTMLElement) { - const isDomInView = inView(dom, container); - - if (isDomInView) return; - - dom.scrollIntoView({ - block: 'nearest', - inline: 'nearest', - behavior: 'smooth', - }); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/notify/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/notify/index.ts deleted file mode 100644 index 1086cabdfd..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/notify/index.ts +++ /dev/null @@ -1,27 +0,0 @@ -import toast from 'react-hot-toast'; - -const commonOptions = { - style: { - background: 'var(--bg-base)', - color: 'var(--text-title)', - shadows: 'var(--shadow)', - }, -}; - -export const notify = { - success: (message: string) => { - toast.success(message, commonOptions); - }, - error: (message: string) => { - toast.error(message, commonOptions); - }, - loading: (message: string) => { - toast.loading(message, commonOptions); - }, - info: (message: string) => { - toast(message, commonOptions); - }, - clear: () => { - toast.dismiss(); - }, -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/popover/Popover.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/popover/Popover.hooks.ts deleted file mode 100644 index 0fc1b5e61e..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/popover/Popover.hooks.ts +++ /dev/null @@ -1,237 +0,0 @@ -import { useState, useEffect, useCallback } from 'react'; -import { PopoverOrigin } from '@mui/material/Popover/Popover'; - -interface PopoverPosition { - anchorOrigin: PopoverOrigin; - transformOrigin: PopoverOrigin; - paperWidth: number; - paperHeight: number; - isEntered: boolean; - anchorPosition?: { left: number; top: number }; -} - -interface UsePopoverAutoPositionProps { - anchorEl?: HTMLElement | null; - anchorPosition?: { left: number; top: number; height: number }; - initialAnchorOrigin?: PopoverOrigin; - initialTransformOrigin?: PopoverOrigin; - initialPaperWidth: number; - initialPaperHeight: number; - marginThreshold?: number; - open: boolean; - anchorSize?: { width: number; height: number }; -} - -const minPaperWidth = 80; -const minPaperHeight = 120; - -function getOffsetLeft( - rect: { - height: number; - width: number; - }, - paperWidth: number, - horizontal: number | 'center' | 'left' | 'right', - transformHorizontal: number | 'center' | 'left' | 'right' -) { - let offset = 0; - - if (typeof horizontal === 'number') { - offset = horizontal; - } else if (horizontal === 'center') { - offset = rect.width / 2; - } else if (horizontal === 'right') { - offset = rect.width; - } - - if (transformHorizontal === 'center') { - offset -= paperWidth / 2; - } else if (transformHorizontal === 'right') { - offset -= paperWidth; - } - - return offset; -} - -function getOffsetTop( - rect: { - height: number; - width: number; - }, - papertHeight: number, - vertical: number | 'center' | 'bottom' | 'top', - transformVertical: number | 'center' | 'bottom' | 'top' -) { - let offset = 0; - - if (typeof vertical === 'number') { - offset = vertical; - } else if (vertical === 'center') { - offset = rect.height / 2; - } else if (vertical === 'bottom') { - offset = rect.height; - } - - if (transformVertical === 'center') { - offset -= papertHeight / 2; - } else if (transformVertical === 'bottom') { - offset -= papertHeight; - } - - return offset; -} - -const defaultAnchorOrigin: PopoverOrigin = { - vertical: 'top', - horizontal: 'left', -}; - -const defaultTransformOrigin: PopoverOrigin = { - vertical: 'top', - horizontal: 'left', -}; - -const usePopoverAutoPosition = ({ - anchorEl, - anchorPosition, - initialAnchorOrigin = defaultAnchorOrigin, - initialTransformOrigin = defaultTransformOrigin, - initialPaperWidth, - initialPaperHeight, - marginThreshold = 16, - open, -}: UsePopoverAutoPositionProps): PopoverPosition & { - calculateAnchorSize: () => void; -} => { - const [position, setPosition] = useState({ - anchorOrigin: initialAnchorOrigin, - transformOrigin: initialTransformOrigin, - paperWidth: initialPaperWidth, - paperHeight: initialPaperHeight, - anchorPosition, - isEntered: false, - }); - - const calculateAnchorSize = useCallback(() => { - const viewportWidth = window.innerWidth; - const viewportHeight = window.innerHeight; - - const getAnchorOffset = () => { - if (anchorPosition) { - return { - ...anchorPosition, - width: 0, - }; - } - - return anchorEl ? anchorEl.getBoundingClientRect() : undefined; - }; - - const anchorRect = getAnchorOffset(); - - if (!anchorRect) return; - let newPaperWidth = initialPaperWidth; - let newPaperHeight = initialPaperHeight; - const newAnchorPosition = { - top: anchorRect.top, - left: anchorRect.left, - }; - - // calculate new paper width - const newLeft = - anchorRect.left + - getOffsetLeft(anchorRect, newPaperWidth, initialAnchorOrigin.horizontal, initialTransformOrigin.horizontal); - const newTop = - anchorRect.top + - getOffsetTop(anchorRect, newPaperHeight, initialAnchorOrigin.vertical, initialTransformOrigin.vertical); - - let isExceedViewportRight = false; - let isExceedViewportBottom = false; - let isExceedViewportLeft = false; - let isExceedViewportTop = false; - - // Check if exceed viewport right - if (newLeft + newPaperWidth > viewportWidth - marginThreshold) { - isExceedViewportRight = true; - // Check if exceed viewport left - if (newLeft - newPaperWidth < marginThreshold) { - isExceedViewportLeft = true; - newPaperWidth = Math.max(minPaperWidth, Math.min(newPaperWidth, viewportWidth - newLeft - marginThreshold)); - } - } - - // Check if exceed viewport bottom - if (newTop + newPaperHeight > viewportHeight - marginThreshold) { - isExceedViewportBottom = true; - // Check if exceed viewport top - if (newTop - newPaperHeight < marginThreshold) { - isExceedViewportTop = true; - newPaperHeight = Math.max(minPaperHeight, Math.min(newPaperHeight, viewportHeight - newTop - marginThreshold)); - } - } - - const newPosition = { - anchorOrigin: { ...initialAnchorOrigin }, - transformOrigin: { ...initialTransformOrigin }, - paperWidth: newPaperWidth, - paperHeight: newPaperHeight, - anchorPosition: newAnchorPosition, - }; - - // If exceed viewport, adjust anchor origin and transform origin - if (!isExceedViewportRight && !isExceedViewportLeft) { - if (isExceedViewportBottom && !isExceedViewportTop) { - newPosition.anchorOrigin.vertical = 'top'; - newPosition.transformOrigin.vertical = 'bottom'; - } else if (!isExceedViewportBottom && isExceedViewportTop) { - newPosition.anchorOrigin.vertical = 'bottom'; - newPosition.transformOrigin.vertical = 'top'; - } - } else if (!isExceedViewportBottom && !isExceedViewportTop) { - if (isExceedViewportRight && !isExceedViewportLeft) { - newPosition.anchorOrigin.horizontal = 'left'; - newPosition.transformOrigin.horizontal = 'right'; - } else if (!isExceedViewportRight && isExceedViewportLeft) { - newPosition.anchorOrigin.horizontal = 'right'; - newPosition.transformOrigin.horizontal = 'left'; - } - } - - // anchorPosition is top-left of the anchor element, so we need to adjust it to avoid overlap with the anchor element - if (newPosition.anchorOrigin.vertical === 'bottom' && newPosition.transformOrigin.vertical === 'top') { - newPosition.anchorPosition.top += anchorRect.height; - } - - if ( - isExceedViewportTop && - isExceedViewportBottom && - newPosition.anchorOrigin.vertical === 'top' && - newPosition.transformOrigin.vertical === 'bottom' - ) { - newPosition.paperHeight = newPaperHeight - anchorRect.height; - } - - // Set new position and set isEntered to true - setPosition({ ...newPosition, isEntered: true }); - }, [ - initialAnchorOrigin, - initialTransformOrigin, - initialPaperWidth, - initialPaperHeight, - marginThreshold, - anchorEl, - anchorPosition, - ]); - - useEffect(() => { - if (!open) return; - calculateAnchorSize(); - }, [open, calculateAnchorSize]); - - return { - ...position, - calculateAnchorSize, - }; -}; - -export default usePopoverAutoPosition; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/popover/utils.ts b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/popover/utils.ts deleted file mode 100644 index dccaf2f4d4..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/popover/utils.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { PopoverOrigin } from '@mui/material/Popover/Popover'; - -export function getOffsetTop(rect: DOMRect, vertical: number | 'center' | 'bottom' | 'top') { - let offset = 0; - - if (typeof vertical === 'number') { - offset = vertical; - } else if (vertical === 'center') { - offset = rect.height / 2; - } else if (vertical === 'bottom') { - offset = rect.height; - } - - return offset; -} - -export function getOffsetLeft(rect: DOMRect, horizontal: number | 'center' | 'left' | 'right') { - let offset = 0; - - if (typeof horizontal === 'number') { - offset = horizontal; - } else if (horizontal === 'center') { - offset = rect.width / 2; - } else if (horizontal === 'right') { - offset = rect.width; - } - - return offset; -} - -export function getAnchorOffset(anchorElement: HTMLElement, anchorOrigin: PopoverOrigin) { - const anchorRect = anchorElement.getBoundingClientRect(); - - return { - top: anchorRect.top + getOffsetTop(anchorRect, anchorOrigin.vertical), - left: anchorRect.left + getOffsetLeft(anchorRect, anchorOrigin.horizontal), - }; -} - -export function getTransformOrigin(elemRect: DOMRect, transformOrigin: PopoverOrigin) { - return { - vertical: getOffsetTop(elemRect, transformOrigin.vertical), - horizontal: getOffsetLeft(elemRect, transformOrigin.horizontal), - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/scroller/AFScroller.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/scroller/AFScroller.tsx deleted file mode 100644 index 0527b6cc26..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/scroller/AFScroller.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { Scrollbars } from 'react-custom-scrollbars'; -import React from 'react'; - -export interface AFScrollerProps { - children: React.ReactNode; - overflowXHidden?: boolean; - overflowYHidden?: boolean; - className?: string; - style?: React.CSSProperties; -} -export const AFScroller = ({ style, children, overflowXHidden, overflowYHidden, className }: AFScrollerProps) => { - return ( -
} - renderThumbVertical={(props) =>
} - {...(overflowXHidden && { - renderTrackHorizontal: (props) => ( -
- ), - })} - {...(overflowYHidden && { - renderTrackVertical: (props) => ( -
- ), - })} - style={style} - renderView={(props) => ( -
- )} - > - {children} - - ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/scroller/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/scroller/index.ts deleted file mode 100644 index 7a740a5bb0..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/scroller/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './AFScroller'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/view_title/ViewBanner.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/view_title/ViewBanner.tsx deleted file mode 100644 index 95e44ae9c2..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/view_title/ViewBanner.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import ViewIconGroup from '$app/components/_shared/view_title/ViewIconGroup'; -import { PageCover, PageIcon } from '$app_reducers/pages/slice'; -import ViewIcon from '$app/components/_shared/view_title/ViewIcon'; -import { ViewCover } from '$app/components/_shared/view_title/cover'; - -function ViewBanner({ - icon, - hover, - onUpdateIcon, - showCover, - cover, - onUpdateCover, -}: { - icon?: PageIcon; - hover: boolean; - onUpdateIcon: (icon: string) => void; - showCover: boolean; - cover?: PageCover; - onUpdateCover?: (cover?: PageCover) => void; -}) { - return ( -
- {showCover && cover && } - -
-
- -
-
- -
-
-
- ); -} - -export default ViewBanner; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/view_title/ViewIcon.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/view_title/ViewIcon.tsx deleted file mode 100644 index 009548df53..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/view_title/ViewIcon.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import React, { useCallback, useState } from 'react'; -import Popover from '@mui/material/Popover'; -import EmojiPicker from '$app/components/_shared/emoji_picker/EmojiPicker'; -import { PageIcon } from '$app_reducers/pages/slice'; - -function ViewIcon({ icon, onUpdateIcon }: { icon?: PageIcon; onUpdateIcon: (icon: string) => void }) { - const [anchorPosition, setAnchorPosition] = useState<{ - top: number; - left: number; - }>(); - - const open = Boolean(anchorPosition); - const onOpen = useCallback((event: React.MouseEvent) => { - const rect = event.currentTarget.getBoundingClientRect(); - - setAnchorPosition({ - top: rect.top + rect.height, - left: rect.left, - }); - }, []); - - const onEmojiSelect = useCallback( - (emoji: string) => { - onUpdateIcon(emoji); - if (!emoji) { - setAnchorPosition(undefined); - } - }, - [onUpdateIcon] - ); - - if (!icon) return null; - return ( - <> -
-
- {icon.value} -
-
- {open && ( - setAnchorPosition(undefined)} - > - { - setAnchorPosition(undefined); - }} - onEmojiSelect={onEmojiSelect} - /> - - )} - - ); -} - -export default ViewIcon; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/view_title/ViewIconGroup.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/view_title/ViewIconGroup.tsx deleted file mode 100644 index 54256f8eb1..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/view_title/ViewIconGroup.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { useTranslation } from 'react-i18next'; -import { CoverType, PageCover, PageIcon } from '$app_reducers/pages/slice'; -import React, { useCallback } from 'react'; -import { randomEmoji } from '$app/utils/emoji'; -import { EmojiEmotionsOutlined } from '@mui/icons-material'; -import Button from '@mui/material/Button'; -import { ReactComponent as ImageIcon } from '$app/assets/image.svg'; -import { ImageType } from '$app/application/document/document.types'; - -interface Props { - icon?: PageIcon; - onUpdateIcon: (icon: string) => void; - showCover: boolean; - cover?: PageCover; - onUpdateCover?: (cover: PageCover) => void; -} - -const defaultCover = { - cover_selection_type: CoverType.Asset, - cover_selection: 'app_flowy_abstract_cover_2.jpeg', - image_type: ImageType.Internal, -}; - -function ViewIconGroup({ icon, onUpdateIcon, showCover, cover, onUpdateCover }: Props) { - const { t } = useTranslation(); - - const showAddIcon = !icon?.value; - - const showAddCover = !cover && showCover; - - const onAddIcon = useCallback(() => { - const emoji = randomEmoji(); - - onUpdateIcon(emoji); - }, [onUpdateIcon]); - - const onAddCover = useCallback(() => { - onUpdateCover?.(defaultCover); - }, [onUpdateCover]); - - return ( -
- {showAddIcon && ( - - )} - {showAddCover && ( - - )} -
- ); -} - -export default ViewIconGroup; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/view_title/ViewTitle.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/view_title/ViewTitle.tsx deleted file mode 100644 index 8d81b6d4b7..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/view_title/ViewTitle.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import React, { useCallback, useEffect, useState } from 'react'; -import ViewBanner from '$app/components/_shared/view_title/ViewBanner'; -import { Page, PageCover, PageIcon } from '$app_reducers/pages/slice'; -import { ViewIconTypePB } from '@/services/backend'; -import ViewTitleInput from '$app/components/_shared/view_title/ViewTitleInput'; - -interface Props { - view: Page; - showTitle?: boolean; - onTitleChange?: (title: string) => void; - onUpdateIcon?: (icon: PageIcon) => void; - forceHover?: boolean; - showCover?: boolean; - onUpdateCover?: (cover?: PageCover) => void; -} - -function ViewTitle({ - view, - forceHover = false, - onTitleChange, - showTitle = true, - onUpdateIcon: onUpdateIconProp, - showCover = false, - onUpdateCover, -}: Props) { - const [hover, setHover] = useState(false); - const [icon, setIcon] = useState(view.icon); - - useEffect(() => { - setIcon(view.icon); - }, [view.icon]); - - const onUpdateIcon = useCallback( - (icon: string) => { - const newIcon = { - value: icon, - ty: ViewIconTypePB.Emoji, - }; - - setIcon(newIcon); - onUpdateIconProp?.(newIcon); - }, - [onUpdateIconProp] - ); - - return ( -
setHover(true)} - onMouseLeave={() => setHover(false)} - > - - {showTitle && ( -
- -
- )} -
- ); -} - -export default ViewTitle; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/view_title/ViewTitleInput.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/view_title/ViewTitleInput.tsx deleted file mode 100644 index 2c69bb4d76..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/view_title/ViewTitleInput.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React, { FormEventHandler, memo, useCallback, useRef } from 'react'; -import { TextareaAutosize } from '@mui/material'; -import { useTranslation } from 'react-i18next'; - -function ViewTitleInput({ value, onChange }: { value: string; onChange?: (value: string) => void }) { - const { t } = useTranslation(); - const textareaRef = useRef(null); - - const onTitleChange: FormEventHandler = useCallback( - (e) => { - const value = e.currentTarget.value; - - onChange?.(value); - }, - [onChange] - ); - - return ( - - ); -} - -export default memo(ViewTitleInput); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/view_title/cover/Colors.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/view_title/cover/Colors.tsx deleted file mode 100644 index 78b8bbcc46..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/view_title/cover/Colors.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from 'react'; -import { colorMap } from '$app/utils/color'; - -const colors = Object.entries(colorMap); - -function Colors({ onDone }: { onDone?: (value: string) => void }) { - return ( -
- {colors.map(([name, value]) => ( -
onDone?.(name)} - /> - ))} -
- ); -} - -export default Colors; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/view_title/cover/CoverPopover.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/view_title/cover/CoverPopover.tsx deleted file mode 100644 index bd8c178380..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/view_title/cover/CoverPopover.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import React, { useMemo } from 'react'; -import { CoverType, PageCover } from '$app_reducers/pages/slice'; -import { PopoverOrigin } from '@mui/material/Popover'; -import { EmbedLink, Unsplash, UploadTabs, TabOption, TAB_KEY, UploadImage } from '$app/components/_shared/image_upload'; -import { useTranslation } from 'react-i18next'; -import Colors from '$app/components/_shared/view_title/cover/Colors'; -import { ImageType } from '$app/application/document/document.types'; -import Button from '@mui/material/Button'; - -const initialOrigin: { - anchorOrigin: PopoverOrigin; - transformOrigin: PopoverOrigin; -} = { - anchorOrigin: { - vertical: 'bottom', - horizontal: 'center', - }, - transformOrigin: { - vertical: 'top', - horizontal: 'center', - }, -}; - -function CoverPopover({ - anchorEl, - open, - onClose, - onUpdateCover, - onRemoveCover, -}: { - anchorEl: HTMLElement | null; - open: boolean; - onClose: () => void; - onUpdateCover?: (cover?: PageCover) => void; - onRemoveCover?: () => void; -}) { - const { t } = useTranslation(); - const tabOptions: TabOption[] = useMemo(() => { - return [ - { - label: t('document.plugins.cover.colors'), - key: TAB_KEY.Colors, - Component: Colors, - onDone: (value: string) => { - onUpdateCover?.({ - cover_selection_type: CoverType.Color, - cover_selection: value, - image_type: ImageType.Internal, - }); - }, - }, - { - label: t('button.upload'), - key: TAB_KEY.UPLOAD, - Component: UploadImage, - onDone: (value: string) => { - onUpdateCover?.({ - cover_selection_type: CoverType.Image, - cover_selection: value, - image_type: ImageType.Local, - }); - onClose(); - }, - }, - { - label: t('document.imageBlock.embedLink.label'), - key: TAB_KEY.EMBED_LINK, - Component: EmbedLink, - onDone: (value: string) => { - onUpdateCover?.({ - cover_selection_type: CoverType.Image, - cover_selection: value, - image_type: ImageType.External, - }); - onClose(); - }, - }, - { - key: TAB_KEY.UNSPLASH, - label: t('document.imageBlock.unsplash.label'), - Component: Unsplash, - onDone: (value: string) => { - onUpdateCover?.({ - cover_selection_type: CoverType.Image, - cover_selection: value, - image_type: ImageType.External, - }); - }, - }, - ]; - }, [onClose, onUpdateCover, t]); - - return ( - - {t('button.remove')} - - } - /> - ); -} - -export default CoverPopover; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/view_title/cover/ViewCover.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/view_title/cover/ViewCover.tsx deleted file mode 100644 index f207e07886..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/view_title/cover/ViewCover.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import React, { useCallback, useMemo, useRef, useState } from 'react'; -import { CoverType, PageCover } from '$app_reducers/pages/slice'; -import { renderColor } from '$app/utils/color'; -import ViewCoverActions from '$app/components/_shared/view_title/cover/ViewCoverActions'; -import CoverPopover from '$app/components/_shared/view_title/cover/CoverPopover'; -import DefaultImage from '$app/assets/images/default_cover.jpg'; -import { ImageType } from '$app/application/document/document.types'; -import { LocalImage } from '$app/components/_shared/image_upload'; - -export function ViewCover({ cover, onUpdateCover }: { cover: PageCover; onUpdateCover?: (cover?: PageCover) => void }) { - const { - cover_selection_type: type, - cover_selection: value = '', - image_type: source, - } = useMemo(() => cover || {}, [cover]); - const [showAction, setShowAction] = useState(false); - const actionRef = useRef(null); - const [showPopover, setShowPopover] = useState(false); - - const renderCoverColor = useCallback((color: string) => { - return ( -
- ); - }, []); - - const renderCoverImage = useCallback((url: string) => { - return {''}; - }, []); - - const handleRemoveCover = useCallback(() => { - onUpdateCover?.(null); - }, [onUpdateCover]); - - const handleClickChange = useCallback(() => { - setShowPopover(true); - }, []); - - return ( -
{ - setShowAction(true); - }} - onMouseLeave={() => { - setShowAction(false); - }} - className={'relative flex h-[255px] w-full'} - > - {source === ImageType.Local ? ( - - ) : ( - <> - {type === CoverType.Asset ? renderCoverImage(DefaultImage) : null} - {type === CoverType.Color ? renderCoverColor(value) : null} - {type === CoverType.Image ? renderCoverImage(value) : null} - - )} - - - {showPopover && ( - setShowPopover(false)} - anchorEl={actionRef.current} - onUpdateCover={onUpdateCover} - onRemoveCover={handleRemoveCover} - /> - )} -
- ); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/view_title/cover/ViewCoverActions.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/view_title/cover/ViewCoverActions.tsx deleted file mode 100644 index fbf8063f44..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/view_title/cover/ViewCoverActions.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import React, { forwardRef } from 'react'; -import Button from '@mui/material/Button'; -import { useTranslation } from 'react-i18next'; -import { ReactComponent as DeleteIcon } from '$app/assets/delete.svg'; - -function ViewCoverActions( - { show, onRemove, onClickChange }: { show: boolean; onRemove: () => void; onClickChange: () => void }, - ref: React.ForwardedRef -) { - const { t } = useTranslation(); - - return ( -
-
-
- -
-
-
- ); -} - -export default forwardRef(ViewCoverActions); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/view_title/cover/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/_shared/view_title/cover/index.ts deleted file mode 100644 index 8df50bb41e..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/_shared/view_title/cover/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ViewCover'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/auth/LoginButtonGroup.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/auth/LoginButtonGroup.tsx deleted file mode 100644 index 481b80a532..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/auth/LoginButtonGroup.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import Button from '@mui/material/Button'; -import GoogleIcon from '$app/assets/settings/google.png'; -import GithubIcon from '$app/assets/settings/github.png'; -import DiscordIcon from '$app/assets/settings/discord.png'; -import { useTranslation } from 'react-i18next'; -import { useAuth } from '$app/components/auth/auth.hooks'; -import { ProviderTypePB } from '@/services/backend'; - -export const LoginButtonGroup = () => { - const { t } = useTranslation(); - - const { signIn } = useAuth(); - - return ( -
- - - -
- ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/auth/ProtectedRoutes.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/auth/ProtectedRoutes.tsx deleted file mode 100644 index 523f0b5188..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/auth/ProtectedRoutes.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import { Outlet } from 'react-router-dom'; -import { useAuth } from './auth.hooks'; -import Layout from '$app/components/layout/Layout'; -import { useCallback, useEffect, useState } from 'react'; -import { Welcome } from '$app/components/auth/Welcome'; -import { isTauri } from '$app/utils/env'; -import { notify } from '$app/components/_shared/notify'; -import { currentUserActions, LoginState } from '$app_reducers/current-user/slice'; -import { CircularProgress, Portal } from '@mui/material'; -import { ReactComponent as Logo } from '$app/assets/logo.svg'; -import { useAppDispatch } from '$app/stores/store'; - -export const ProtectedRoutes = () => { - const { currentUser, checkUser, subscribeToUser, signInWithOAuth } = useAuth(); - const dispatch = useAppDispatch(); - - const isLoading = currentUser?.loginState === LoginState.Loading; - - const [checked, setChecked] = useState(false); - - const checkUserStatus = useCallback(async () => { - await checkUser(); - setChecked(true); - }, [checkUser]); - - useEffect(() => { - void checkUserStatus(); - }, [checkUserStatus]); - - useEffect(() => { - if (currentUser.isAuthenticated) { - return subscribeToUser(); - } - }, [currentUser.isAuthenticated, subscribeToUser]); - - const onDeepLink = useCallback(async () => { - if (!isTauri()) return; - const { event } = await import('@tauri-apps/api'); - - // On macOS You still have to install a .app bundle you got from tauri build --debug for this to work! - return await event.listen('open_deep_link', async (e) => { - const payload = e.payload as string; - - const [, hash] = payload.split('//#'); - const obj = parseHash(hash); - - if (!obj.access_token) { - notify.error('Failed to sign in, the access token is missing'); - dispatch(currentUserActions.setLoginState(LoginState.Error)); - return; - } - - try { - await signInWithOAuth(payload); - } catch (e) { - notify.error('Failed to sign in, please try again'); - } - }); - }, [dispatch, signInWithOAuth]); - - useEffect(() => { - void onDeepLink(); - }, [onDeepLink]); - - return ( -
- {checked ? ( - - ) : ( -
- -
- )} - - {isLoading && } -
- ); -}; - -const StartLoading = () => { - const dispatch = useAppDispatch(); - - useEffect(() => { - const preventDefault = (e: KeyboardEvent) => { - if (e.key === 'Escape') { - e.preventDefault(); - dispatch(currentUserActions.resetLoginState()); - } - }; - - document.addEventListener('keydown', preventDefault, true); - - return () => { - document.removeEventListener('keydown', preventDefault, true); - }; - }, [dispatch]); - return ( - -
- -
-
- ); -}; - -const SplashScreen = ({ isAuthenticated }: { isAuthenticated: boolean }) => { - if (isAuthenticated) { - return ( - - - - ); - } else { - return ; - } -}; - -function parseHash(hash: string) { - const hashParams = new URLSearchParams(hash); - const hashObject: Record = {}; - - for (const [key, value] of hashParams) { - hashObject[key] = value; - } - - return hashObject; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/auth/Welcome.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/auth/Welcome.tsx deleted file mode 100644 index eadcf08c21..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/auth/Welcome.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { ReactComponent as AppflowyLogo } from '$app/assets/logo.svg'; -import Button from '@mui/material/Button'; -import { useTranslation } from 'react-i18next'; -import { LoginButtonGroup } from '$app/components/auth/LoginButtonGroup'; -import { useAuth } from '$app/components/auth/auth.hooks'; -import { Log } from '$app/utils/log'; - -export const Welcome = () => { - const { signInAsAnonymous } = useAuth(); - const { t } = useTranslation(); - - return ( - <> -
e.preventDefault()} method='POST'> -
-
- -
- -
- - {t('welcomeTo')} {t('appName')} - -
- -
- -
-
- {t('signIn.or')} -
-
-
- -
-
-
- - - ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/auth/auth.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/auth/auth.hooks.ts deleted file mode 100644 index 89b7388e64..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/auth/auth.hooks.ts +++ /dev/null @@ -1,186 +0,0 @@ -import { currentUserActions, LoginState, parseWorkspaceSettingPBToSetting } from '$app_reducers/current-user/slice'; -import { AuthenticatorPB, ProviderTypePB, UserNotification, UserProfilePB } from '@/services/backend/events/flowy-user'; -import { UserService } from '$app/application/user/user.service'; -import { AuthService } from '$app/application/user/auth.service'; -import { useAppDispatch, useAppSelector } from '$app/stores/store'; -import { getCurrentWorkspaceSetting } from '$app/application/folder/workspace.service'; -import { useCallback } from 'react'; -import { subscribeNotifications } from '$app/application/notification'; -import { nanoid } from 'nanoid'; -import { open } from '@tauri-apps/api/shell'; - -export const useAuth = () => { - const dispatch = useAppDispatch(); - const currentUser = useAppSelector((state) => state.currentUser); - - // Subscribe to user update events - const subscribeToUser = useCallback(() => { - const unsubscribePromise = subscribeNotifications({ - [UserNotification.DidUpdateUserProfile]: async (changeset) => { - dispatch( - currentUserActions.updateUser({ - email: changeset.email, - displayName: changeset.name, - iconUrl: changeset.icon_url, - }) - ); - }, - }); - - return () => { - void unsubscribePromise.then((fn) => fn()); - }; - }, [dispatch]); - - const setUser = useCallback( - async (userProfile?: Partial) => { - if (!userProfile) return; - - const workspaceSetting = await getCurrentWorkspaceSetting(); - - const isLocal = userProfile.authenticator === AuthenticatorPB.Local; - - dispatch( - currentUserActions.updateUser({ - id: userProfile.id, - token: userProfile.token, - email: userProfile.email, - displayName: userProfile.name, - iconUrl: userProfile.icon_url, - isAuthenticated: true, - workspaceSetting: workspaceSetting ? parseWorkspaceSettingPBToSetting(workspaceSetting) : undefined, - isLocal, - }) - ); - }, - [dispatch] - ); - - // Check if the user is authenticated - const checkUser = useCallback(async () => { - const userProfile = await UserService.getUserProfile(); - - await setUser(userProfile); - - return userProfile; - }, [setUser]); - - const register = useCallback( - async (email: string, password: string, name: string): Promise => { - const deviceId = currentUser?.deviceId ?? nanoid(8); - const userProfile = await AuthService.signUp({ deviceId, email, password, name }); - - await setUser(userProfile); - - return userProfile; - }, - [setUser, currentUser?.deviceId] - ); - - const logout = useCallback(async () => { - await AuthService.signOut(); - dispatch(currentUserActions.logout()); - }, [dispatch]); - - const signInAsAnonymous = useCallback(async () => { - const fakeEmail = nanoid(8) + '@appflowy.io'; - const fakePassword = 'AppFlowy123@'; - const fakeName = 'Me'; - - await register(fakeEmail, fakePassword, fakeName); - }, [register]); - - const signIn = useCallback( - async (provider: ProviderTypePB) => { - dispatch(currentUserActions.setLoginState(LoginState.Loading)); - try { - const url = await AuthService.getOAuthURL(provider); - - await open(url); - } catch { - dispatch(currentUserActions.setLoginState(LoginState.Error)); - } - }, - [dispatch] - ); - - const signInWithOAuth = useCallback( - async (uri: string) => { - dispatch(currentUserActions.setLoginState(LoginState.Loading)); - try { - const deviceId = currentUser?.deviceId ?? nanoid(8); - - await AuthService.signInWithOAuth({ uri, deviceId }); - const userProfile = await UserService.getUserProfile(); - - await setUser(userProfile); - - return userProfile; - } catch (e) { - dispatch(currentUserActions.setLoginState(LoginState.Error)); - return Promise.reject(e); - } - }, - [dispatch, currentUser?.deviceId, setUser] - ); - - // Only for development purposes - const signInWithEmailPassword = useCallback( - async (email: string, password: string, domain?: string) => { - dispatch(currentUserActions.setLoginState(LoginState.Loading)); - - try { - const response = await fetch( - `https://${domain ? domain : 'test.appflowy.cloud'}/gotrue/token?grant_type=password`, - { - method: 'POST', - mode: 'cors', - cache: 'no-cache', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - redirect: 'follow', - referrerPolicy: 'no-referrer', - body: JSON.stringify({ - email, - password, - }), - } - ); - - const data = await response.json(); - - let uri = `appflowy-flutter://#`; - const params: string[] = []; - - Object.keys(data).forEach((key) => { - if (typeof data[key] === 'object') { - return; - } - - params.push(`${key}=${data[key]}`); - }); - uri += params.join('&'); - - return signInWithOAuth(uri); - } catch (e) { - dispatch(currentUserActions.setLoginState(LoginState.Error)); - return Promise.reject(e); - } - }, - [dispatch, signInWithOAuth] - ); - - return { - currentUser, - checkUser, - register, - logout, - subscribeToUser, - signInAsAnonymous, - signIn, - signInWithOAuth, - signInWithEmailPassword, - }; -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/Database.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/Database.hooks.ts deleted file mode 100644 index 2597c158a1..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/Database.hooks.ts +++ /dev/null @@ -1,204 +0,0 @@ -import { createContext, useCallback, useContext, useEffect, useMemo } from 'react'; -import { useSearchParams } from 'react-router-dom'; -import { proxy, useSnapshot } from 'valtio'; - -import { DatabaseLayoutPB, DatabaseNotification, FieldVisibility } from '@/services/backend'; -import { subscribeNotifications } from '$app/application/notification'; -import { - Cell, - Database, - databaseService, - cellListeners, - fieldListeners, - rowListeners, - sortListeners, - filterListeners, -} from '$app/application/database'; - -export function useSelectDatabaseView({ viewId }: { viewId?: string }) { - const key = 'v'; - const [searchParams, setSearchParams] = useSearchParams(); - - const selectedViewId = useMemo(() => searchParams.get(key) || viewId, [searchParams, viewId]); - - const onChange = useCallback( - (value: string) => { - setSearchParams({ [key]: value }); - }, - [setSearchParams] - ); - - return { - selectedViewId, - onChange, - }; -} - -const DatabaseContext = createContext({ - id: '', - isLinked: false, - layoutType: DatabaseLayoutPB.Grid, - fields: [], - rowMetas: [], - filters: [], - sorts: [], - groupSettings: [], - groups: [], - typeOptions: {}, - cells: {}, -}); - -export const DatabaseProvider = DatabaseContext.Provider; - -export const useDatabase = () => useSnapshot(useContext(DatabaseContext)); - -export const useSelectorCell = (rowId: string, fieldId: string) => { - const database = useContext(DatabaseContext); - const cells = useSnapshot(database.cells); - - return cells[`${rowId}:${fieldId}`]; -}; - -export const useDispatchCell = () => { - const database = useContext(DatabaseContext); - - const setCell = useCallback( - (cell: Cell) => { - const id = `${cell.rowId}:${cell.fieldId}`; - - database.cells[id] = cell; - }, - [database] - ); - - const deleteCells = useCallback( - ({ rowId, fieldId }: { rowId: string; fieldId?: string }) => { - cellListeners.didDeleteCells({ database, rowId, fieldId }); - }, - [database] - ); - - return { - deleteCells, - setCell, - }; -}; - -export const useDatabaseSorts = () => { - const context = useContext(DatabaseContext); - - return useSnapshot(context.sorts); -}; - -export const useSortsCount = () => { - const { sorts } = useDatabase(); - - return sorts?.length; -}; - -export const useFiltersCount = () => { - const { filters, fields } = useDatabase(); - - // filter fields: if the field is deleted, it will not be displayed - return useMemo( - () => filters?.map((filter) => fields.find((field) => field.id === filter.fieldId)).filter(Boolean).length, - [filters, fields] - ); -}; - -export function useStaticTypeOption(fieldId: string) { - const context = useContext(DatabaseContext); - const typeOptions = context.typeOptions; - - return typeOptions[fieldId] as T; -} - -export function useTypeOption(fieldId: string) { - const context = useContext(DatabaseContext); - const typeOptions = useSnapshot(context.typeOptions); - - return typeOptions[fieldId] as T; -} - -export const useDatabaseVisibilityRows = () => { - const { rowMetas } = useDatabase(); - - return useMemo(() => rowMetas.filter((row) => row && !row.isHidden), [rowMetas]); -}; - -export const useDatabaseVisibilityFields = () => { - const database = useDatabase(); - - return useMemo( - () => database.fields.filter((field) => field.visibility !== FieldVisibility.AlwaysHidden), - [database.fields] - ); -}; - -export const useConnectDatabase = (viewId: string) => { - const database = useMemo(() => { - const proxyDatabase = proxy({ - id: '', - isLinked: false, - layoutType: DatabaseLayoutPB.Grid, - fields: [], - rowMetas: [], - filters: [], - sorts: [], - groupSettings: [], - groups: [], - typeOptions: {}, - cells: {}, - }); - - void databaseService.openDatabase(viewId).then((value) => Object.assign(proxyDatabase, value)); - - return proxyDatabase; - }, [viewId]); - - useEffect(() => { - const unsubscribePromise = subscribeNotifications( - { - [DatabaseNotification.DidUpdateFields]: async (changeset) => { - await fieldListeners.didUpdateFields(viewId, database, changeset); - }, - [DatabaseNotification.DidUpdateFieldSettings]: (changeset) => { - fieldListeners.didUpdateFieldSettings(database, changeset); - }, - [DatabaseNotification.DidUpdateViewRows]: async (changeset) => { - await rowListeners.didUpdateViewRows(viewId, database, changeset); - }, - [DatabaseNotification.DidReorderRows]: (changeset) => { - rowListeners.didReorderRows(database, changeset); - }, - [DatabaseNotification.DidReorderSingleRow]: (changeset) => { - rowListeners.didReorderSingleRow(database, changeset); - }, - - [DatabaseNotification.DidUpdateSort]: (changeset) => { - sortListeners.didUpdateSort(database, changeset); - }, - - [DatabaseNotification.DidUpdateFilter]: (changeset) => { - filterListeners.didUpdateFilter(database, changeset); - }, - [DatabaseNotification.DidUpdateViewRowsVisibility]: async (changeset) => { - await rowListeners.didUpdateViewRowsVisibility(viewId, database, changeset); - }, - }, - { id: viewId } - ); - - return () => void unsubscribePromise.then((unsubscribe) => unsubscribe()); - }, [viewId, database]); - - return database; -}; - -const DatabaseRenderedContext = createContext<(viewId: string) => void>(() => { - return; -}); - -export const DatabaseRenderedProvider = DatabaseRenderedContext.Provider; - -export const useDatabaseRendered = () => useContext(DatabaseRenderedContext); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/Database.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/Database.tsx deleted file mode 100644 index d5e7bba45b..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/Database.tsx +++ /dev/null @@ -1,202 +0,0 @@ -import React, { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { useViewId } from '$app/hooks/ViewId.hooks'; -import { databaseViewService } from '$app/application/database'; -import { DatabaseTabBar } from './components'; -import { DatabaseLoader } from './DatabaseLoader'; -import { DatabaseView } from './DatabaseView'; -import { DatabaseCollection } from './components/database_settings'; -import SwipeableViews from 'react-swipeable-views'; -import { TabPanel } from '$app/components/database/components/tab_bar/ViewTabs'; -import DatabaseSettings from '$app/components/database/components/database_settings/DatabaseSettings'; -import { Portal } from '@mui/material'; -import { useTranslation } from 'react-i18next'; -import { ErrorCode, FolderNotification } from '@/services/backend'; -import ExpandRecordModal from '$app/components/database/components/edit_record/ExpandRecordModal'; -import { subscribeNotifications } from '$app/application/notification'; -import { Page } from '$app_reducers/pages/slice'; -import { getPage } from '$app/application/folder/page.service'; -import './database.scss'; - -interface Props { - selectedViewId?: string; - setSelectedViewId?: (viewId: string) => void; -} - -export const Database = forwardRef(({ selectedViewId, setSelectedViewId }, ref) => { - const innerRef = useRef(); - const databaseRef = (ref ?? innerRef) as React.MutableRefObject; - const viewId = useViewId(); - const [settingDom, setSettingDom] = useState(null); - - const [page, setPage] = useState(null); - const { t } = useTranslation(); - const [notFound, setNotFound] = useState(false); - const [childViews, setChildViews] = useState([]); - const [editRecordRowId, setEditRecordRowId] = useState(null); - const [openCollections, setOpenCollections] = useState([]); - - const handleResetDatabaseViews = useCallback(async (viewId: string) => { - await databaseViewService - .getDatabaseViews(viewId) - .then((value) => { - setChildViews(value); - }) - .catch((err) => { - if (err.code === ErrorCode.RecordNotFound) { - setNotFound(true); - } - }); - }, []); - - const handleGetPage = useCallback(async () => { - try { - const page = await getPage(viewId); - - setPage(page); - } catch (e) { - setNotFound(true); - } - }, [viewId]); - - const parentId = page?.parentId; - - useEffect(() => { - void handleGetPage(); - void handleResetDatabaseViews(viewId); - const unsubscribePromise = subscribeNotifications({ - [FolderNotification.DidUpdateView]: (changeset) => { - if (changeset.parent_view_id !== viewId && changeset.id !== viewId) return; - setChildViews((prev) => { - const index = prev.findIndex((view) => view.id === changeset.id); - - if (index === -1) { - return prev; - } - - const newViews = [...prev]; - - newViews[index] = { - ...newViews[index], - name: changeset.name, - }; - - return newViews; - }); - }, - [FolderNotification.DidUpdateChildViews]: (changeset) => { - if (changeset.parent_view_id !== viewId && changeset.parent_view_id !== parentId) return; - if (changeset.create_child_views.length === 0 && changeset.delete_child_views.length === 0) { - return; - } - - void handleResetDatabaseViews(viewId); - }, - }); - - return () => void unsubscribePromise.then((unsubscribe) => unsubscribe()); - }, [handleGetPage, handleResetDatabaseViews, viewId, parentId]); - - const value = useMemo(() => { - return Math.max( - 0, - childViews.findIndex((view) => view.id === (selectedViewId ?? viewId)) - ); - }, [childViews, selectedViewId, viewId]); - - const onToggleCollection = useCallback( - (id: string, forceOpen?: boolean) => { - if (forceOpen) { - setOpenCollections((prev) => { - if (prev.includes(id)) { - return prev; - } - - return [...prev, id]; - }); - return; - } - - if (openCollections.includes(id)) { - setOpenCollections((prev) => prev.filter((item) => item !== id)); - } else { - setOpenCollections((prev) => [...prev, id]); - } - }, - [openCollections, setOpenCollections] - ); - - const onEditRecord = useCallback( - (rowId: string) => { - setEditRecordRowId(rowId); - }, - [setEditRecordRowId] - ); - - if (notFound) { - return ( -
-

{t('deletePagePrompt.text')}

-
- ); - } - - return ( -
- - - {childViews.map((view, index) => ( - - - {selectedViewId === view.id && ( - <> - {settingDom && ( - - onToggleCollection(view.id, forceOpen)} - /> - - )} - - - {editRecordRowId && ( - { - setEditRecordRowId(null); - }} - /> - )} - - )} - - - - - ))} - -
- ); -}); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/DatabaseLoader.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/DatabaseLoader.tsx deleted file mode 100644 index b0aeab10a2..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/DatabaseLoader.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { FC, PropsWithChildren } from 'react'; -import { ViewIdProvider } from '$app/hooks'; -import { DatabaseProvider, useConnectDatabase } from './Database.hooks'; - -export interface DatabaseLoaderProps { - viewId: string; -} - -export const DatabaseLoader: FC> = ({ viewId, children }) => { - const database = useConnectDatabase(viewId); - - return ( - - {/* Make sure that the viewId is current */} - {children} - - ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/DatabaseTitle.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/DatabaseTitle.tsx deleted file mode 100644 index cd94947d8d..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/DatabaseTitle.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { FormEventHandler, useCallback } from 'react'; -import { useViewId } from '$app/hooks'; -import { useAppDispatch, useAppSelector } from '$app/stores/store'; -import { updatePageName } from '$app_reducers/pages/async_actions'; -import { useTranslation } from 'react-i18next'; - -export const DatabaseTitle = () => { - const viewId = useViewId(); - const { t } = useTranslation(); - const pageName = useAppSelector((state) => state.pages.pageMap[viewId]?.name || ''); - const dispatch = useAppDispatch(); - - const handleInput = useCallback( - (event) => { - const newTitle = (event.target as HTMLInputElement).value; - - void dispatch(updatePageName({ id: viewId, name: newTitle })); - }, - [viewId, dispatch] - ); - - return ( -
- -
- ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/DatabaseView.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/DatabaseView.tsx deleted file mode 100644 index 98b16d5fea..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/DatabaseView.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { DatabaseLayoutPB } from '@/services/backend'; -import { FC } from 'react'; -import { useDatabase } from './Database.hooks'; -import { Grid } from './grid'; -import { Board } from './board'; -import { Calendar } from './calendar'; - -export const DatabaseView: FC<{ - onEditRecord: (rowId: string) => void; -}> = (props) => { - const { layoutType } = useDatabase(); - - switch (layoutType) { - case DatabaseLayoutPB.Grid: - return ; - case DatabaseLayoutPB.Board: - return ; - case DatabaseLayoutPB.Calendar: - return ; - default: - return null; - } -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/_shared/CellText.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/_shared/CellText.tsx deleted file mode 100644 index 01666121cd..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/_shared/CellText.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React, { HTMLAttributes, PropsWithChildren } from 'react'; - -export interface CellTextProps { - className?: string; -} - -export const CellText = React.forwardRef>>( - function CellText(props, ref) { - const { children, className, ...other } = props; - - return ( -
- {children} -
- ); - } -); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/_shared/LinearProgressWithLabel.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/_shared/LinearProgressWithLabel.tsx deleted file mode 100644 index 7d4c0d1811..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/_shared/LinearProgressWithLabel.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React, { useMemo } from 'react'; - -function LinearProgressWithLabel({ - value, - count, - selectedCount, -}: { - value: number; - count: number; - selectedCount: number; -}) { - const result = useMemo(() => `${Math.round(value * 100)}%`, [value]); - - const options = useMemo(() => { - return Array.from({ length: count }, (_, i) => ({ - id: i, - checked: i < selectedCount, - })); - }, [count, selectedCount]); - - const isSplit = count < 6; - - return ( -
-
- {options.map((option) => ( - - ))} -
-
{result}
-
- ); -} - -export default LinearProgressWithLabel; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/_shared/constants.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/_shared/constants.ts deleted file mode 100644 index fd1aab7a37..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/_shared/constants.ts +++ /dev/null @@ -1,9 +0,0 @@ -export enum DragType { - Row = 'row', - Field = 'field', -} - -export enum DropPosition { - Before = 0, - After = 1, -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/_shared/dnd/dnd.context.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/_shared/dnd/dnd.context.ts deleted file mode 100644 index 8954dc733a..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/_shared/dnd/dnd.context.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { createContext } from 'react'; -import { proxy } from 'valtio'; - -export interface DragItem> { - type: string; - data: T; -} - -export interface DndContextDescriptor { - dragging: DragItem | null, -} - -const defaultDndContext: DndContextDescriptor = proxy({ - dragging: null, -}); - -export const DndContext = createContext(defaultDndContext); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/_shared/dnd/drag.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/_shared/dnd/drag.hooks.ts deleted file mode 100644 index ce8afe6f31..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/_shared/dnd/drag.hooks.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { DragEventHandler, useCallback, useContext, useMemo, useRef, useState } from 'react'; -import { DndContext } from './dnd.context'; -import { autoScrollOnEdge, EdgeGap, getScrollParent, ScrollDirection } from './utils'; - -export interface UseDraggableOptions { - type: string; - effectAllowed?: DataTransfer['effectAllowed']; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - data?: Record; - disabled?: boolean; - scrollOnEdge?: { - direction?: ScrollDirection; - getScrollElement?: () => HTMLElement | null; - edgeGap?: number | Partial; - }; -} - -export const useDraggable = ({ - type, - effectAllowed = 'copyMove', - data, - disabled, - scrollOnEdge, -}: UseDraggableOptions) => { - const scrollDirection = scrollOnEdge?.direction; - const edgeGap = scrollOnEdge?.edgeGap; - - const context = useContext(DndContext); - const typeRef = useRef(type); - const dataRef = useRef(data); - const previewRef = useRef(null); - const [isDragging, setIsDragging] = useState(false); - - typeRef.current = type; - dataRef.current = data; - - const setPreviewRef = useCallback((previewElement: null | Element) => { - previewRef.current = previewElement; - }, []); - - const attributes: { - draggable?: boolean; - } = useMemo(() => { - if (disabled) { - return {}; - } - - return { - draggable: true, - }; - }, [disabled]); - - const onDragStart = useCallback( - (event) => { - setIsDragging(true); - context.dragging = { - type: typeRef.current, - data: dataRef.current ?? {}, - }; - - const { dataTransfer } = event; - const previewNode = previewRef.current; - - dataTransfer.effectAllowed = effectAllowed; - - if (previewNode) { - const { clientX, clientY } = event; - const rect = previewNode.getBoundingClientRect(); - - dataTransfer.setDragImage(previewNode, clientX - rect.x, clientY - rect.y); - } - - if (scrollDirection === undefined) { - return; - } - - const scrollParent: HTMLElement | null = - scrollOnEdge?.getScrollElement?.() ?? getScrollParent(event.target as HTMLElement, scrollDirection); - - if (scrollParent) { - autoScrollOnEdge({ - element: scrollParent, - direction: scrollDirection, - edgeGap, - }); - } - }, - [context, effectAllowed, scrollDirection, scrollOnEdge, edgeGap] - ); - - const onDragEnd = useCallback(() => { - setIsDragging(false); - context.dragging = null; - }, [context]); - - const listeners: { - onDragStart?: DragEventHandler; - onDragEnd?: DragEventHandler; - } = useMemo( - () => ({ - onDragStart, - onDragEnd, - }), - [onDragStart, onDragEnd] - ); - - return { - isDragging, - previewRef, - attributes, - listeners, - setPreviewRef, - }; -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/_shared/dnd/drop.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/_shared/dnd/drop.hooks.ts deleted file mode 100644 index 7b3d79aeb2..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/_shared/dnd/drop.hooks.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { DragEventHandler, useContext, useState, useMemo, useCallback } from 'react'; -import { useSnapshot } from 'valtio'; -import { DragItem, DndContext } from './dnd.context'; - -interface UseDroppableOptions { - accept: string; - dropEffect?: DataTransfer['dropEffect']; - disabled?: boolean; - onDragOver?: DragEventHandler, - onDrop?: (data: DragItem) => void; -} - -export const useDroppable = ({ - accept, - dropEffect = 'move', - disabled, - onDragOver: handleDragOver, - onDrop: handleDrop, -}: UseDroppableOptions) => { - const dndContext = useContext(DndContext); - const dndSnapshot = useSnapshot(dndContext); - - const [ dragOver, setDragOver ] = useState(false); - const canDrop = useMemo( - () => !disabled && dndSnapshot.dragging?.type === accept, - [ disabled, accept, dndSnapshot.dragging?.type ], - ); - const isOver = useMemo(()=> canDrop && dragOver, [ canDrop, dragOver ]); - - const onDragEnter = useCallback((event) => { - if (!canDrop) { - return; - } - - event.preventDefault(); - event.dataTransfer.dropEffect = dropEffect; - - setDragOver(true); - }, [ canDrop, dropEffect ]); - - const onDragOver = useCallback((event) => { - if (!canDrop) { - return; - } - - event.preventDefault(); - event.dataTransfer.dropEffect = dropEffect; - - setDragOver(true); - handleDragOver?.(event); - }, [ canDrop, dropEffect, handleDragOver ]); - - const onDragLeave = useCallback(() => { - if (!canDrop) { - return; - } - - setDragOver(false); - }, [ canDrop ]); - - const onDrop = useCallback(() => { - if (!canDrop) { - return; - } - - const dragging = dndSnapshot.dragging; - - if (!dragging) { - return; - } - - setDragOver(false); - handleDrop?.(dragging); - }, [ canDrop, dndSnapshot.dragging, handleDrop ]); - - const listeners = useMemo(() => ({ - onDragEnter, - onDragOver, - onDragLeave, - onDrop, - }), [ onDragEnter, onDragOver, onDragLeave, onDrop ]); - - return { - isOver, - canDrop, - listeners, - }; -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/_shared/dnd/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/_shared/dnd/index.ts deleted file mode 100644 index 8688534359..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/_shared/dnd/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -export * from './dnd.context'; -export * from './drag.hooks'; -export * from './drop.hooks'; -export { - ScrollDirection, - Edge, -} from './utils'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/_shared/dnd/utils.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/_shared/dnd/utils.ts deleted file mode 100644 index 3aafa6f77c..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/_shared/dnd/utils.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { interval } from '$app/utils/tool'; - -export enum Edge { - Top = 'top', - Bottom = 'bottom', - Left = 'left', - Right = 'right', -} - -export enum ScrollDirection { - Horizontal = 'horizontal', - Vertical = 'vertical', -} - -export interface EdgeGap { - top: number; - bottom: number; - left: number; - right: number; -} - -export const isReachEdge = (element: Element, edge: Edge) => { - switch (edge) { - case Edge.Left: - return element.scrollLeft === 0; - case Edge.Right: - return element.scrollLeft + element.clientWidth === element.scrollWidth; - case Edge.Top: - return element.scrollTop === 0; - case Edge.Bottom: - return element.scrollTop + element.clientHeight === element.scrollHeight; - default: - return true; - } -}; - -export const scrollBy = (element: Element, edge: Edge, offset: number) => { - let step = offset; - let prop = edge; - - if (edge === Edge.Left || edge === Edge.Top) { - step = -offset; - } else if (edge === Edge.Right) { - prop = Edge.Left; - } else if (edge === Edge.Bottom) { - prop = Edge.Top; - } - - element.scrollBy({ [prop]: step }); -}; - -export const scrollElement = (element: Element, edge: Edge, offset: number) => { - if (isReachEdge(element, edge)) { - return; - } - - scrollBy(element, edge, offset); -}; - -export const calculateLeaveEdge = ( - { x: mouseX, y: mouseY }: { x: number; y: number }, - rect: DOMRect, - gaps: EdgeGap, - direction: ScrollDirection -) => { - if (direction === ScrollDirection.Horizontal) { - if (mouseX - rect.left < gaps.left) { - return Edge.Left; - } - - if (rect.right - mouseX < gaps.right) { - return Edge.Right; - } - } - - if (direction === ScrollDirection.Vertical) { - if (mouseY - rect.top < gaps.top) { - return Edge.Top; - } - - if (rect.bottom - mouseY < gaps.bottom) { - return Edge.Bottom; - } - } - - return null; -}; - -export const getScrollParent = (element: HTMLElement | null, direction: ScrollDirection): HTMLElement | null => { - if (element === null) { - return null; - } - - if (direction === ScrollDirection.Horizontal && element.scrollWidth > element.clientWidth) { - return element; - } - - if (direction === ScrollDirection.Vertical && element.scrollHeight > element.clientHeight) { - return element; - } - - return getScrollParent(element.parentElement, direction); -}; - -export interface AutoScrollOnEdgeOptions { - element: HTMLElement; - direction: ScrollDirection; - edgeGap?: number | Partial; - step?: number; -} - -const defaultEdgeGap = 30; - -export const autoScrollOnEdge = ({ element, direction, edgeGap, step = 8 }: AutoScrollOnEdgeOptions) => { - const gaps = - typeof edgeGap === 'number' - ? { - top: edgeGap, - bottom: edgeGap, - left: edgeGap, - right: edgeGap, - } - : { - top: defaultEdgeGap, - bottom: defaultEdgeGap, - left: defaultEdgeGap, - right: defaultEdgeGap, - ...edgeGap, - }; - - const keepScroll = interval(scrollElement, 8); - - let leaveEdge: Edge | null = null; - - const onDragOver = (event: DragEvent) => { - const rect = element.getBoundingClientRect(); - - leaveEdge = calculateLeaveEdge({ x: event.clientX, y: event.clientY }, rect, gaps, direction); - - if (leaveEdge) { - keepScroll(element, leaveEdge, step); - } else { - keepScroll.cancel(); - } - }; - - const onDragLeave = () => { - if (!leaveEdge) { - return; - } - - keepScroll(element, leaveEdge, step * 2); - }; - - const cleanup = () => { - keepScroll.cancel(); - - element.removeEventListener('dragover', onDragOver); - element.removeEventListener('dragleave', onDragLeave); - - document.removeEventListener('dragend', cleanup); - }; - - element.addEventListener('dragover', onDragOver); - element.addEventListener('dragleave', onDragLeave); - - document.addEventListener('dragend', cleanup); - - return cleanup; -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/_shared/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/_shared/index.ts deleted file mode 100644 index 6bfa1f812b..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/_shared/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './constants'; - -export * from './dnd'; - -export * from './CellText'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/board/Board.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/board/Board.tsx deleted file mode 100644 index 790f841701..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/board/Board.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { FC } from 'react'; - -export const Board: FC = () => { - return null; -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/board/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/board/index.ts deleted file mode 100644 index 9294d869ce..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/board/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Board'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/calendar/Calendar.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/calendar/Calendar.tsx deleted file mode 100644 index b8473fda25..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/calendar/Calendar.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { FC } from 'react'; - -export const Calendar: FC = () => { - return null; -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/calendar/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/calendar/index.ts deleted file mode 100644 index a723380592..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/calendar/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Calendar'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/Cell.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/Cell.hooks.ts deleted file mode 100644 index 76bba7b152..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/Cell.hooks.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { useCallback, useEffect, useState } from 'react'; -import { DatabaseNotification } from '@/services/backend'; -import { useNotification, useViewId } from '$app/hooks'; -import { cellService, Cell, Field } from '$app/application/database'; -import { useDispatchCell, useSelectorCell } from '$app/components/database'; - -export const useCell = (rowId: string, field: Field) => { - const viewId = useViewId(); - const { setCell } = useDispatchCell(); - const [loading, setLoading] = useState(false); - const cell = useSelectorCell(rowId, field.id); - - const fetchCell = useCallback(() => { - setLoading(true); - void cellService.getCell(viewId, rowId, field.id, field.type).then((data) => { - // cache cell - setCell(data); - setLoading(false); - }); - }, [viewId, rowId, field.id, field.type, setCell]); - - useEffect(() => { - // fetch cell if not cached - if (!cell && !loading) { - // fetch cell in next tick to avoid blocking - const timeout = setTimeout(fetchCell, 0); - - return () => { - clearTimeout(timeout); - }; - } - }, [fetchCell, cell, loading, rowId, field.id]); - - useNotification(DatabaseNotification.DidUpdateCell, fetchCell, { id: `${rowId}:${field.id}` }); - - return cell; -}; - -export const useInputCell = (cell?: Cell) => { - const [editing, setEditing] = useState(false); - const [value, setValue] = useState(''); - const viewId = useViewId(); - const updateCell = useCallback(() => { - if (!cell) return; - const { rowId, fieldId } = cell; - - if (editing) { - if (value !== cell.data) { - void cellService.updateCell(viewId, rowId, fieldId, value); - } - - setEditing(false); - } - }, [cell, editing, value, viewId]); - - return { - updateCell, - editing, - setEditing, - value, - setValue, - }; -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/Cell.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/Cell.tsx deleted file mode 100644 index a092a1d75a..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/Cell.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import React, { FC, HTMLAttributes } from 'react'; -import { FieldType } from '@/services/backend'; - -import { Cell as CellType, Field } from '$app/application/database'; -import { useCell } from './Cell.hooks'; -import { TextCell } from './TextCell'; -import { SelectCell } from './SelectCell'; -import { CheckboxCell } from './CheckboxCell'; -import NumberCell from '$app/components/database/components/cell/NumberCell'; -import URLCell from '$app/components/database/components/cell/URLCell'; -import ChecklistCell from '$app/components/database/components/cell/ChecklistCell'; -import DateTimeCell from '$app/components/database/components/cell/DateTimeCell'; -import TimestampCell from '$app/components/database/components/cell/TimestampCell'; - -export interface CellProps extends HTMLAttributes { - rowId: string; - field: Field; - icon?: string; - placeholder?: string; -} - -export interface CellComponentProps extends CellProps { - cell: CellType; -} - -const getCellComponent = (fieldType: FieldType) => { - switch (fieldType) { - case FieldType.RichText: - return TextCell as FC; - case FieldType.SingleSelect: - case FieldType.MultiSelect: - return SelectCell as FC; - case FieldType.Checkbox: - return CheckboxCell as FC; - case FieldType.Checklist: - return ChecklistCell as FC; - case FieldType.Number: - return NumberCell as FC; - case FieldType.URL: - return URLCell as FC; - case FieldType.DateTime: - return DateTimeCell as FC; - case FieldType.LastEditedTime: - case FieldType.CreatedTime: - return TimestampCell as FC; - default: - return null; - } -}; - -export const Cell: FC = ({ rowId, field, ...props }) => { - const cell = useCell(rowId, field); - - const Component = getCellComponent(field.type); - - if (!cell) { - return
; - } - - if (!Component) { - return null; - } - - return ; -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/CheckboxCell.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/CheckboxCell.tsx deleted file mode 100644 index f6f3dcf0d2..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/CheckboxCell.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React, { FC, useCallback } from 'react'; -import { ReactComponent as CheckboxCheckSvg } from '$app/assets/database/checkbox-check.svg'; -import { ReactComponent as CheckboxUncheckSvg } from '$app/assets/database/checkbox-uncheck.svg'; -import { useViewId } from '$app/hooks'; -import { cellService, CheckboxCell as CheckboxCellType, Field } from '$app/application/database'; - -export const CheckboxCell: FC<{ - field: Field; - cell: CheckboxCellType; -}> = ({ field, cell }) => { - const viewId = useViewId(); - const checked = cell.data; - - const handleClick = useCallback(() => { - void cellService.updateCell(viewId, cell.rowId, field.id, !checked ? 'Yes' : 'No'); - }, [viewId, cell, field.id, checked]); - - return ( -
- {checked ? : } -
- ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/ChecklistCell.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/ChecklistCell.tsx deleted file mode 100644 index 5ecac431c4..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/ChecklistCell.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import React, { useState, Suspense, useMemo } from 'react'; -import { ChecklistCell as ChecklistCellType, ChecklistField } from '$app/application/database'; -import ChecklistCellActions from '$app/components/database/components/field_types/checklist/ChecklistCellActions'; -import LinearProgressWithLabel from '$app/components/database/_shared/LinearProgressWithLabel'; -import { PopoverOrigin } from '@mui/material/Popover/Popover'; -import usePopoverAutoPosition from '$app/components/_shared/popover/Popover.hooks'; - -interface Props { - field: ChecklistField; - cell: ChecklistCellType; - placeholder?: string; -} - -const initialAnchorOrigin: PopoverOrigin = { - vertical: 'bottom', - horizontal: 'left', -}; - -const initialTransformOrigin: PopoverOrigin = { - vertical: 'top', - horizontal: 'left', -}; - -function ChecklistCell({ cell, placeholder }: Props) { - const value = cell?.data.percentage ?? 0; - const options = useMemo(() => cell?.data.options ?? [], [cell?.data.options]); - const selectedOptions = useMemo(() => cell?.data.selectedOptions ?? [], [cell?.data.selectedOptions]); - const [anchorEl, setAnchorEl] = useState(undefined); - const open = Boolean(anchorEl); - const handleClick = (e: React.MouseEvent) => { - setAnchorEl(e.currentTarget); - }; - - const handleClose = () => { - setAnchorEl(undefined); - }; - - const { paperHeight, paperWidth, transformOrigin, anchorOrigin, isEntered } = usePopoverAutoPosition({ - initialPaperWidth: 369, - initialPaperHeight: 300, - anchorEl, - initialAnchorOrigin, - initialTransformOrigin, - open, - }); - - return ( - <> -
- {options.length > 0 ? ( - - ) : ( -
{placeholder}
- )} -
- - {open && ( - - )} - - - ); -} - -export default ChecklistCell; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/DateTimeCell.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/DateTimeCell.tsx deleted file mode 100644 index aea4f79849..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/DateTimeCell.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import React, { Suspense, useRef, useState, useMemo, useEffect } from 'react'; -import { DateTimeCell as DateTimeCellType, DateTimeField } from '$app/application/database'; -import DateTimeCellActions from '$app/components/database/components/field_types/date/DateTimeCellActions'; -import usePopoverAutoPosition from '$app/components/_shared/popover/Popover.hooks'; -import { PopoverOrigin } from '@mui/material/Popover/Popover'; - -interface Props { - field: DateTimeField; - cell: DateTimeCellType; - placeholder?: string; -} - -const initialAnchorOrigin: PopoverOrigin = { - vertical: 'bottom', - horizontal: 'left', -}; - -const initialTransformOrigin: PopoverOrigin = { - vertical: 'top', - horizontal: 'left', -}; - -function DateTimeCell({ field, cell, placeholder }: Props) { - const isRange = cell.data.isRange; - const includeTime = cell.data.includeTime; - const [open, setOpen] = useState(false); - const ref = useRef(null); - - const handleClose = () => { - setOpen(false); - }; - - const handleClick = () => { - setOpen(true); - }; - - const content = useMemo(() => { - const { date, time, endDate, endTime } = cell.data; - - if (date) { - return ( - <> - {date} - {includeTime && time ? ' ' + time : ''} - {isRange && endDate ? ' - ' + endDate : ''} - {isRange && includeTime && endTime ? ' ' + endTime : ''} - - ); - } - - return
{placeholder}
; - }, [cell, includeTime, isRange, placeholder]); - - const { paperHeight, paperWidth, transformOrigin, anchorOrigin, isEntered, calculateAnchorSize } = - usePopoverAutoPosition({ - initialPaperWidth: 248, - initialPaperHeight: 500, - anchorEl: ref.current, - initialAnchorOrigin, - initialTransformOrigin, - open, - marginThreshold: 34, - }); - - useEffect(() => { - if (!open) return; - - const anchorEl = ref.current; - - const parent = anchorEl?.parentElement?.parentElement; - - if (!anchorEl || !parent) return; - - let timeout: NodeJS.Timeout; - const handleObserve = () => { - anchorEl.scrollIntoView({ block: 'nearest' }); - - timeout = setTimeout(() => { - calculateAnchorSize(); - }, 200); - }; - - const observer = new MutationObserver(handleObserve); - - observer.observe(parent, { - childList: true, - subtree: true, - }); - return () => { - observer.disconnect(); - clearTimeout(timeout); - }; - }, [calculateAnchorSize, open]); - - return ( - <> -
- {content} -
- - {open && ( - - )} - - - ); -} - -export default DateTimeCell; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/NumberCell.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/NumberCell.tsx deleted file mode 100644 index 727e78de3f..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/NumberCell.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React, { Suspense, useCallback, useMemo, useRef } from 'react'; -import { Field, NumberCell as NumberCellType } from '$app/application/database'; -import { CellText } from '$app/components/database/_shared'; -import EditNumberCellInput from '$app/components/database/components/field_types/number/EditNumberCellInput'; -import { useInputCell } from '$app/components/database/components/cell/Cell.hooks'; - -interface Props { - field: Field; - cell: NumberCellType; - placeholder?: string; -} - -function NumberCell({ field, cell, placeholder }: Props) { - const cellRef = useRef(null); - const { value, editing, updateCell, setEditing, setValue } = useInputCell(cell); - const content = useMemo(() => { - if (typeof cell.data === 'string' && cell.data) { - return cell.data; - } - - return
{placeholder}
; - }, [cell, placeholder]); - - const handleClick = useCallback(() => { - setValue(cell.data); - setEditing(true); - }, [cell, setEditing, setValue]); - - return ( - <> - -
{content}
-
- - {editing && ( - - )} - - - ); -} - -export default NumberCell; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/SelectCell.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/SelectCell.tsx deleted file mode 100644 index a951ddd9e4..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/SelectCell.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { FC, useCallback, useMemo, useState, Suspense, lazy } from 'react'; -import { MenuProps } from '@mui/material'; -import { SelectField, SelectCell as SelectCellType, SelectTypeOption } from '$app/application/database'; -import { Tag } from '../field_types/select/Tag'; -import { useTypeOption } from '$app/components/database'; -import usePopoverAutoPosition from '$app/components/_shared/popover/Popover.hooks'; -import { PopoverOrigin } from '@mui/material/Popover/Popover'; -import Popover from '@mui/material/Popover'; - -const initialAnchorOrigin: PopoverOrigin = { - vertical: 'bottom', - horizontal: 'center', -}; - -const initialTransformOrigin: PopoverOrigin = { - vertical: 'top', - horizontal: 'center', -}; -const SelectCellActions = lazy( - () => import('$app/components/database/components/field_types/select/select_cell_actions/SelectCellActions') -); -const menuProps: Partial = { - classes: { - list: 'py-5', - }, -}; - -export const SelectCell: FC<{ - field: SelectField; - cell: SelectCellType; - placeholder?: string; -}> = ({ field, cell, placeholder }) => { - const [anchorEl, setAnchorEl] = useState(null); - const selectedIds = useMemo(() => cell.data?.selectedOptionIds ?? [], [cell]); - const open = Boolean(anchorEl); - const handleClose = useCallback(() => { - setAnchorEl(null); - }, []); - - const typeOption = useTypeOption(field.id); - - const renderSelectedOptions = useCallback( - (selected: string[]) => - selected - .map((id) => typeOption.options?.find((option) => option.id === id)) - .map((option) => option && ), - [typeOption] - ); - - const { paperHeight, paperWidth, transformOrigin, anchorOrigin, isEntered } = usePopoverAutoPosition({ - initialPaperWidth: 369, - initialPaperHeight: 400, - anchorEl, - initialAnchorOrigin, - initialTransformOrigin, - open, - }); - - return ( -
-
{ - setAnchorEl(e.currentTarget); - }} - className={'flex h-full w-full cursor-pointer items-center gap-2 overflow-x-hidden px-2 py-1'} - > - {selectedIds.length === 0 ? ( -
{placeholder}
- ) : ( - renderSelectedOptions(selectedIds) - )} -
- - {open ? ( - { - const isInput = (e.target as Element).closest('input'); - - if (isInput) return; - - e.preventDefault(); - e.stopPropagation(); - }} - > - - - ) : null} - -
- ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/TextCell.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/TextCell.tsx deleted file mode 100644 index 38927d744b..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/TextCell.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { FC, FormEventHandler, Suspense, lazy, useCallback, useRef, useMemo } from 'react'; -import { TextCell as TextCellType } from '$app/application/database'; -import { CellText } from '../../_shared'; -import { useInputCell } from '$app/components/database/components/cell/Cell.hooks'; - -const EditTextCellInput = lazy(() => import('$app/components/database/components/field_types/text/EditTextCellInput')); - -interface TextCellProps { - cell: TextCellType; - placeholder?: string; -} -export const TextCell: FC = ({ placeholder, cell }) => { - const cellRef = useRef(null); - const { value, editing, updateCell, setEditing, setValue } = useInputCell(cell); - const handleClose = () => { - if (!cell) return; - updateCell(); - }; - - const handleClick = useCallback(() => { - if (!cell) return; - setValue(cell.data); - setEditing(true); - }, [cell, setEditing, setValue]); - - const handleInput = useCallback>( - (event) => { - setValue((event.target as HTMLTextAreaElement).value); - }, - [setValue] - ); - - const content = useMemo(() => { - if (cell && typeof cell.data === 'string' && cell.data) { - return cell.data; - } - - return
{placeholder}
; - }, [cell, placeholder]); - - return ( - <> - - {content} - - - {editing && ( - - )} - - - ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/TimestampCell.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/TimestampCell.tsx deleted file mode 100644 index 5889a13915..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/TimestampCell.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import { CreatedTimeField, LastEditedTimeField, TimeStampCell } from '$app/application/database'; - -interface Props { - field: LastEditedTimeField | CreatedTimeField; - cell: TimeStampCell; -} - -function TimestampCell({ cell }: Props) { - return
{cell.data.dataTime}
; -} - -export default TimestampCell; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/URLCell.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/URLCell.tsx deleted file mode 100644 index 592850ada1..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/URLCell.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import React, { FormEventHandler, lazy, Suspense, useCallback, useMemo, useRef } from 'react'; -import { useInputCell } from '$app/components/database/components/cell/Cell.hooks'; -import { Field, UrlCell as URLCellType } from '$app/application/database'; -import { CellText } from '$app/components/database/_shared'; -import { openUrl } from '$app/utils/open_url'; - -const EditTextCellInput = lazy(() => import('$app/components/database/components/field_types/text/EditTextCellInput')); - -interface Props { - field: Field; - cell: URLCellType; - placeholder?: string; -} - -function UrlCell({ field, cell, placeholder }: Props) { - const cellRef = useRef(null); - const { value, editing, updateCell, setEditing, setValue } = useInputCell(cell); - const handleClick = useCallback(() => { - setValue(cell.data); - setEditing(true); - }, [cell, setEditing, setValue]); - - const handleClose = () => { - updateCell(); - }; - - const handleInput = useCallback>( - (event) => { - setValue((event.target as HTMLTextAreaElement).value); - }, - [setValue] - ); - - const content = useMemo(() => { - if (cell.data) { - return ( - { - e.stopPropagation(); - openUrl(cell.data); - }} - target={'_blank'} - className={'cursor-pointer text-content-blue-400 underline'} - > - {cell.data} - - ); - } - - return
{placeholder}
; - }, [cell, placeholder]); - - return ( - <> - - {content} - - - {editing && ( - - )} - - - ); -} - -export default UrlCell; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/index.ts deleted file mode 100644 index 2440976340..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/cell/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Cell'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/database_settings/DatabaseCollection.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/database_settings/DatabaseCollection.tsx deleted file mode 100644 index 0fe9fb6d5b..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/database_settings/DatabaseCollection.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Sorts } from '../sort'; -import Filters from '../filter/Filters'; -import React from 'react'; - -interface Props { - open: boolean; -} - -export const DatabaseCollection = ({ open }: Props) => { - return ( -
-
- - -
-
- ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/database_settings/DatabaseSettings.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/database_settings/DatabaseSettings.tsx deleted file mode 100644 index ea1378eab8..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/database_settings/DatabaseSettings.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React, { useState } from 'react'; -import { TextButton } from '$app/components/database/components/tab_bar/TextButton'; -import { useTranslation } from 'react-i18next'; - -import SortSettings from '$app/components/database/components/database_settings/SortSettings'; -import SettingsMenu from '$app/components/database/components/database_settings/SettingsMenu'; -import FilterSettings from '$app/components/database/components/database_settings/FilterSettings'; - -interface Props { - onToggleCollection: (forceOpen?: boolean) => void; -} - -function DatabaseSettings(props: Props) { - const { t } = useTranslation(); - const [settingAnchorEl, setSettingAnchorEl] = useState(null); - - return ( -
- - - setSettingAnchorEl(e.currentTarget)}> - {t('settings.title')} - - setSettingAnchorEl(null)} - /> -
- ); -} - -export default DatabaseSettings; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/database_settings/FilterSettings.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/database_settings/FilterSettings.tsx deleted file mode 100644 index d0b89208d0..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/database_settings/FilterSettings.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React, { useState } from 'react'; -import { TextButton } from '$app/components/database/components/tab_bar/TextButton'; -import { useTranslation } from 'react-i18next'; -import { useFiltersCount } from '$app/components/database'; -import FilterFieldsMenu from '$app/components/database/components/filter/FilterFieldsMenu'; - -function FilterSettings({ onToggleCollection }: { onToggleCollection: (forceOpen?: boolean) => void }) { - const { t } = useTranslation(); - const filtersCount = useFiltersCount(); - const highlight = filtersCount > 0; - const [filterAnchorEl, setFilterAnchorEl] = useState(null); - const open = Boolean(filterAnchorEl); - - const handleClick = (e: React.MouseEvent) => { - if (highlight) { - onToggleCollection(); - return; - } - - setFilterAnchorEl(e.currentTarget); - }; - - return ( - <> - - {t('grid.settings.filter')} - - onToggleCollection(true)} - open={open} - anchorEl={filterAnchorEl} - onClose={() => setFilterAnchorEl(null)} - transformOrigin={{ - vertical: 'top', - horizontal: 'right', - }} - anchorOrigin={{ - vertical: 'bottom', - horizontal: 'right', - }} - /> - - ); -} - -export default FilterSettings; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/database_settings/Properties.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/database_settings/Properties.tsx deleted file mode 100644 index af2bbce218..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/database_settings/Properties.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { useDatabase } from '$app/components/database'; -import { Field as FieldType, fieldService } from '$app/application/database'; -import { Property } from '$app/components/database/components/property'; -import { FieldVisibility } from '@/services/backend'; -import { ReactComponent as EyeOpen } from '$app/assets/eye_open.svg'; -import { ReactComponent as EyeClosed } from '$app/assets/eye_close.svg'; -import { IconButton, MenuItem } from '@mui/material'; -import { DragDropContext, Draggable, Droppable, DropResult } from 'react-beautiful-dnd'; -import { useViewId } from '$app/hooks'; -import { ReactComponent as DragSvg } from '$app/assets/drag.svg'; - -interface PropertiesProps { - onItemClick: (field: FieldType) => void; -} -function Properties({ onItemClick }: PropertiesProps) { - const { fields } = useDatabase(); - const [state, setState] = useState(fields as FieldType[]); - const viewId = useViewId(); - const [menuPropertyId, setMenuPropertyId] = useState(); - - useEffect(() => { - setState(fields as FieldType[]); - }, [fields]); - - const handleOnDragEnd = async (result: DropResult) => { - const { destination, draggableId, source } = result; - const oldIndex = source.index; - const newIndex = destination?.index; - - if (oldIndex === newIndex) { - return; - } - - if (newIndex === undefined || newIndex === null) { - return; - } - - const newId = fields[newIndex ?? 0].id; - - const newProperties = fieldService.reorderFields(fields as FieldType[], oldIndex, newIndex ?? 0); - - setState(newProperties); - - await fieldService.moveField(viewId, draggableId, newId ?? ''); - }; - - return ( - - - {(dropProvided) => ( -
- {state.map((field, index) => ( - - {(provided) => { - return ( - { - setMenuPropertyId(field.id); - }} - key={field.id} - > - - - -
- { - setMenuPropertyId(undefined); - }} - menuOpened={menuPropertyId === field.id} - field={field} - /> -
- - { - e.stopPropagation(); - onItemClick(field); - }} - className={'ml-2'} - > - {field.visibility !== FieldVisibility.AlwaysHidden ? : } - -
- ); - }} -
- ))} - {dropProvided.placeholder} -
- )} -
-
- ); -} - -export default Properties; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/database_settings/SettingsMenu.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/database_settings/SettingsMenu.tsx deleted file mode 100644 index c6a9d244f0..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/database_settings/SettingsMenu.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import React, { useCallback, useMemo, useRef, useState } from 'react'; -import { Menu, MenuProps, Popover } from '@mui/material'; -import { useTranslation } from 'react-i18next'; -import Properties from '$app/components/database/components/database_settings/Properties'; -import { Field } from '$app/application/database'; -import { FieldVisibility } from '@/services/backend'; -import { updateFieldSetting } from '$app/application/database/field/field_service'; -import { useViewId } from '$app/hooks'; -import KeyboardNavigation from '$app/components/_shared/keyboard_navigation/KeyboardNavigation'; - -type SettingsMenuProps = MenuProps; - -function SettingsMenu(props: SettingsMenuProps) { - const viewId = useViewId(); - const ref = useRef(null); - const { t } = useTranslation(); - const [propertiesAnchorElPosition, setPropertiesAnchorElPosition] = useState< - | undefined - | { - top: number; - left: number; - } - >(undefined); - - const openProperties = Boolean(propertiesAnchorElPosition); - - const togglePropertyVisibility = async (field: Field) => { - let visibility = field.visibility; - - if (visibility === FieldVisibility.AlwaysHidden) { - visibility = FieldVisibility.AlwaysShown; - } else { - visibility = FieldVisibility.AlwaysHidden; - } - - await updateFieldSetting(viewId, field.id, { - visibility, - }); - }; - - const options = useMemo(() => { - return [{ key: 'properties', content:
{t('grid.settings.properties')}
}]; - }, [t]); - - const onConfirm = useCallback( - (optionKey: string) => { - if (optionKey === 'properties') { - const target = ref.current?.querySelector(`[data-key=${optionKey}]`) as HTMLElement; - const rect = target.getBoundingClientRect(); - - setPropertiesAnchorElPosition({ - top: rect.top, - left: rect.left + rect.width, - }); - props.onClose?.({}, 'backdropClick'); - } - }, - [props] - ); - - return ( - <> - - { - props.onClose?.({}, 'escapeKeyDown'); - }} - options={options} - /> - - { - setPropertiesAnchorElPosition(undefined); - }} - anchorReference={'anchorPosition'} - anchorPosition={propertiesAnchorElPosition} - transformOrigin={{ - vertical: 'top', - horizontal: 'right', - }} - onKeyDown={(e) => { - if (e.key === 'Escape') { - e.preventDefault(); - e.stopPropagation(); - props.onClose?.({}, 'escapeKeyDown'); - } - }} - > - - - - ); -} - -export default SettingsMenu; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/database_settings/SortSettings.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/database_settings/SortSettings.tsx deleted file mode 100644 index 7f978120df..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/database_settings/SortSettings.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { useDatabase } from '$app/components/database'; -import { TextButton } from '$app/components/database/components/tab_bar/TextButton'; -import SortFieldsMenu from '$app/components/database/components/sort/SortFieldsMenu'; - -interface Props { - onToggleCollection: (forceOpen?: boolean) => void; -} - -function SortSettings({ onToggleCollection }: Props) { - const { t } = useTranslation(); - const { sorts } = useDatabase(); - - const highlight = sorts && sorts.length > 0; - - const [sortAnchorEl, setSortAnchorEl] = React.useState(null); - const open = Boolean(sortAnchorEl); - const handleClick = (event: React.MouseEvent) => { - if (highlight) { - onToggleCollection(); - return; - } - - setSortAnchorEl(event.currentTarget); - }; - - const handleClose = () => { - setSortAnchorEl(null); - }; - - return ( - <> - - {t('grid.settings.sort')} - - onToggleCollection(true)} - open={open} - anchorEl={sortAnchorEl} - onClose={handleClose} - transformOrigin={{ - vertical: 'top', - horizontal: 'right', - }} - anchorOrigin={{ - vertical: 'bottom', - horizontal: 'right', - }} - /> - - ); -} - -export default SortSettings; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/database_settings/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/database_settings/index.ts deleted file mode 100644 index efb89af437..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/database_settings/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './DatabaseCollection'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/EditRecord.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/EditRecord.tsx deleted file mode 100644 index 13f29a7dfc..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/EditRecord.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import RecordDocument from '$app/components/database/components/edit_record/RecordDocument'; -import RecordHeader from '$app/components/database/components/edit_record/RecordHeader'; -import { Page } from '$app_reducers/pages/slice'; -import { ErrorCode, ViewLayoutPB } from '@/services/backend'; -import { Log } from '$app/utils/log'; -import { useDatabase } from '$app/components/database'; -import { createOrphanPage, getPage } from '$app/application/folder/page.service'; - -interface Props { - rowId: string; -} - -function EditRecord({ rowId }: Props) { - const { rowMetas } = useDatabase(); - const row = useMemo(() => { - return rowMetas.find((row) => row.id === rowId); - }, [rowMetas, rowId]); - const [page, setPage] = useState(null); - const id = row?.documentId; - - const loadPage = useCallback(async () => { - if (!id) return; - - try { - const page = await getPage(id); - - setPage(page); - } catch (e) { - // Record not found - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - if (e.code === ErrorCode.RecordNotFound) { - try { - const page = await createOrphanPage({ - view_id: id, - name: '', - layout: ViewLayoutPB.Document, - }); - - setPage(page); - } catch (e) { - Log.error(e); - } - } - } - }, [id]); - - useEffect(() => { - void loadPage(); - }, [loadPage]); - - if (!id || !page) return null; - - return ( - <> - - - - ); -} - -export default EditRecord; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/ExpandRecordModal.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/ExpandRecordModal.tsx deleted file mode 100644 index 7056cd353d..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/ExpandRecordModal.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import React, { useState } from 'react'; -import { DialogProps, IconButton, Portal } from '@mui/material'; -import Dialog from '@mui/material/Dialog'; -import { ReactComponent as DetailsIcon } from '$app/assets/details.svg'; -import RecordActions from '$app/components/database/components/edit_record/RecordActions'; -import EditRecord from '$app/components/database/components/edit_record/EditRecord'; -import { AFScroller } from '$app/components/_shared/scroller'; - -interface Props extends DialogProps { - rowId: string; -} - -function ExpandRecordModal({ open, onClose, rowId }: Props) { - const [detailAnchorEl, setDetailAnchorEl] = useState(null); - - return ( - - e.stopPropagation()} - open={open} - onClose={onClose} - PaperProps={{ - className: 'h-[calc(100%-144px)] w-[80%] max-w-[960px] overflow-visible', - }} - > - - - - { - setDetailAnchorEl(e.currentTarget); - }} - > - - - - { - onClose?.({}, 'escapeKeyDown'); - }} - onClose={() => setDetailAnchorEl(null)} - /> - - ); -} - -export default ExpandRecordModal; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/RecordActions.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/RecordActions.tsx deleted file mode 100644 index 412c1a953e..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/RecordActions.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React, { useCallback } from 'react'; -import { Icon, Menu, MenuProps } from '@mui/material'; -import { ReactComponent as DelSvg } from '$app/assets/delete.svg'; -import { ReactComponent as CopySvg } from '$app/assets/copy.svg'; -import { useTranslation } from 'react-i18next'; -import { rowService } from '$app/application/database'; -import { useViewId } from '$app/hooks'; -import MenuItem from '@mui/material/MenuItem'; - -interface Props extends MenuProps { - rowId: string; - onEscape?: () => void; - onClose?: () => void; -} -function RecordActions({ anchorEl, open, onEscape, onClose, rowId }: Props) { - const viewId = useViewId(); - const { t } = useTranslation(); - - const handleDelRow = useCallback(() => { - void rowService.deleteRow(viewId, rowId); - onEscape?.(); - }, [viewId, rowId, onEscape]); - - const handleDuplicateRow = useCallback(() => { - void rowService.duplicateRow(viewId, rowId); - onEscape?.(); - }, [viewId, rowId, onEscape]); - - const menuOptions = [ - { - label: t('grid.row.duplicate'), - icon: , - onClick: handleDuplicateRow, - }, - - { - label: t('grid.row.delete'), - icon: , - onClick: handleDelRow, - divider: true, - }, - ]; - - return ( - - {menuOptions.map((option) => ( - { - option.onClick(); - onClose?.(); - onEscape?.(); - }} - > - {option.icon} - {option.label} - - ))} - - ); -} - -export default RecordActions; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/RecordDocument.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/RecordDocument.tsx deleted file mode 100644 index 653f3c5944..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/RecordDocument.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import Editor from '$app/components/editor/Editor'; - -interface Props { - documentId: string; -} - -function RecordDocument({ documentId }: Props) { - return ; -} - -export default RecordDocument; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/RecordHeader.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/RecordHeader.tsx deleted file mode 100644 index d2381ec165..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/RecordHeader.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React, { useEffect, useRef } from 'react'; -import RecordTitle from '$app/components/database/components/edit_record/RecordTitle'; -import RecordProperties from '$app/components/database/components/edit_record/record_properties/RecordProperties'; -import { Divider } from '@mui/material'; -import { RowMeta } from '$app/application/database'; -import { Page } from '$app_reducers/pages/slice'; - -interface Props { - page: Page | null; - row: RowMeta; -} -function RecordHeader({ page, row }: Props) { - const ref = useRef(null); - - useEffect(() => { - const el = ref.current; - - if (!el) return; - - const preventSelectionTrigger = (e: MouseEvent) => { - e.stopPropagation(); - }; - - el.addEventListener('mousedown', preventSelectionTrigger); - return () => { - el.removeEventListener('mousedown', preventSelectionTrigger); - }; - }, []); - - return ( -
- - - -
- ); -} - -export default RecordHeader; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/RecordTitle.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/RecordTitle.tsx deleted file mode 100644 index c2f195aee2..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/RecordTitle.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import React, { useCallback, useMemo } from 'react'; -import { Page, PageIcon } from '$app_reducers/pages/slice'; -import ViewTitle from '$app/components/_shared/view_title/ViewTitle'; -import { ViewIconTypePB } from '@/services/backend'; -import { useViewId } from '$app/hooks'; -import { updateRowMeta } from '$app/application/database/row/row_service'; -import { cellService, Field, RowMeta, TextCell } from '$app/application/database'; -import { useDatabase } from '$app/components/database'; -import { useCell } from '$app/components/database/components/cell/Cell.hooks'; - -interface Props { - page: Page | null; - row: RowMeta; -} - -function RecordTitle({ row, page }: Props) { - const { fields } = useDatabase(); - const field = useMemo(() => { - return fields.find((field) => field.isPrimary) as Field; - }, [fields]); - const rowId = row.id; - const cell = useCell(rowId, field) as TextCell; - const title = cell.data; - - const viewId = useViewId(); - - const onTitleChange = useCallback( - async (title: string) => { - try { - await cellService.updateCell(viewId, rowId, field.id, title); - } catch (e) { - // toast.error('Failed to update title'); - } - }, - [field.id, rowId, viewId] - ); - - const onUpdateIcon = useCallback( - async (icon: PageIcon) => { - try { - await updateRowMeta(viewId, rowId, { iconUrl: icon.value }); - } catch (e) { - // toast.error('Failed to update icon'); - } - }, - [rowId, viewId] - ); - - return ( -
- {page && ( - - )} -
- ); -} - -export default React.memo(RecordTitle); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/record_properties/Property.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/record_properties/Property.tsx deleted file mode 100644 index 279ee13f68..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/record_properties/Property.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React, { HTMLAttributes } from 'react'; -import PropertyName from '$app/components/database/components/edit_record/record_properties/PropertyName'; -import PropertyValue from '$app/components/database/components/edit_record/record_properties/PropertyValue'; -import { Field } from '$app/application/database'; -import { IconButton, Tooltip } from '@mui/material'; -import { ReactComponent as DragSvg } from '$app/assets/drag.svg'; -import { useTranslation } from 'react-i18next'; - -interface Props extends HTMLAttributes { - field: Field; - rowId: string; - ishovered: boolean; - onHover: (id: string | null) => void; - menuOpened?: boolean; - onOpenMenu?: () => void; - onCloseMenu?: () => void; -} - -function Property( - { field, rowId, ishovered, onHover, menuOpened, onCloseMenu, onOpenMenu, ...props }: Props, - ref: React.ForwardedRef -) { - const { t } = useTranslation(); - - return ( - <> -
{ - onHover(field.id); - }} - onMouseLeave={() => { - onHover(null); - }} - className={'relative flex items-start gap-6 rounded hover:bg-content-blue-50'} - key={field.id} - {...props} - > - - - {ishovered && ( -
- - - - - -
- )} -
- - ); -} - -export default React.forwardRef(Property); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/record_properties/PropertyList.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/record_properties/PropertyList.tsx deleted file mode 100644 index 138c7543fd..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/record_properties/PropertyList.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React, { HTMLAttributes, useState } from 'react'; -import { Field } from '$app/application/database'; -import Property from '$app/components/database/components/edit_record/record_properties/Property'; -import { Draggable } from 'react-beautiful-dnd'; - -interface Props extends HTMLAttributes { - properties: Field[]; - rowId: string; - placeholderNode?: React.ReactNode; - openMenuPropertyId?: string; - setOpenMenuPropertyId?: (id?: string) => void; -} - -function PropertyList( - { properties, rowId, placeholderNode, openMenuPropertyId, setOpenMenuPropertyId, ...props }: Props, - ref: React.ForwardedRef -) { - const [hoverId, setHoverId] = useState(null); - - return ( -
- {properties.map((field, index) => { - return ( - - {(provided) => { - return ( - { - setOpenMenuPropertyId?.(field.id); - }} - onCloseMenu={() => { - if (openMenuPropertyId === field.id) { - setOpenMenuPropertyId?.(undefined); - } - }} - /> - ); - }} - - ); - })} - {placeholderNode} -
- ); -} - -export default React.forwardRef(PropertyList); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/record_properties/PropertyName.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/record_properties/PropertyName.tsx deleted file mode 100644 index e7de3f1fb0..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/record_properties/PropertyName.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React, { useRef } from 'react'; -import { Property } from '$app/components/database/components/property'; -import { Field as FieldType } from '$app/application/database'; - -interface Props { - field: FieldType; - menuOpened?: boolean; - onOpenMenu?: () => void; - onCloseMenu?: () => void; -} -function PropertyName({ field, menuOpened = false, onOpenMenu, onCloseMenu }: Props) { - const ref = useRef(null); - - return ( - <> -
{ - e.stopPropagation(); - e.preventDefault(); - onOpenMenu?.(); - }} - className={'flex min-h-[36px] w-[200px] cursor-pointer items-center'} - onClick={onOpenMenu} - > - -
- - ); -} - -export default PropertyName; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/record_properties/PropertyValue.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/record_properties/PropertyValue.tsx deleted file mode 100644 index 4bb33e7f05..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/record_properties/PropertyValue.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; -import { Cell } from '$app/components/database/components'; -import { Field } from '$app/application/database'; -import { useTranslation } from 'react-i18next'; - -function PropertyValue(props: { rowId: string; field: Field }) { - const { t } = useTranslation(); - const ref = useRef(null); - const [width, setWidth] = useState(props.field.width); - - useEffect(() => { - const el = ref.current; - - if (!el) return; - const width = el.getBoundingClientRect().width; - - setWidth(width); - }, []); - return ( -
- -
- ); -} - -export default React.memo(PropertyValue); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/record_properties/RecordProperties.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/record_properties/RecordProperties.tsx deleted file mode 100644 index 16fc122615..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/record_properties/RecordProperties.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { Field, fieldService, RowMeta } from '$app/application/database'; -import { useDatabase } from '$app/components/database'; -import { FieldVisibility } from '@/services/backend'; - -import PropertyList from '$app/components/database/components/edit_record/record_properties/PropertyList'; -import NewProperty from '$app/components/database/components/property/NewProperty'; -import { useViewId } from '$app/hooks'; -import { DragDropContext, Droppable, DropResult, OnDragEndResponder } from 'react-beautiful-dnd'; -import SwitchPropertiesVisible from '$app/components/database/components/edit_record/record_properties/SwitchPropertiesVisible'; - -interface Props { - row: RowMeta; -} - -function RecordProperties({ row }: Props) { - const viewId = useViewId(); - const { fields } = useDatabase(); - const fieldId = useMemo(() => { - return fields.find((field) => field.isPrimary)?.id; - }, [fields]); - const rowId = row.id; - const [openMenuPropertyId, setOpenMenuPropertyId] = useState(undefined); - const [showHiddenFields, setShowHiddenFields] = useState(false); - - const properties = useMemo(() => { - return fields.filter((field) => { - // exclude the current field, because it's already displayed in the title - // filter out hidden fields if the user doesn't want to see them - return field.id !== fieldId && (showHiddenFields || field.visibility !== FieldVisibility.AlwaysHidden); - }); - }, [fieldId, fields, showHiddenFields]); - - const hiddenFieldsCount = useMemo(() => { - return fields.filter((field) => { - return field.visibility === FieldVisibility.AlwaysHidden; - }).length; - }, [fields]); - - const [state, setState] = useState(properties); - - useEffect(() => { - setState(properties); - }, [properties]); - - // move the field in the state - const handleOnDragEnd: OnDragEndResponder = useCallback( - async (result: DropResult) => { - const { destination, draggableId, source } = result; - const newIndex = destination?.index; - const oldIndex = source.index; - - if (newIndex === undefined || newIndex === null) { - return; - } - - const newId = properties[newIndex ?? 0].id; - - // reorder the properties synchronously to avoid flickering - const newProperties = fieldService.reorderFields(properties, oldIndex, newIndex ?? 0); - - setState(newProperties); - - await fieldService.moveField(viewId, draggableId, newId); - }, - [properties, viewId] - ); - - return ( -
- - - {(dropProvided) => ( - - )} - - - - - -
- ); -} - -export default RecordProperties; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/record_properties/SwitchPropertiesVisible.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/record_properties/SwitchPropertiesVisible.tsx deleted file mode 100644 index f8937bbf21..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/edit_record/record_properties/SwitchPropertiesVisible.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; -import Button from '@mui/material/Button'; -import { useTranslation } from 'react-i18next'; -import { ReactComponent as EyeClosedSvg } from '$app/assets/eye_close.svg'; -import { ReactComponent as EyeOpenSvg } from '$app/assets/eye_open.svg'; - -function SwitchPropertiesVisible({ - hiddenFieldsCount, - showHiddenFields, - setShowHiddenFields, -}: { - hiddenFieldsCount: number; - showHiddenFields: boolean; - setShowHiddenFields: (showHiddenFields: boolean) => void; -}) { - const { t } = useTranslation(); - - return hiddenFieldsCount > 0 ? ( - - ) : null; -} - -export default SwitchPropertiesVisible; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/checklist/AddNewOption.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/checklist/AddNewOption.tsx deleted file mode 100644 index 027936d280..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/checklist/AddNewOption.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React, { useState } from 'react'; -import { updateChecklistCell } from '$app/application/database/cell/cell_service'; -import { useViewId } from '$app/hooks'; -import { Button } from '@mui/material'; -import { useTranslation } from 'react-i18next'; - -function AddNewOption({ - rowId, - fieldId, - onClose, - onFocus, -}: { - rowId: string; - fieldId: string; - onClose: () => void; - onFocus: () => void; -}) { - const { t } = useTranslation(); - const [value, setValue] = useState(''); - const viewId = useViewId(); - const createOption = async () => { - await updateChecklistCell(viewId, rowId, fieldId, { - insertOptions: [value], - }); - setValue(''); - }; - - return ( -
- { - if (e.key === 'Enter') { - e.stopPropagation(); - e.preventDefault(); - void createOption(); - return; - } - - if (e.key === 'Escape') { - e.stopPropagation(); - e.preventDefault(); - onClose(); - return; - } - }} - value={value} - spellCheck={false} - onChange={(e) => { - setValue(e.target.value); - }} - /> - -
- ); -} - -export default AddNewOption; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/checklist/ChecklistCellActions.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/checklist/ChecklistCellActions.tsx deleted file mode 100644 index 61583c4746..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/checklist/ChecklistCellActions.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import React, { useState } from 'react'; -import Popover, { PopoverProps } from '@mui/material/Popover'; -import { Divider } from '@mui/material'; -import { ChecklistCell as ChecklistCellType } from '$app/application/database'; -import ChecklistItem from '$app/components/database/components/field_types/checklist/ChecklistItem'; -import AddNewOption from '$app/components/database/components/field_types/checklist/AddNewOption'; -import LinearProgressWithLabel from '$app/components/database/_shared/LinearProgressWithLabel'; - -function ChecklistCellActions({ - cell, - maxHeight, - maxWidth, - ...props -}: PopoverProps & { - cell: ChecklistCellType; - maxWidth?: number; - maxHeight?: number; -}) { - const { fieldId, rowId } = cell; - const { percentage, selectedOptions = [], options = [] } = cell.data; - - const [focusedId, setFocusedId] = useState(null); - - return ( - -
- {options.length > 0 && ( - <> -
- -
-
- {options?.map((option) => { - return ( - setFocusedId(option.id)} - onClose={() => props.onClose?.({}, 'escapeKeyDown')} - checked={selectedOptions?.includes(option.id) || false} - /> - ); - })} -
- - - - )} - - { - setFocusedId(null); - }} - onClose={() => props.onClose?.({}, 'escapeKeyDown')} - fieldId={fieldId} - rowId={rowId} - /> -
-
- ); -} - -export default ChecklistCellActions; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/checklist/ChecklistItem.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/checklist/ChecklistItem.tsx deleted file mode 100644 index 5c6a55fa60..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/checklist/ChecklistItem.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import React, { useCallback, useMemo, useState } from 'react'; -import { SelectOption } from '$app/application/database'; -import { IconButton } from '@mui/material'; -import { updateChecklistCell } from '$app/application/database/cell/cell_service'; -import { useViewId } from '$app/hooks'; -import { ReactComponent as DeleteIcon } from '$app/assets/delete.svg'; -import { ReactComponent as CheckboxCheckSvg } from '$app/assets/database/checkbox-check.svg'; -import { ReactComponent as CheckboxUncheckSvg } from '$app/assets/database/checkbox-uncheck.svg'; -import isHotkey from 'is-hotkey'; -import debounce from 'lodash-es/debounce'; -import { useTranslation } from 'react-i18next'; - -const DELAY_CHANGE = 200; - -function ChecklistItem({ - checked, - option, - rowId, - fieldId, - onClose, - isSelected, - onFocus, -}: { - checked: boolean; - option: SelectOption; - rowId: string; - fieldId: string; - onClose: () => void; - isSelected: boolean; - onFocus: () => void; -}) { - const inputRef = React.useRef(null); - const { t } = useTranslation(); - const [value, setValue] = useState(option.name); - const viewId = useViewId(); - const updateText = useCallback(async () => { - await updateChecklistCell(viewId, rowId, fieldId, { - updateOptions: [ - { - ...option, - name: value, - }, - ], - }); - }, [fieldId, option, rowId, value, viewId]); - - const onCheckedChange = useMemo(() => { - return debounce( - () => - updateChecklistCell(viewId, rowId, fieldId, { - selectedOptionIds: [option.id], - }), - DELAY_CHANGE - ); - }, [fieldId, option.id, rowId, viewId]); - - const deleteOption = useCallback(async () => { - await updateChecklistCell(viewId, rowId, fieldId, { - deleteOptionIds: [option.id], - }); - }, [fieldId, option.id, rowId, viewId]); - - return ( -
-
- {checked ? : } -
- - { - if (e.key === 'Escape') { - e.stopPropagation(); - e.preventDefault(); - void updateText(); - onClose(); - return; - } - - if (e.key === 'Enter') { - e.stopPropagation(); - e.preventDefault(); - void updateText(); - if (isHotkey('mod+enter', e)) { - void onCheckedChange(); - } - - return; - } - }} - spellCheck={false} - onChange={(e) => { - setValue(e.target.value); - }} - /> -
- - - -
-
- ); -} - -export default ChecklistItem; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/checklist/LinearProgressWithLabel.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/checklist/LinearProgressWithLabel.tsx deleted file mode 100644 index e8b9c95a44..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/checklist/LinearProgressWithLabel.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import * as React from 'react'; -import LinearProgress, { LinearProgressProps } from '@mui/material/LinearProgress'; -import Typography from '@mui/material/Typography'; -import Box from '@mui/material/Box'; - -export function LinearProgressWithLabel(props: LinearProgressProps & { value: number }) { - return ( - - - - - - {`${Math.round(props.value * 100)}%`} - - - ); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/CustomCalendar.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/CustomCalendar.tsx deleted file mode 100644 index 4a36498bd2..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/CustomCalendar.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import DatePicker, { ReactDatePickerCustomHeaderProps } from 'react-datepicker'; -import 'react-datepicker/dist/react-datepicker.css'; -import dayjs from 'dayjs'; -import { ReactComponent as LeftSvg } from '$app/assets/arrow-left.svg'; -import { ReactComponent as RightSvg } from '$app/assets/arrow-right.svg'; -import { IconButton } from '@mui/material'; -import './calendar.scss'; - -function CustomCalendar({ - handleChange, - isRange, - timestamp, - endTimestamp, -}: { - handleChange: (params: { date?: number; endDate?: number }) => void; - isRange: boolean; - timestamp?: number; - endTimestamp?: number; -}) { - const [startDate, setStartDate] = useState(() => { - if (!timestamp) return null; - return new Date(timestamp * 1000); - }); - const [endDate, setEndDate] = useState(() => { - if (!endTimestamp) return null; - return new Date(endTimestamp * 1000); - }); - - useEffect(() => { - if (!isRange || !endTimestamp) return; - setEndDate(new Date(endTimestamp * 1000)); - }, [isRange, endTimestamp]); - - useEffect(() => { - if (!timestamp) return; - setStartDate(new Date(timestamp * 1000)); - }, [timestamp]); - - return ( -
- { - return ( -
-
- {dayjs(props.date).format('MMMM YYYY')} -
- -
- - - - - - -
-
- ); - }} - selected={startDate} - onChange={(dates) => { - if (!dates) return; - if (isRange && Array.isArray(dates)) { - let start = dates[0] as Date; - let end = dates[1] as Date; - - if (!end && start && startDate && endDate) { - const currentTime = start.getTime(); - const startTimeStamp = startDate.getTime(); - const endTimeStamp = endDate.getTime(); - const isGreaterThanStart = currentTime > startTimeStamp; - const isGreaterThanEnd = currentTime > endTimeStamp; - const isLessThanStart = currentTime < startTimeStamp; - const isLessThanEnd = currentTime < endTimeStamp; - const isEqualsStart = currentTime === startTimeStamp; - const isEqualsEnd = currentTime === endTimeStamp; - - if ((isGreaterThanStart && isLessThanEnd) || isGreaterThanEnd) { - end = start; - start = startDate; - } else if (isEqualsStart || isEqualsEnd) { - end = start; - } else if (isLessThanStart) { - end = endDate; - } - } - - setStartDate(start); - setEndDate(end); - if (!start || !end) return; - handleChange({ - date: start.getTime() / 1000, - endDate: end.getTime() / 1000, - }); - } else { - const date = dates as Date; - - setStartDate(date); - handleChange({ - date: date.getTime() / 1000, - }); - } - }} - startDate={isRange ? startDate : null} - endDate={isRange ? endDate : null} - selectsRange={isRange} - inline - /> -
- ); -} - -export default CustomCalendar; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/DateFormat.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/DateFormat.tsx deleted file mode 100644 index fd5ba57889..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/DateFormat.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import React, { useCallback, useMemo, useRef, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { MenuItem, Menu } from '@mui/material'; -import { ReactComponent as MoreSvg } from '$app/assets/more.svg'; -import { ReactComponent as SelectCheckSvg } from '$app/assets/select-check.svg'; - -import { DateFormatPB } from '@/services/backend'; -import KeyboardNavigation, { - KeyboardNavigationOption, -} from '$app/components/_shared/keyboard_navigation/KeyboardNavigation'; - -interface Props { - value: DateFormatPB; - onChange: (value: DateFormatPB) => void; -} - -function DateFormat({ value, onChange }: Props) { - const { t } = useTranslation(); - const [open, setOpen] = useState(false); - const ref = useRef(null); - - const renderOptionContent = useCallback( - (option: DateFormatPB, title: string) => { - return ( -
-
{title}
- {value === option && } -
- ); - }, - [value] - ); - - const options: KeyboardNavigationOption[] = useMemo(() => { - return [ - { - key: DateFormatPB.Friendly, - content: renderOptionContent(DateFormatPB.Friendly, t('grid.field.dateFormatFriendly')), - }, - { - key: DateFormatPB.ISO, - content: renderOptionContent(DateFormatPB.ISO, t('grid.field.dateFormatISO')), - }, - { - key: DateFormatPB.US, - content: renderOptionContent(DateFormatPB.US, t('grid.field.dateFormatUS')), - }, - { - key: DateFormatPB.Local, - content: renderOptionContent(DateFormatPB.Local, t('grid.field.dateFormatLocal')), - }, - { - key: DateFormatPB.DayMonthYear, - content: renderOptionContent(DateFormatPB.DayMonthYear, t('grid.field.dateFormatDayMonthYear')), - }, - ]; - }, [renderOptionContent, t]); - - const handleClick = (option: DateFormatPB) => { - onChange(option); - setOpen(false); - }; - - return ( - <> - setOpen(true)} - > - {t('grid.field.dateFormat')} - - - setOpen(false)} - > - { - setOpen(false); - }} - disableFocus={true} - options={options} - onConfirm={handleClick} - /> - - - ); -} - -export default DateFormat; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/DateTimeCellActions.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/DateTimeCellActions.tsx deleted file mode 100644 index 78e3129d4f..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/DateTimeCellActions.tsx +++ /dev/null @@ -1,197 +0,0 @@ -import React, { useCallback, useMemo } from 'react'; -import Popover, { PopoverProps } from '@mui/material/Popover'; -import { DateTimeCell, DateTimeField, DateTimeTypeOption } from '$app/application/database'; -import { useViewId } from '$app/hooks'; -import { useTranslation } from 'react-i18next'; -import { updateDateCell } from '$app/application/database/cell/cell_service'; -import { Divider, MenuItem, MenuList } from '@mui/material'; -import dayjs from 'dayjs'; -import RangeSwitch from '$app/components/database/components/field_types/date/RangeSwitch'; -import CustomCalendar from '$app/components/database/components/field_types/date/CustomCalendar'; -import IncludeTimeSwitch from '$app/components/database/components/field_types/date/IncludeTimeSwitch'; -import DateTimeFormatSelect from '$app/components/database/components/field_types/date/DateTimeFormatSelect'; -import DateTimeSet from '$app/components/database/components/field_types/date/DateTimeSet'; -import { useTypeOption } from '$app/components/database'; -import { getDateFormat, getTimeFormat } from '$app/components/database/components/field_types/date/utils'; -import { notify } from '$app/components/_shared/notify'; - -function DateTimeCellActions({ - cell, - field, - maxWidth, - maxHeight, - ...props -}: PopoverProps & { - field: DateTimeField; - cell: DateTimeCell; - maxWidth?: number; - maxHeight?: number; -}) { - const typeOption = useTypeOption(field.id); - - const timeFormat = useMemo(() => { - return getTimeFormat(typeOption.timeFormat); - }, [typeOption.timeFormat]); - - const dateFormat = useMemo(() => { - return getDateFormat(typeOption.dateFormat); - }, [typeOption.dateFormat]); - - const { includeTime } = cell.data; - - const timestamp = useMemo(() => cell.data.timestamp || undefined, [cell.data.timestamp]); - const endTimestamp = useMemo(() => cell.data.endTimestamp || undefined, [cell.data.endTimestamp]); - const time = useMemo(() => cell.data.time || undefined, [cell.data.time]); - const endTime = useMemo(() => cell.data.endTime || undefined, [cell.data.endTime]); - - const viewId = useViewId(); - const { t } = useTranslation(); - - const handleChange = useCallback( - async (params: { - includeTime?: boolean; - date?: number; - endDate?: number; - time?: string; - endTime?: string; - isRange?: boolean; - clearFlag?: boolean; - }) => { - try { - const isRange = params.isRange ?? cell.data.isRange; - - const data = { - date: params.date ?? timestamp, - endDate: isRange ? params.endDate ?? endTimestamp : undefined, - time: params.time ?? time, - endTime: isRange ? params.endTime ?? endTime : undefined, - includeTime: params.includeTime ?? includeTime, - isRange, - clearFlag: params.clearFlag, - }; - - // if isRange and date is greater than endDate, swap date and endDate - if ( - data.isRange && - data.date && - data.endDate && - dayjs(dayjs.unix(data.date).format('YYYY/MM/DD ') + data.time).unix() > - dayjs(dayjs.unix(data.endDate).format('YYYY/MM/DD ') + data.endTime).unix() - ) { - if (params.date || params.time) { - data.endDate = data.date; - data.endTime = data.time; - } - - if (params.endDate || params.endTime) { - data.date = data.endDate; - data.time = data.endTime; - } - } - - await updateDateCell(viewId, cell.rowId, cell.fieldId, data); - } catch (e) { - notify.error(String(e)); - } - }, - [cell, endTime, endTimestamp, includeTime, time, timestamp, viewId] - ); - - const isRange = cell.data.isRange || false; - - return ( - { - if (e.key === 'Escape') { - e.preventDefault(); - e.stopPropagation(); - props.onClose?.({}, 'escapeKeyDown'); - } - }} - > -
- - - - - -
- { - void handleChange({ - isRange: val, - // reset endTime when isRange is changed - endTime: time, - endDate: timestamp, - }); - }} - checked={isRange} - /> - { - void handleChange({ - includeTime: val, - // reset time when includeTime is changed - time: val ? dayjs().format(timeFormat) : undefined, - endTime: val && isRange ? dayjs().format(timeFormat) : undefined, - }); - }} - checked={includeTime} - /> -
- - - - - - { - await handleChange({ - isRange: false, - includeTime: false, - }); - await handleChange({ - clearFlag: true, - }); - - props.onClose?.({}, 'backdropClick'); - }} - > - {t('grid.field.clearDate')} - - -
-
- ); -} - -export default DateTimeCellActions; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/DateTimeFieldActions.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/DateTimeFieldActions.tsx deleted file mode 100644 index 6c4b41a494..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/DateTimeFieldActions.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import { UndeterminedDateField } from '$app/application/database'; -import DateTimeFormat from '$app/components/database/components/field_types/date/DateTimeFormat'; -import { Divider } from '@mui/material'; - -function DateTimeFieldActions({ field }: { field: UndeterminedDateField }) { - return ( - <> -
- -
- - - ); -} - -export default DateTimeFieldActions; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/DateTimeFormat.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/DateTimeFormat.tsx deleted file mode 100644 index 0107997c24..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/DateTimeFormat.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import React, { useCallback } from 'react'; -import DateFormat from '$app/components/database/components/field_types/date/DateFormat'; -import TimeFormat from '$app/components/database/components/field_types/date/TimeFormat'; -import { TimeStampTypeOption, UndeterminedDateField, updateTypeOption } from '$app/application/database'; -import { DateFormatPB, FieldType, TimeFormatPB } from '@/services/backend'; -import { useViewId } from '$app/hooks'; -import Typography from '@mui/material/Typography'; -import { useTranslation } from 'react-i18next'; -import IncludeTimeSwitch from '$app/components/database/components/field_types/date/IncludeTimeSwitch'; -import { useTypeOption } from '$app/components/database'; - -interface Props { - field: UndeterminedDateField; - showLabel?: boolean; -} - -function DateTimeFormat({ field, showLabel = true }: Props) { - const viewId = useViewId(); - const { t } = useTranslation(); - const showIncludeTime = field.type === FieldType.CreatedTime || field.type === FieldType.LastEditedTime; - const typeOption = useTypeOption(field.id); - const { timeFormat = TimeFormatPB.TwentyFourHour, dateFormat = DateFormatPB.Friendly, includeTime } = typeOption; - const handleChange = useCallback( - async (params: { timeFormat?: TimeFormatPB; dateFormat?: DateFormatPB; includeTime?: boolean }) => { - try { - await updateTypeOption(viewId, field.id, field.type, { - timeFormat: params.timeFormat ?? timeFormat, - dateFormat: params.dateFormat ?? dateFormat, - includeTime: params.includeTime ?? includeTime, - fieldType: field.type, - }); - } catch (e) { - // toast.error(e.message); - } - }, - [dateFormat, field.id, field.type, includeTime, timeFormat, viewId] - ); - - return ( -
- {showLabel && ( - - {t('grid.field.format')} - - )} - - { - void handleChange({ dateFormat: val }); - }} - /> - { - void handleChange({ timeFormat: val }); - }} - /> - - {showIncludeTime && ( -
- { - void handleChange({ includeTime: checked }); - }} - /> -
- )} -
- ); -} - -export default DateTimeFormat; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/DateTimeFormatSelect.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/DateTimeFormatSelect.tsx deleted file mode 100644 index f0393139b0..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/DateTimeFormatSelect.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React, { useState, useRef } from 'react'; -import { Menu, MenuItem } from '@mui/material'; -import { useTranslation } from 'react-i18next'; -import { DateTimeField } from '$app/application/database'; -import DateTimeFormat from '$app/components/database/components/field_types/date/DateTimeFormat'; -import { ReactComponent as MoreSvg } from '$app/assets/more.svg'; - -interface Props { - field: DateTimeField; -} - -function DateTimeFormatSelect({ field }: Props) { - const { t } = useTranslation(); - const [open, setOpen] = useState(false); - const ref = useRef(null); - - return ( - <> - setOpen(true)} className={'text-xs font-medium'}> -
- {t('grid.field.dateFormat')} & {t('grid.field.timeFormat')} -
- -
- { - if (e.key === 'Escape') { - e.stopPropagation(); - e.preventDefault(); - setOpen(false); - } - }} - onClose={() => setOpen(false)} - MenuListProps={{ - className: 'px-2', - }} - > - - - - ); -} - -export default DateTimeFormatSelect; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/DateTimeInput.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/DateTimeInput.tsx deleted file mode 100644 index 82080b7d25..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/DateTimeInput.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import React, { useMemo } from 'react'; -import { DateField, TimeField } from '@mui/x-date-pickers-pro'; -import dayjs from 'dayjs'; -import { Divider } from '@mui/material'; -import debounce from 'lodash-es/debounce'; - -interface Props { - onChange: (params: { date?: number; time?: string }) => void; - date?: number; - time?: string; - timeFormat: string; - dateFormat: string; - includeTime?: boolean; -} - -const sx = { - '& .MuiOutlinedInput-notchedOutline': { - border: 'none', - }, - '& .MuiOutlinedInput-input': { - padding: '0', - }, -}; - -function DateTimeInput({ includeTime, dateFormat, timeFormat, ...props }: Props) { - const date = useMemo(() => { - return props.date ? dayjs.unix(props.date) : undefined; - }, [props.date]); - - const time = useMemo(() => { - return props.time ? dayjs(dayjs().format('YYYY/MM/DD ') + props.time) : undefined; - }, [props.time]); - - const debounceOnChange = useMemo(() => { - return debounce(props.onChange, 500); - }, [props.onChange]); - - return ( -
- { - if (!date) return; - debounceOnChange({ - date: date.unix(), - }); - }} - inputProps={{ - className: 'text-[12px]', - }} - format={dateFormat} - size={'small'} - sx={sx} - className={'flex-1 pl-2'} - /> - - {includeTime && ( - <> - - { - if (!time) return; - debounceOnChange({ - time: time.format(timeFormat), - }); - }} - format={timeFormat} - size={'small'} - sx={sx} - className={'w-[70px] pl-1'} - /> - - )} -
- ); -} - -export default DateTimeInput; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/DateTimeSet.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/DateTimeSet.tsx deleted file mode 100644 index 8e86b952d1..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/DateTimeSet.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import React from 'react'; -import { LocalizationProvider } from '@mui/x-date-pickers-pro'; -import { AdapterDayjs } from '@mui/x-date-pickers-pro/AdapterDayjs'; -import DateTimeInput from '$app/components/database/components/field_types/date/DateTimeInput'; - -interface Props { - onChange: (params: { date?: number; endDate?: number; time?: string; endTime?: string }) => void; - date?: number; - endDate?: number; - time?: string; - endTime?: string; - isRange?: boolean; - timeFormat: string; - dateFormat: string; - includeTime?: boolean; -} -function DateTimeSet({ onChange, date, endDate, time, endTime, isRange, timeFormat, dateFormat, includeTime }: Props) { - return ( -
- - { - onChange({ - date, - time, - }); - }} - date={date} - time={time} - timeFormat={timeFormat} - dateFormat={dateFormat} - includeTime={includeTime} - /> - {isRange && ( - { - onChange({ - endDate: date, - endTime: time, - }); - }} - timeFormat={timeFormat} - dateFormat={dateFormat} - includeTime={includeTime} - /> - )} - -
- ); -} - -export default DateTimeSet; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/IncludeTimeSwitch.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/IncludeTimeSwitch.tsx deleted file mode 100644 index f40e179ae4..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/IncludeTimeSwitch.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import { Switch, SwitchProps } from '@mui/material'; -import { ReactComponent as TimeSvg } from '$app/assets/database/field-type-last-edited-time.svg'; -import Typography from '@mui/material/Typography'; -import { useTranslation } from 'react-i18next'; - -function IncludeTimeSwitch({ - checked, - onIncludeTimeChange, - ...props -}: SwitchProps & { - onIncludeTimeChange: (checked: boolean) => void; -}) { - const { t } = useTranslation(); - const handleChange = (event: React.ChangeEvent) => { - onIncludeTimeChange(event.target.checked); - }; - - return ( -
-
- - {t('grid.field.includeTime')} -
- -
- ); -} - -export default IncludeTimeSwitch; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/RangeSwitch.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/RangeSwitch.tsx deleted file mode 100644 index 76431af5fa..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/RangeSwitch.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import { Switch, SwitchProps } from '@mui/material'; -import { useTranslation } from 'react-i18next'; -import Typography from '@mui/material/Typography'; -import { ReactComponent as DateSvg } from '$app/assets/database/field-type-date.svg'; - -function RangeSwitch({ - checked, - onIsRangeChange, - ...props -}: SwitchProps & { - onIsRangeChange: (checked: boolean) => void; -}) { - const { t } = useTranslation(); - const handleChange = (event: React.ChangeEvent) => { - onIsRangeChange(event.target.checked); - }; - - return ( -
-
- - {t('grid.field.isRange')} -
- -
- ); -} - -export default RangeSwitch; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/TimeFormat.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/TimeFormat.tsx deleted file mode 100644 index 89a9ad1756..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/TimeFormat.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import React, { useCallback, useMemo, useRef, useState } from 'react'; -import { TimeFormatPB } from '@/services/backend'; -import { useTranslation } from 'react-i18next'; -import { Menu, MenuItem } from '@mui/material'; -import { ReactComponent as MoreSvg } from '$app/assets/more.svg'; -import { ReactComponent as SelectCheckSvg } from '$app/assets/select-check.svg'; -import KeyboardNavigation, { - KeyboardNavigationOption, -} from '$app/components/_shared/keyboard_navigation/KeyboardNavigation'; - -interface Props { - value: TimeFormatPB; - onChange: (value: TimeFormatPB) => void; -} -function TimeFormat({ value, onChange }: Props) { - const { t } = useTranslation(); - const [open, setOpen] = useState(false); - const ref = useRef(null); - - const renderOptionContent = useCallback( - (option: TimeFormatPB, title: string) => { - return ( -
-
{title}
- {value === option && } -
- ); - }, - [value] - ); - - const options: KeyboardNavigationOption[] = useMemo(() => { - return [ - { - key: TimeFormatPB.TwelveHour, - content: renderOptionContent(TimeFormatPB.TwelveHour, t('grid.field.timeFormatTwelveHour')), - }, - { - key: TimeFormatPB.TwentyFourHour, - content: renderOptionContent(TimeFormatPB.TwentyFourHour, t('grid.field.timeFormatTwentyFourHour')), - }, - ]; - }, [renderOptionContent, t]); - - const handleClick = (option: TimeFormatPB) => { - onChange(option); - setOpen(false); - }; - - return ( - <> - setOpen(true)} - > - {t('grid.field.timeFormat')} - - - setOpen(false)} - > - { - setOpen(false); - }} - disableFocus={true} - options={options} - onConfirm={handleClick} - /> - - - ); -} - -export default TimeFormat; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/calendar.scss b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/calendar.scss deleted file mode 100644 index 257467ed24..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/calendar.scss +++ /dev/null @@ -1,82 +0,0 @@ - -.react-datepicker__month-container { - width: 100%; - border-radius: 0; -} -.react-datepicker__header { - border-radius: 0; - background: transparent; - border-bottom: 0; - -} -.react-datepicker__day-names { - border: none; -} -.react-datepicker__day-name { - color: var(--text-caption); -} -.react-datepicker__month { - border: none; -} - -.react-datepicker__day { - border: none; - color: var(--text-title); - border-radius: 100%; -} -.react-datepicker__day:hover { - border-radius: 100%; - background: var(--fill-default); - color: var(--content-on-fill); -} -.react-datepicker__day--outside-month { - color: var(--text-caption); -} -.react-datepicker__day--in-range { - background: var(--fill-hover); - color: var(--content-on-fill); -} - - -.react-datepicker__day--today { - border: 1px solid var(--fill-default); - color: var(--text-title); - border-radius: 100%; - background: transparent; - font-weight: 500; - -} - -.react-datepicker__day--today:hover{ - background: var(--fill-default); - color: var(--content-on-fill); -} - -.react-datepicker__day--in-selecting-range, .react-datepicker__day--today.react-datepicker__day--in-range { - background: var(--fill-hover); - color: var(--content-on-fill); - border-color: transparent; -} - -.react-datepicker__day--keyboard-selected { - background: transparent; -} - - -.react-datepicker__day--range-start, .react-datepicker__day--range-end, .react-datepicker__day--selected { - &.react-datepicker__day--today { - background: var(--fill-default); - color: var(--content-on-fill); - } - background: var(--fill-default) !important; - color: var(--content-on-fill); -} - -.react-datepicker__day--range-start, .react-datepicker__day--range-end, .react-datepicker__day--selected:hover { - background: var(--fill-default); - color: var(--content-on-fill); -} - -.react-swipeable-view-container { - height: 100%; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/utils.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/utils.ts deleted file mode 100644 index 129e84c4e7..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/date/utils.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { DateFormatPB, TimeFormatPB } from '@/services/backend'; - -export function getTimeFormat(timeFormat?: TimeFormatPB) { - switch (timeFormat) { - case TimeFormatPB.TwelveHour: - return 'h:mm A'; - case TimeFormatPB.TwentyFourHour: - return 'HH:mm'; - default: - return 'HH:mm'; - } -} - -export function getDateFormat(dateFormat?: DateFormatPB) { - switch (dateFormat) { - case DateFormatPB.Friendly: - return 'MMM DD, YYYY'; - case DateFormatPB.ISO: - return 'YYYY-MMM-DD'; - case DateFormatPB.US: - return 'YYYY/MMM/DD'; - case DateFormatPB.Local: - return 'MMM/DD/YYYY'; - case DateFormatPB.DayMonthYear: - return 'DD/MMM/YYYY'; - default: - return 'YYYY-MMM-DD'; - } -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/number/EditNumberCellInput.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/number/EditNumberCellInput.tsx deleted file mode 100644 index d2b538a7e1..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/number/EditNumberCellInput.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import React, { useCallback } from 'react'; -import { Popover } from '@mui/material'; -import InputBase from '@mui/material/InputBase'; - -function EditNumberCellInput({ - editing, - anchorEl, - width, - onClose, - value, - onChange, -}: { - editing: boolean; - anchorEl: HTMLDivElement | null; - width: number | undefined; - onClose: () => void; - value: string; - onChange: (value: string) => void; -}) { - const handleInput = (e: React.FormEvent) => { - const value = (e.target as HTMLInputElement).value; - - onChange(value); - }; - - const handleKeyDown = useCallback( - (event: React.KeyboardEvent) => { - if (event.key === 'Enter') { - onClose(); - } - }, - [onClose] - ); - - return ( - - - - ); -} - -export default EditNumberCellInput; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/number/NumberFieldActions.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/number/NumberFieldActions.tsx deleted file mode 100644 index eceb128804..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/number/NumberFieldActions.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import React, { useCallback } from 'react'; -import { NumberField, NumberTypeOption, updateTypeOption } from '$app/application/database'; -import { Divider } from '@mui/material'; -import { useTranslation } from 'react-i18next'; -import NumberFormatSelect from '$app/components/database/components/field_types/number/NumberFormatSelect'; -import { NumberFormatPB } from '@/services/backend'; -import { useViewId } from '$app/hooks'; -import { useTypeOption } from '$app/components/database'; - -function NumberFieldActions({ field }: { field: NumberField }) { - const viewId = useViewId(); - const { t } = useTranslation(); - const typeOption = useTypeOption(field.id); - const onChange = useCallback( - async (value: NumberFormatPB) => { - await updateTypeOption(viewId, field.id, field.type, { - format: value, - }); - }, - [field.id, field.type, viewId] - ); - - return ( - <> -
-
{t('grid.field.format')}
- -
- - - ); -} - -export default NumberFieldActions; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/number/NumberFormatMenu.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/number/NumberFormatMenu.tsx deleted file mode 100644 index 0f9be6a21a..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/number/NumberFormatMenu.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React, { useCallback, useMemo, useRef } from 'react'; -import { NumberFormatPB } from '@/services/backend'; -import { Menu, MenuProps } from '@mui/material'; -import { formats, formatText } from '$app/components/database/components/field_types/number/const'; -import { ReactComponent as SelectCheckSvg } from '$app/assets/select-check.svg'; -import KeyboardNavigation, { - KeyboardNavigationOption, -} from '$app/components/_shared/keyboard_navigation/KeyboardNavigation'; - -function NumberFormatMenu({ - value, - onChangeFormat, - ...props -}: MenuProps & { - value: NumberFormatPB; - onChangeFormat: (value: NumberFormatPB) => void; -}) { - const scrollRef = useRef(null); - const onConfirm = useCallback( - (format: NumberFormatPB) => { - onChangeFormat(format); - props.onClose?.({}, 'backdropClick'); - }, - [onChangeFormat, props] - ); - - const renderContent = useCallback( - (format: NumberFormatPB) => { - return ( - <> - {formatText(format)} - {value === format && } - - ); - }, - [value] - ); - - const options: KeyboardNavigationOption[] = useMemo( - () => - formats.map((format) => ({ - key: format.value as NumberFormatPB, - content: renderContent(format.value as NumberFormatPB), - })), - [renderContent] - ); - - return ( - -
- props.onClose?.({}, 'escapeKeyDown')} - /> -
-
- ); -} - -export default NumberFormatMenu; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/number/NumberFormatSelect.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/number/NumberFormatSelect.tsx deleted file mode 100644 index 5a02c6759b..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/number/NumberFormatSelect.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React, { useRef, useState } from 'react'; -import { MenuItem } from '@mui/material'; -import { NumberFormatPB } from '@/services/backend'; -import { ReactComponent as MoreSvg } from '$app/assets/more.svg'; -import { formatText } from '$app/components/database/components/field_types/number/const'; -import NumberFormatMenu from '$app/components/database/components/field_types/number/NumberFormatMenu'; - -function NumberFormatSelect({ value, onChange }: { value: NumberFormatPB; onChange: (value: NumberFormatPB) => void }) { - const ref = useRef(null); - const [expanded, setExpanded] = useState(false); - - return ( - <> - { - setExpanded(!expanded); - }} - className={'flex w-full justify-between rounded-none'} - > -
{formatText(value)}
- -
- setExpanded(false)} - onChangeFormat={onChange} - /> - - ); -} - -export default NumberFormatSelect; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/number/const.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/number/const.ts deleted file mode 100644 index 38621cb114..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/number/const.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { NumberFormatPB } from '@/services/backend'; - -export const formats = Object.entries(NumberFormatPB) - .filter(([, value]) => typeof value !== 'string') - .map(([key, value]) => { - return { - key, - value, - }; - }); - -export const formatText = (format: NumberFormatPB) => { - return formats.find((item) => item.value === format)?.key; -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/select/SelectOptionModifyMenu.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/select/SelectOptionModifyMenu.tsx deleted file mode 100644 index 6c6cf37aae..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/select/SelectOptionModifyMenu.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import { FC, useMemo, useRef, useState } from 'react'; -import { Divider, ListSubheader, MenuItem, MenuList, MenuProps, OutlinedInput } from '@mui/material'; -import { SelectOptionColorPB } from '@/services/backend'; -import { ReactComponent as DeleteSvg } from '$app/assets/delete.svg'; -import { ReactComponent as SelectCheckSvg } from '$app/assets/select-check.svg'; -import { SelectOption } from '$app/application/database'; -import { SelectOptionColorMap, SelectOptionColorTextMap } from './constants'; -import Button from '@mui/material/Button'; -import { - deleteSelectOption, - insertOrUpdateSelectOption, -} from '$app/application/database/field/select_option/select_option_service'; -import { useViewId } from '$app/hooks'; -import Popover from '@mui/material/Popover'; -import debounce from 'lodash-es/debounce'; -import { useTranslation } from 'react-i18next'; - -interface SelectOptionMenuProps { - fieldId: string; - option: SelectOption; - MenuProps: MenuProps; -} - -const Colors = [ - SelectOptionColorPB.Purple, - SelectOptionColorPB.Pink, - SelectOptionColorPB.LightPink, - SelectOptionColorPB.Orange, - SelectOptionColorPB.Yellow, - SelectOptionColorPB.Lime, - SelectOptionColorPB.Green, - SelectOptionColorPB.Aqua, - SelectOptionColorPB.Blue, -]; - -export const SelectOptionModifyMenu: FC = ({ fieldId, option, MenuProps: menuProps }) => { - const { t } = useTranslation(); - const [tagName, setTagName] = useState(option.name); - const viewId = useViewId(); - const inputRef = useRef(null); - const updateColor = async (color: SelectOptionColorPB) => { - await insertOrUpdateSelectOption(viewId, fieldId, [ - { - ...option, - color, - }, - ]); - }; - - const updateName = useMemo(() => { - return debounce(async (tagName) => { - if (tagName === option.name) return; - - await insertOrUpdateSelectOption(viewId, fieldId, [ - { - ...option, - name: tagName, - }, - ]); - }, 500); - }, [option, viewId, fieldId]); - - const onClose = () => { - menuProps.onClose?.({}, 'backdropClick'); - }; - - const deleteOption = async () => { - await deleteSelectOption(viewId, fieldId, [option]); - onClose(); - }; - - return ( - { - e.stopPropagation(); - }} - onClose={onClose} - onMouseDown={(e) => { - const isInput = inputRef.current?.contains(e.target as Node); - - if (isInput) return; - e.preventDefault(); - e.stopPropagation(); - }} - > - - { - setTagName(e.target.value); - void updateName(e.target.value); - }} - onKeyDown={(e) => { - if (e.key === 'Escape') { - e.preventDefault(); - e.stopPropagation(); - void updateName(tagName); - onClose(); - } - }} - onClick={(e) => { - e.stopPropagation(); - }} - onMouseDown={(e) => { - e.stopPropagation(); - }} - autoFocus={true} - placeholder={t('grid.selectOption.tagName')} - size='small' - /> - -
- -
- - - {t('grid.selectOption.colorPanelTitle')} - - {Colors.map((color) => ( - { - e.preventDefault(); - e.stopPropagation(); - }} - onClick={(e) => { - e.preventDefault(); - void updateColor(color); - }} - key={color} - value={color} - className={'px-1.5'} - > - - {t(`grid.selectOption.${SelectOptionColorTextMap[color]}`)} - {option.color === color && } - - ))} - -
- ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/select/Tag.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/select/Tag.tsx deleted file mode 100644 index 3e4677d57d..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/select/Tag.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { FC } from 'react'; -import { Chip, ChipProps } from '@mui/material'; -import { SelectOptionColorPB } from '@/services/backend'; -import { SelectOptionColorMap } from './constants'; - -export interface TagProps extends Omit { - color?: SelectOptionColorPB | ChipProps['color']; -} - -export const Tag: FC = ({ color, classes, ...props }) => { - - return ( - - ) -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/select/constants.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/select/constants.ts deleted file mode 100644 index 58a42f7dad..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/select/constants.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { SelectOptionColorPB } from '@/services/backend'; - -export const SelectOptionColorMap = { - [SelectOptionColorPB.Purple]: 'bg-tint-purple', - [SelectOptionColorPB.Pink]: 'bg-tint-pink', - [SelectOptionColorPB.LightPink]: 'bg-tint-red', - [SelectOptionColorPB.Orange]: 'bg-tint-orange', - [SelectOptionColorPB.Yellow]: 'bg-tint-yellow', - [SelectOptionColorPB.Lime]: 'bg-tint-lime', - [SelectOptionColorPB.Green]: 'bg-tint-green', - [SelectOptionColorPB.Aqua]: 'bg-tint-aqua', - [SelectOptionColorPB.Blue]: 'bg-tint-blue', -}; - -export const SelectOptionColorTextMap = { - [SelectOptionColorPB.Purple]: 'purpleColor', - [SelectOptionColorPB.Pink]: 'pinkColor', - [SelectOptionColorPB.LightPink]: 'lightPinkColor', - [SelectOptionColorPB.Orange]: 'orangeColor', - [SelectOptionColorPB.Yellow]: 'yellowColor', - [SelectOptionColorPB.Lime]: 'limeColor', - [SelectOptionColorPB.Green]: 'greenColor', - [SelectOptionColorPB.Aqua]: 'aquaColor', - [SelectOptionColorPB.Blue]: 'blueColor', -} as const; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/select/select_cell_actions/SearchInput.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/select/select_cell_actions/SearchInput.tsx deleted file mode 100644 index 5c8acb4759..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/select/select_cell_actions/SearchInput.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React, { FormEvent, useCallback } from 'react'; -import { OutlinedInput } from '@mui/material'; -import { useTranslation } from 'react-i18next'; - -function SearchInput({ - setNewOptionName, - newOptionName, - inputRef, -}: { - newOptionName: string; - setNewOptionName: (value: string) => void; - inputRef?: React.RefObject; -}) { - const { t } = useTranslation(); - const handleInput = useCallback( - (event: FormEvent) => { - const value = (event.target as HTMLInputElement).value; - - setNewOptionName(value); - }, - [setNewOptionName] - ); - - return ( - - ); -} - -export default SearchInput; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/select/select_cell_actions/SelectCellActions.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/select/select_cell_actions/SelectCellActions.tsx deleted file mode 100644 index e2cd27019f..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/select/select_cell_actions/SelectCellActions.tsx +++ /dev/null @@ -1,162 +0,0 @@ -import React, { useCallback, useMemo, useRef, useState } from 'react'; -import { SelectOptionItem } from '$app/components/database/components/field_types/select/select_cell_actions/SelectOptionItem'; -import { cellService, SelectCell as SelectCellType, SelectField, SelectTypeOption } from '$app/application/database'; -import { useViewId } from '$app/hooks'; -import { - createSelectOption, - insertOrUpdateSelectOption, -} from '$app/application/database/field/select_option/select_option_service'; -import { FieldType } from '@/services/backend'; -import { useTypeOption } from '$app/components/database'; -import SearchInput from './SearchInput'; -import { useTranslation } from 'react-i18next'; -import KeyboardNavigation, { - KeyboardNavigationOption, -} from '$app/components/_shared/keyboard_navigation/KeyboardNavigation'; -import { Tag } from '$app/components/database/components/field_types/select/Tag'; - -const CREATE_OPTION_KEY = 'createOption'; - -function SelectCellActions({ - field, - cell, - onUpdated, - onClose, -}: { - field: SelectField; - cell: SelectCellType; - onUpdated?: () => void; - onClose?: () => void; -}) { - const { t } = useTranslation(); - const rowId = cell?.rowId; - const viewId = useViewId(); - const typeOption = useTypeOption(field.id); - const options = useMemo(() => typeOption.options ?? [], [typeOption.options]); - const scrollRef = useRef(null); - const inputRef = useRef(null); - const selectedOptionIds = useMemo(() => cell?.data?.selectedOptionIds ?? [], [cell]); - const [newOptionName, setNewOptionName] = useState(''); - - const filteredOptions: KeyboardNavigationOption[] = useMemo(() => { - const result = options - .filter((option) => { - return option.name.toLowerCase().includes(newOptionName.toLowerCase()); - }) - .map((option) => ({ - key: option.id, - content: ( - - ), - })); - - if (result.length === 0 && newOptionName) { - result.push({ - key: CREATE_OPTION_KEY, - content: , - }); - } - - return result; - }, [newOptionName, options, selectedOptionIds, cell?.fieldId]); - - const shouldCreateOption = filteredOptions.length === 1 && filteredOptions[0].key === 'createOption'; - - const updateCell = useCallback( - async (optionIds: string[]) => { - if (!cell || !rowId) return; - const deleteOptionIds = selectedOptionIds?.filter((id) => optionIds.find((cur) => cur === id) === undefined); - - await cellService.updateSelectCell(viewId, rowId, field.id, { - insertOptionIds: optionIds, - deleteOptionIds, - }); - onUpdated?.(); - }, - [cell, field.id, onUpdated, rowId, selectedOptionIds, viewId] - ); - - const createOption = useCallback(async () => { - const option = await createSelectOption(viewId, field.id, newOptionName); - - if (!option) return; - await insertOrUpdateSelectOption(viewId, field.id, [option]); - setNewOptionName(''); - return option; - }, [viewId, field.id, newOptionName]); - - const onConfirm = useCallback( - async (key: string) => { - let optionId = key; - - if (key === CREATE_OPTION_KEY) { - const option = await createOption(); - - optionId = option?.id || ''; - } - - if (!optionId) return; - - if (field.type === FieldType.SingleSelect) { - const newOptionIds = [optionId]; - - if (selectedOptionIds?.includes(optionId)) { - newOptionIds.pop(); - } - - void updateCell(newOptionIds); - return; - } - - let newOptionIds = []; - - if (!selectedOptionIds) { - newOptionIds.push(optionId); - } else { - const isSelected = selectedOptionIds.includes(optionId); - - if (isSelected) { - newOptionIds = selectedOptionIds.filter((id) => id !== optionId); - } else { - newOptionIds = [...selectedOptionIds, optionId]; - } - } - - void updateCell(newOptionIds); - }, - [createOption, field.type, selectedOptionIds, updateCell] - ); - - return ( -
- - - {filteredOptions.length > 0 && ( -
- {shouldCreateOption ? t('grid.selectOption.createNew') : t('grid.selectOption.orSelectOne')} -
- )} - -
- null} - /> -
-
- ); -} - -export default SelectCellActions; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/select/select_cell_actions/SelectOptionItem.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/select/select_cell_actions/SelectOptionItem.tsx deleted file mode 100644 index 2a855a4085..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/select/select_cell_actions/SelectOptionItem.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { FC, MouseEventHandler, useCallback, useRef, useState } from 'react'; -import { IconButton } from '@mui/material'; -import { ReactComponent as DetailsSvg } from '$app/assets/details.svg'; -import { SelectOption } from '$app/application/database'; -import { SelectOptionModifyMenu } from '../SelectOptionModifyMenu'; -import { Tag } from '../Tag'; -import { ReactComponent as SelectCheckSvg } from '$app/assets/select-check.svg'; - -export interface SelectOptionItemProps { - option: SelectOption; - fieldId: string; - isSelected?: boolean; -} - -export const SelectOptionItem: FC = ({ isSelected, fieldId, option }) => { - const [open, setOpen] = useState(false); - const anchorEl = useRef(null); - const [hovered, setHovered] = useState(false); - const handleClick = useCallback>((event) => { - event.stopPropagation(); - setOpen(true); - }, []); - - return ( - <> -
setHovered(true)} - onMouseLeave={() => setHovered(false)} - > -
- -
- {isSelected && !hovered && } - {hovered && ( - - - - )} -
- {open && ( - setOpen(false), - }} - /> - )} - - ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/select/select_field_actions/AddAnOption.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/select/select_field_actions/AddAnOption.tsx deleted file mode 100644 index 0fb180bb08..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/select/select_field_actions/AddAnOption.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import React, { useMemo, useState } from 'react'; -import Button from '@mui/material/Button'; -import { useTranslation } from 'react-i18next'; -import { ReactComponent as AddSvg } from '$app/assets/add.svg'; -import { OutlinedInput } from '@mui/material'; -import { - createSelectOption, - insertOrUpdateSelectOption, -} from '$app/application/database/field/select_option/select_option_service'; -import { useViewId } from '$app/hooks'; -import { SelectOption } from '$app/application/database'; -import { notify } from '$app/components/_shared/notify'; - -function AddAnOption({ fieldId, options }: { fieldId: string; options: SelectOption[] }) { - const viewId = useViewId(); - const { t } = useTranslation(); - const [edit, setEdit] = useState(false); - const [newOptionName, setNewOptionName] = useState(''); - const exitEdit = () => { - setNewOptionName(''); - setEdit(false); - }; - - const isOptionExist = useMemo(() => { - return options.some((option) => option.name === newOptionName); - }, [options, newOptionName]); - - const createOption = async () => { - if (!newOptionName) return; - if (isOptionExist) { - notify.error(t('grid.field.optionAlreadyExist')); - return; - } - - const option = await createSelectOption(viewId, fieldId, newOptionName); - - if (!option) return; - await insertOrUpdateSelectOption(viewId, fieldId, [option]); - setNewOptionName(''); - }; - - return edit ? ( - { - setNewOptionName(e.target.value); - }} - value={newOptionName} - onKeyDown={(e) => { - if (e.key === 'Enter') { - e.stopPropagation(); - e.preventDefault(); - void createOption(); - return; - } - - if (e.key === 'Escape') { - e.stopPropagation(); - e.preventDefault(); - exitEdit(); - } - }} - className={'mx-2 mb-1'} - placeholder={t('grid.selectOption.typeANewOption')} - size='small' - /> - ) : ( - - ); -} - -export default AddAnOption; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/select/select_field_actions/Option.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/select/select_field_actions/Option.tsx deleted file mode 100644 index ad363d4a1d..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/select/select_field_actions/Option.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React, { useRef, useState } from 'react'; -import { ReactComponent as MoreIcon } from '$app/assets/more.svg'; -import { SelectOption } from '$app/application/database'; -// import { ReactComponent as DragIcon } from '$app/assets/drag.svg'; - -import { SelectOptionModifyMenu } from '$app/components/database/components/field_types/select/SelectOptionModifyMenu'; -import Button from '@mui/material/Button'; -import { SelectOptionColorMap } from '$app/components/database/components/field_types/select/constants'; - -function Option({ option, fieldId }: { option: SelectOption; fieldId: string }) { - const [expanded, setExpanded] = useState(false); - const ref = useRef(null); - - return ( - <> - - setExpanded(false), - open: expanded, - transformOrigin: { - vertical: 'center', - horizontal: 'left', - }, - anchorOrigin: { vertical: 'center', horizontal: 'right' }, - }} - option={option} - /> - - ); -} - -export default Option; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/select/select_field_actions/Options.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/select/select_field_actions/Options.tsx deleted file mode 100644 index 4e06236263..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/select/select_field_actions/Options.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; -import { SelectOption } from '$app/application/database'; -import Option from './Option'; - -interface Props { - options: SelectOption[]; - fieldId: string; -} -function Options({ options, fieldId }: Props) { - return ( -
- {options.map((option) => { - return
- ); -} - -export default Options; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/select/select_field_actions/SelectFieldActions.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/select/select_field_actions/SelectFieldActions.tsx deleted file mode 100644 index a3d51ceb60..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/select/select_field_actions/SelectFieldActions.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React, { useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; -import AddAnOption from '$app/components/database/components/field_types/select/select_field_actions/AddAnOption'; -import Options from '$app/components/database/components/field_types/select/select_field_actions/Options'; -import { SelectField, SelectTypeOption } from '$app/application/database'; -import { Divider } from '@mui/material'; -import { useTypeOption } from '$app/components/database'; - -function SelectFieldActions({ field }: { field: SelectField }) { - const typeOption = useTypeOption(field.id); - const options = useMemo(() => typeOption.options ?? [], [typeOption.options]); - const { t } = useTranslation(); - - return ( - <> -
-
{t('grid.field.optionTitle')}
- - -
- - - ); -} - -export default SelectFieldActions; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/text/EditTextCellInput.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/text/EditTextCellInput.tsx deleted file mode 100644 index 005d185c8f..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/field_types/text/EditTextCellInput.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import React, { useCallback } from 'react'; -import { Popover, TextareaAutosize } from '@mui/material'; - -interface Props { - editing: boolean; - anchorEl: HTMLDivElement | null; - onClose: () => void; - text: string; - onInput: (event: React.FormEvent) => void; -} - -function EditTextCellInput({ editing, anchorEl, onClose, text, onInput }: Props) { - const handleEnter = (e: React.KeyboardEvent) => { - const shift = e.shiftKey; - - // If shift is pressed, allow the user to enter a new line, otherwise close the popover - if (!shift && e.key === 'Enter') { - e.preventDefault(); - e.stopPropagation(); - onClose(); - } - }; - - const setRef = useCallback((e: HTMLTextAreaElement | null) => { - if (!e) return; - const selectionStart = e.value.length; - - e.setSelectionRange(selectionStart, selectionStart); - }, []); - - return ( - { - if (e.key === 'Escape') { - e.stopPropagation(); - e.preventDefault(); - onClose(); - } - }} - > - - - ); -} - -export default EditTextCellInput; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/ConditionSelect.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/ConditionSelect.tsx deleted file mode 100644 index 0ca6c42a86..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/ConditionSelect.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import React, { useCallback, useMemo, useState } from 'react'; -import KeyboardNavigation, { - KeyboardNavigationOption, -} from '$app/components/_shared/keyboard_navigation/KeyboardNavigation'; -import { ReactComponent as MoreSvg } from '$app/assets/more.svg'; -import Popover from '@mui/material/Popover'; - -function ConditionSelect({ - conditions, - value, - onChange, -}: { - conditions: { - value: number; - text: string; - }[]; - value: number; - onChange: (condition: number) => void; -}) { - const [anchorEl, setAnchorEl] = useState(null); - const options: KeyboardNavigationOption[] = useMemo(() => { - return conditions.map((condition) => { - return { - key: condition.value, - content: condition.text, - }; - }); - }, [conditions]); - - const handleClose = useCallback(() => { - setAnchorEl(null); - }, []); - - const onConfirm = useCallback( - (key: number) => { - onChange(key); - }, - [onChange] - ); - - const valueText = useMemo(() => { - return conditions.find((condition) => condition.value === value)?.text; - }, [conditions, value]); - - const open = Boolean(anchorEl); - - return ( -
-
{ - setAnchorEl(e.currentTarget); - }} - className={'flex cursor-pointer select-none items-center gap-2 py-2 text-xs'} - > -
{valueText}
- -
- - - -
- ); -} - -export default ConditionSelect; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/Filter.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/Filter.tsx deleted file mode 100644 index fdd7bccb5b..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/Filter.tsx +++ /dev/null @@ -1,200 +0,0 @@ -import React, { FC, useMemo, useState } from 'react'; -import { - CheckboxFilterData, - ChecklistFilterData, - DateFilterData, - Field as FieldData, - Filter as FilterType, - NumberFilterData, - SelectFilterData, - TextFilterData, - UndeterminedFilter, -} from '$app/application/database'; -import { Chip, Popover } from '@mui/material'; -import { Property } from '$app/components/database/components/property'; -import { ReactComponent as DropDownSvg } from '$app/assets/dropdown.svg'; -import TextFilter from './text_filter/TextFilter'; -import { CheckboxFilterConditionPB, ChecklistFilterConditionPB, FieldType } from '@/services/backend'; -import FilterActions from '$app/components/database/components/filter/FilterActions'; -import { updateFilter } from '$app/application/database/filter/filter_service'; -import { useViewId } from '$app/hooks'; -import SelectFilter from './select_filter/SelectFilter'; - -import DateFilter from '$app/components/database/components/filter/date_filter/DateFilter'; -import FilterConditionSelect from '$app/components/database/components/filter/FilterConditionSelect'; -import TextFilterValue from '$app/components/database/components/filter/text_filter/TextFilterValue'; -import SelectFilterValue from '$app/components/database/components/filter/select_filter/SelectFilterValue'; -import NumberFilterValue from '$app/components/database/components/filter/number_filter/NumberFilterValue'; -import { useTranslation } from 'react-i18next'; -import DateFilterValue from '$app/components/database/components/filter/date_filter/DateFilterValue'; - -interface Props { - filter: FilterType; - field: FieldData; -} - -interface FilterComponentProps { - filter: FilterType; - field: FieldData; - onChange: (data: UndeterminedFilter['data']) => void; - onClose?: () => void; -} - -type FilterComponent = FC; -const getFilterComponent = (field: FieldData) => { - switch (field.type) { - case FieldType.RichText: - case FieldType.URL: - case FieldType.Number: - return TextFilter as FilterComponent; - case FieldType.SingleSelect: - case FieldType.MultiSelect: - return SelectFilter as FilterComponent; - - case FieldType.DateTime: - case FieldType.LastEditedTime: - case FieldType.CreatedTime: - return DateFilter as FilterComponent; - default: - return null; - } -}; - -function Filter({ filter, field }: Props) { - const viewId = useViewId(); - const { t } = useTranslation(); - const [anchorEl, setAnchorEl] = useState(null); - const open = Boolean(anchorEl); - const handleClick = (e: React.MouseEvent) => { - setAnchorEl(e.currentTarget); - }; - - const handleClose = () => { - setAnchorEl(null); - }; - - const onDataChange = async (data: UndeterminedFilter['data']) => { - const newFilter = { - ...filter, - data: { - ...(filter.data || {}), - ...data, - }, - } as UndeterminedFilter; - - try { - await updateFilter(viewId, newFilter); - } catch (e) { - // toast.error(e.message); - } - }; - - const Component = getFilterComponent(field); - - const condition = useMemo(() => { - switch (field.type) { - case FieldType.RichText: - case FieldType.URL: - return (filter.data as TextFilterData).condition; - case FieldType.SingleSelect: - case FieldType.MultiSelect: - return (filter.data as SelectFilterData).condition; - case FieldType.Number: - return (filter.data as NumberFilterData).condition; - case FieldType.Checkbox: - return (filter.data as CheckboxFilterData).condition; - case FieldType.Checklist: - return (filter.data as ChecklistFilterData).condition; - case FieldType.DateTime: - case FieldType.LastEditedTime: - case FieldType.CreatedTime: - return (filter.data as DateFilterData).condition; - default: - return; - } - }, [field, filter]); - - const conditionValue = useMemo(() => { - switch (field.type) { - case FieldType.RichText: - case FieldType.URL: - return ; - case FieldType.SingleSelect: - case FieldType.MultiSelect: - return ; - case FieldType.Number: - return ; - case FieldType.Checkbox: - return (filter.data as CheckboxFilterData).condition === CheckboxFilterConditionPB.IsChecked - ? t('grid.checkboxFilter.isChecked') - : t('grid.checkboxFilter.isUnchecked'); - case FieldType.Checklist: - return (filter.data as ChecklistFilterData).condition === ChecklistFilterConditionPB.IsComplete - ? t('grid.checklistFilter.isComplete') - : t('grid.checklistFilter.isIncomplted'); - case FieldType.DateTime: - case FieldType.LastEditedTime: - case FieldType.CreatedTime: - return ; - default: - return ''; - } - }, [field.id, field.type, filter.data, t]); - - return ( - <> - - - {conditionValue} - -
- } - onClick={handleClick} - /> - {condition !== undefined && open && ( - { - if (e.key === 'Escape') { - e.preventDefault(); - e.stopPropagation(); - handleClose(); - } - }} - > -
- { - void onDataChange({ - condition, - }); - }} - /> - -
- {Component && } -
- )} - - ); -} - -export default Filter; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/FilterActions.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/FilterActions.tsx deleted file mode 100644 index ebc9e8982c..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/FilterActions.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import React, { useMemo, useState } from 'react'; -import { IconButton, Menu } from '@mui/material'; -import { ReactComponent as MoreSvg } from '$app/assets/details.svg'; -import { Filter } from '$app/application/database'; -import { useTranslation } from 'react-i18next'; -import { deleteFilter } from '$app/application/database/filter/filter_service'; -import { useViewId } from '$app/hooks'; -import KeyboardNavigation, { - KeyboardNavigationOption, -} from '$app/components/_shared/keyboard_navigation/KeyboardNavigation'; - -function FilterActions({ filter }: { filter: Filter }) { - const viewId = useViewId(); - const { t } = useTranslation(); - const [disableSelect, setDisableSelect] = useState(true); - const [anchorEl, setAnchorEl] = useState(null); - const open = Boolean(anchorEl); - const onClose = () => { - setDisableSelect(true); - setAnchorEl(null); - }; - - const onDelete = async () => { - try { - await deleteFilter(viewId, filter); - } catch (e) { - // toast.error(e.message); - } - - setDisableSelect(true); - }; - - const options: KeyboardNavigationOption[] = useMemo( - () => [ - { - key: 'delete', - content: t('grid.settings.deleteFilter'), - }, - ], - [t] - ); - - return ( - <> - { - setAnchorEl(e.currentTarget); - }} - className={'mx-2 my-1.5'} - > - - - {open && ( - { - if (e.key === 'Escape') { - e.preventDefault(); - e.stopPropagation(); - onClose(); - } - }} - keepMounted={false} - open={open} - anchorEl={anchorEl} - onClose={onClose} - > - { - if (e.key === 'ArrowDown') { - setDisableSelect(false); - } - }} - disableSelect={disableSelect} - options={options} - onConfirm={onDelete} - onEscape={onClose} - /> - - )} - - ); -} - -export default FilterActions; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/FilterConditionSelect.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/FilterConditionSelect.tsx deleted file mode 100644 index 8b793942da..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/FilterConditionSelect.tsx +++ /dev/null @@ -1,224 +0,0 @@ -import React, { useMemo } from 'react'; -import ConditionSelect from './ConditionSelect'; -import { - CheckboxFilterConditionPB, - ChecklistFilterConditionPB, - DateFilterConditionPB, - FieldType, - NumberFilterConditionPB, - SelectOptionFilterConditionPB, - TextFilterConditionPB, -} from '@/services/backend'; - -import { useTranslation } from 'react-i18next'; - -function FilterConditionSelect({ - name, - condition, - fieldType, - onChange, -}: { - name: string; - condition: number; - fieldType: FieldType; - onChange: (condition: number) => void; -}) { - const { t } = useTranslation(); - const conditions = useMemo(() => { - switch (fieldType) { - case FieldType.RichText: - case FieldType.URL: - return [ - { - value: TextFilterConditionPB.TextContains, - text: t('grid.textFilter.contains'), - }, - { - value: TextFilterConditionPB.TextDoesNotContain, - text: t('grid.textFilter.doesNotContain'), - }, - { - value: TextFilterConditionPB.TextStartsWith, - text: t('grid.textFilter.startWith'), - }, - { - value: TextFilterConditionPB.TextEndsWith, - text: t('grid.textFilter.endsWith'), - }, - { - value: TextFilterConditionPB.TextIs, - text: t('grid.textFilter.is'), - }, - { - value: TextFilterConditionPB.TextIsNot, - text: t('grid.textFilter.isNot'), - }, - { - value: TextFilterConditionPB.TextIsEmpty, - text: t('grid.textFilter.isEmpty'), - }, - { - value: TextFilterConditionPB.TextIsNotEmpty, - text: t('grid.textFilter.isNotEmpty'), - }, - ]; - case FieldType.SingleSelect: - return [ - { - value: SelectOptionFilterConditionPB.OptionIs, - text: t('grid.selectOptionFilter.is'), - }, - { - value: SelectOptionFilterConditionPB.OptionIsNot, - text: t('grid.selectOptionFilter.isNot'), - }, - { - value: SelectOptionFilterConditionPB.OptionIsEmpty, - text: t('grid.selectOptionFilter.isEmpty'), - }, - { - value: SelectOptionFilterConditionPB.OptionIsNotEmpty, - text: t('grid.selectOptionFilter.isNotEmpty'), - }, - ]; - case FieldType.MultiSelect: - return [ - { - value: SelectOptionFilterConditionPB.OptionIs, - text: t('grid.selectOptionFilter.is'), - }, - { - value: SelectOptionFilterConditionPB.OptionIsNot, - text: t('grid.selectOptionFilter.isNot'), - }, - { - value: SelectOptionFilterConditionPB.OptionContains, - text: t('grid.selectOptionFilter.contains'), - }, - { - value: SelectOptionFilterConditionPB.OptionDoesNotContain, - text: t('grid.selectOptionFilter.doesNotContain'), - }, - { - value: SelectOptionFilterConditionPB.OptionIsEmpty, - text: t('grid.selectOptionFilter.isEmpty'), - }, - { - value: SelectOptionFilterConditionPB.OptionIsNotEmpty, - text: t('grid.selectOptionFilter.isNotEmpty'), - }, - ]; - case FieldType.Number: - return [ - { - value: NumberFilterConditionPB.Equal, - text: '=', - }, - { - value: NumberFilterConditionPB.NotEqual, - text: '!=', - }, - { - value: NumberFilterConditionPB.GreaterThan, - text: '>', - }, - { - value: NumberFilterConditionPB.LessThan, - text: '<', - }, - { - value: NumberFilterConditionPB.GreaterThanOrEqualTo, - text: '>=', - }, - { - value: NumberFilterConditionPB.LessThanOrEqualTo, - text: '<=', - }, - { - value: NumberFilterConditionPB.NumberIsEmpty, - text: t('grid.textFilter.isEmpty'), - }, - { - value: NumberFilterConditionPB.NumberIsNotEmpty, - text: t('grid.textFilter.isNotEmpty'), - }, - ]; - case FieldType.Checkbox: - return [ - { - value: CheckboxFilterConditionPB.IsChecked, - text: t('grid.checkboxFilter.isChecked'), - }, - { - value: CheckboxFilterConditionPB.IsUnChecked, - text: t('grid.checkboxFilter.isUnchecked'), - }, - ]; - case FieldType.Checklist: - return [ - { - value: ChecklistFilterConditionPB.IsComplete, - text: t('grid.checklistFilter.isComplete'), - }, - { - value: ChecklistFilterConditionPB.IsIncomplete, - text: t('grid.checklistFilter.isIncomplted'), - }, - ]; - case FieldType.DateTime: - case FieldType.LastEditedTime: - case FieldType.CreatedTime: - return [ - { - value: DateFilterConditionPB.DateIs, - text: t('grid.dateFilter.is'), - }, - { - value: DateFilterConditionPB.DateBefore, - text: t('grid.dateFilter.before'), - }, - { - value: DateFilterConditionPB.DateAfter, - text: t('grid.dateFilter.after'), - }, - { - value: DateFilterConditionPB.DateOnOrBefore, - text: t('grid.dateFilter.onOrBefore'), - }, - { - value: DateFilterConditionPB.DateOnOrAfter, - text: t('grid.dateFilter.onOrAfter'), - }, - { - value: DateFilterConditionPB.DateWithIn, - text: t('grid.dateFilter.between'), - }, - { - value: DateFilterConditionPB.DateIsEmpty, - text: t('grid.dateFilter.empty'), - }, - { - value: DateFilterConditionPB.DateIsNotEmpty, - text: t('grid.dateFilter.notEmpty'), - }, - ]; - default: - return []; - } - }, [fieldType, t]); - - return ( -
-
{name}
- { - onChange(e); - }} - value={condition} - /> -
- ); -} - -export default FilterConditionSelect; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/FilterFieldsMenu.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/FilterFieldsMenu.tsx deleted file mode 100644 index e161badbf8..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/FilterFieldsMenu.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import React, { useCallback } from 'react'; -import { MenuProps } from '@mui/material'; -import PropertiesList from '$app/components/database/components/property/PropertiesList'; -import { Field } from '$app/application/database'; -import { useViewId } from '$app/hooks'; -import { useTranslation } from 'react-i18next'; -import { insertFilter } from '$app/application/database/filter/filter_service'; -import { getDefaultFilter } from '$app/application/database/filter/filter_data'; -import Popover from '@mui/material/Popover'; - -function FilterFieldsMenu({ - onInserted, - ...props -}: MenuProps & { - onInserted?: () => void; -}) { - const viewId = useViewId(); - const { t } = useTranslation(); - - const addFilter = useCallback( - async (field: Field) => { - const filterData = getDefaultFilter(field.type); - - await insertFilter({ - viewId, - fieldId: field.id, - fieldType: field.type, - data: filterData, - }); - props.onClose?.({}, 'backdropClick'); - onInserted?.(); - }, - [props, viewId, onInserted] - ); - - return ( - { - if (e.key === 'Escape') { - e.preventDefault(); - e.stopPropagation(); - props.onClose?.({}, 'escapeKeyDown'); - } - }} - {...props} - > - { - props.onClose?.({}, 'escapeKeyDown'); - }} - showSearch - searchPlaceholder={t('grid.settings.filterBy')} - onItemClick={addFilter} - /> - - ); -} - -export default FilterFieldsMenu; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/Filters.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/Filters.tsx deleted file mode 100644 index 860ce9f69f..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/Filters.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React, { useMemo, useState } from 'react'; -import Filter from '$app/components/database/components/filter/Filter'; -import Button from '@mui/material/Button'; -import FilterFieldsMenu from '$app/components/database/components/filter/FilterFieldsMenu'; -import { useTranslation } from 'react-i18next'; -import { ReactComponent as AddSvg } from '$app/assets/add.svg'; -import { useDatabase } from '$app/components/database'; - -function Filters() { - const { t } = useTranslation(); - const { filters, fields } = useDatabase(); - - const options = useMemo(() => { - return filters.map((filter) => { - const field = fields.find((field) => field.id === filter.fieldId); - - return { - filter, - field, - }; - }); - }, [filters, fields]); - - const [filterAnchorEl, setFilterAnchorEl] = useState(null); - const openAddFilterMenu = Boolean(filterAnchorEl); - - const handleClick = (e: React.MouseEvent) => { - setFilterAnchorEl(e.currentTarget); - }; - - return ( -
- {options.map(({ filter, field }) => (field ? : null))} - - setFilterAnchorEl(null)} - anchorOrigin={{ - vertical: 'bottom', - horizontal: 'left', - }} - /> -
- ); -} - -export default Filters; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/date_filter/DateFilter.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/date_filter/DateFilter.tsx deleted file mode 100644 index 5c96d42b96..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/date_filter/DateFilter.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import React, { useMemo } from 'react'; -import { - DateFilter as DateFilterType, - DateFilterData, - DateTimeField, - DateTimeTypeOption, -} from '$app/application/database'; -import { DateFilterConditionPB } from '@/services/backend'; -import CustomCalendar from '$app/components/database/components/field_types/date/CustomCalendar'; -import DateTimeSet from '$app/components/database/components/field_types/date/DateTimeSet'; -import { useTypeOption } from '$app/components/database'; -import { getDateFormat, getTimeFormat } from '$app/components/database/components/field_types/date/utils'; - -interface Props { - filter: DateFilterType; - field: DateTimeField; - onChange: (filterData: DateFilterData) => void; -} - -function DateFilter({ filter, field, onChange }: Props) { - const typeOption = useTypeOption(field.id); - - const showCalendar = - filter.data.condition !== DateFilterConditionPB.DateIsEmpty && - filter.data.condition !== DateFilterConditionPB.DateIsNotEmpty; - - const condition = filter.data.condition; - const isRange = condition === DateFilterConditionPB.DateWithIn; - const timestamp = useMemo(() => { - if (isRange) { - return filter.data.start; - } - - return filter.data.timestamp; - }, [filter.data.start, filter.data.timestamp, isRange]); - - const endTimestamp = useMemo(() => { - if (isRange) { - return filter.data.end; - } - - return; - }, [filter.data.end, isRange]); - - const timeFormat = useMemo(() => { - return getTimeFormat(typeOption.timeFormat); - }, [typeOption.timeFormat]); - - const dateFormat = useMemo(() => { - return getDateFormat(typeOption.dateFormat); - }, [typeOption.dateFormat]); - - return ( -
- {showCalendar && ( - <> -
- { - onChange({ - condition, - timestamp: date, - start: endDate ? date : undefined, - end: endDate, - }); - }} - date={timestamp} - endDate={endTimestamp} - timeFormat={timeFormat} - dateFormat={dateFormat} - includeTime={false} - isRange={isRange} - /> -
- { - onChange({ - condition, - timestamp: date, - start: endDate ? date : undefined, - end: endDate, - }); - }} - isRange={isRange} - timestamp={timestamp} - endTimestamp={endTimestamp} - /> - - )} -
- ); -} - -export default DateFilter; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/date_filter/DateFilterValue.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/date_filter/DateFilterValue.tsx deleted file mode 100644 index dd75d25852..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/date_filter/DateFilterValue.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React, { useMemo } from 'react'; -import { DateFilterData } from '$app/application/database'; -import { useTranslation } from 'react-i18next'; -import dayjs from 'dayjs'; -import { DateFilterConditionPB } from '@/services/backend'; - -function DateFilterValue({ data }: { data: DateFilterData }) { - const { t } = useTranslation(); - - const value = useMemo(() => { - if (!data.timestamp) return ''; - - let startStr = ''; - let endStr = ''; - - if (data.start) { - const end = data.end ?? data.start; - const moreThanOneYear = dayjs.unix(end).diff(dayjs.unix(data.start), 'year') > 1; - const format = moreThanOneYear ? 'MMM D, YYYY' : 'MMM D'; - - startStr = dayjs.unix(data.start).format(format); - endStr = dayjs.unix(end).format(format); - } - - const timestamp = dayjs.unix(data.timestamp).format('MMM D'); - - switch (data.condition) { - case DateFilterConditionPB.DateIs: - return `: ${timestamp}`; - case DateFilterConditionPB.DateBefore: - return `: ${t('grid.dateFilter.choicechipPrefix.before')} ${timestamp}`; - case DateFilterConditionPB.DateAfter: - return `: ${t('grid.dateFilter.choicechipPrefix.after')} ${timestamp}`; - case DateFilterConditionPB.DateOnOrBefore: - return `: ${t('grid.dateFilter.choicechipPrefix.onOrBefore')} ${timestamp}`; - case DateFilterConditionPB.DateOnOrAfter: - return `: ${t('grid.dateFilter.choicechipPrefix.onOrAfter')} ${timestamp}`; - case DateFilterConditionPB.DateWithIn: - return `: ${startStr} - ${endStr}`; - case DateFilterConditionPB.DateIsEmpty: - return `: ${t('grid.dateFilter.choicechipPrefix.isEmpty')}`; - case DateFilterConditionPB.DateIsNotEmpty: - return `: ${t('grid.dateFilter.choicechipPrefix.isNotEmpty')}`; - default: - return ''; - } - }, [data, t]); - - return <>{value}; -} - -export default DateFilterValue; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/number_filter/NumberFilterValue.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/number_filter/NumberFilterValue.tsx deleted file mode 100644 index 658ef13d69..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/number_filter/NumberFilterValue.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React, { useMemo } from 'react'; -import { NumberFilterData } from '$app/application/database'; -import { NumberFilterConditionPB } from '@/services/backend'; -import { useTranslation } from 'react-i18next'; - -function NumberFilterValue({ data }: { data: NumberFilterData }) { - const { t } = useTranslation(); - - const value = useMemo(() => { - if (!data.content) { - return ''; - } - - const content = parseInt(data.content); - - switch (data.condition) { - case NumberFilterConditionPB.Equal: - return `= ${content}`; - case NumberFilterConditionPB.NotEqual: - return `!= ${content}`; - case NumberFilterConditionPB.GreaterThan: - return `> ${content}`; - case NumberFilterConditionPB.GreaterThanOrEqualTo: - return `>= ${content}`; - case NumberFilterConditionPB.LessThan: - return `< ${content}`; - case NumberFilterConditionPB.LessThanOrEqualTo: - return `<= ${content}`; - case NumberFilterConditionPB.NumberIsEmpty: - return t('grid.textFilter.isEmpty'); - case NumberFilterConditionPB.NumberIsNotEmpty: - return t('grid.textFilter.isNotEmpty'); - } - }, [data.condition, data.content, t]); - - return <>{value}; -} - -export default NumberFilterValue; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/select_filter/SelectFilter.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/select_filter/SelectFilter.tsx deleted file mode 100644 index bd1d1f239a..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/select_filter/SelectFilter.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import React, { useMemo, useRef } from 'react'; -import { - SelectField, - SelectFilter as SelectFilterType, - SelectFilterData, - SelectTypeOption, -} from '$app/application/database'; -import { Tag } from '$app/components/database/components/field_types/select/Tag'; -import { ReactComponent as SelectCheckSvg } from '$app/assets/select-check.svg'; -import { SelectOptionFilterConditionPB } from '@/services/backend'; -import { useTypeOption } from '$app/components/database'; -import KeyboardNavigation, { - KeyboardNavigationOption, -} from '$app/components/_shared/keyboard_navigation/KeyboardNavigation'; - -interface Props { - filter: SelectFilterType; - field: SelectField; - onChange: (filterData: SelectFilterData) => void; - onClose?: () => void; -} - -function SelectFilter({ onClose, filter, field, onChange }: Props) { - const scrollRef = useRef(null); - const condition = filter.data.condition; - const typeOption = useTypeOption(field.id); - const options: KeyboardNavigationOption[] = useMemo(() => { - return ( - typeOption?.options?.map((option) => { - return { - key: option.id, - content: ( -
- - {filter.data.optionIds?.includes(option.id) && } -
- ), - }; - }) ?? [] - ); - }, [filter.data.optionIds, typeOption?.options]); - - const showOptions = - options.length > 0 && - condition !== SelectOptionFilterConditionPB.OptionIsEmpty && - condition !== SelectOptionFilterConditionPB.OptionIsNotEmpty; - - const handleChange = ({ - condition, - optionIds, - }: { - condition?: SelectFilterData['condition']; - optionIds?: SelectFilterData['optionIds']; - }) => { - onChange({ - condition: condition ?? filter.data.condition, - optionIds: optionIds ?? filter.data.optionIds, - }); - }; - - const handleSelectOption = (optionId: string) => { - const prev = filter.data.optionIds; - let newOptionIds = []; - - if (!prev) { - newOptionIds.push(optionId); - } else { - const isSelected = prev.includes(optionId); - - if (isSelected) { - newOptionIds = prev.filter((id) => id !== optionId); - } else { - newOptionIds = [...prev, optionId]; - } - } - - handleChange({ - condition, - optionIds: newOptionIds, - }); - }; - - if (!showOptions) return null; - - return ( -
- -
- ); -} - -export default SelectFilter; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/select_filter/SelectFilterValue.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/select_filter/SelectFilterValue.tsx deleted file mode 100644 index 72576deae1..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/select_filter/SelectFilterValue.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React, { useMemo } from 'react'; -import { SelectFilterData, SelectTypeOption } from '$app/application/database'; -import { useStaticTypeOption } from '$app/components/database'; -import { useTranslation } from 'react-i18next'; -import { SelectOptionFilterConditionPB } from '@/services/backend'; - -function SelectFilterValue({ data, fieldId }: { data: SelectFilterData; fieldId: string }) { - const typeOption = useStaticTypeOption(fieldId); - const { t } = useTranslation(); - const value = useMemo(() => { - if (!data.optionIds?.length) return ''; - - const options = data.optionIds - .map((optionId) => { - const option = typeOption?.options?.find((option) => option.id === optionId); - - return option?.name; - }) - .join(', '); - - switch (data.condition) { - case SelectOptionFilterConditionPB.OptionIs: - return `: ${options}`; - case SelectOptionFilterConditionPB.OptionIsNot: - return `: ${t('grid.textFilter.choicechipPrefix.isNot')} ${options}`; - case SelectOptionFilterConditionPB.OptionIsEmpty: - return `: ${t('grid.textFilter.choicechipPrefix.isEmpty')}`; - case SelectOptionFilterConditionPB.OptionIsNotEmpty: - return `: ${t('grid.textFilter.choicechipPrefix.isNotEmpty')}`; - default: - return ''; - } - }, [data.condition, data.optionIds, t, typeOption?.options]); - - return <>{value}; -} - -export default SelectFilterValue; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/text_filter/TextFilter.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/text_filter/TextFilter.tsx deleted file mode 100644 index 0c7eab6e05..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/text_filter/TextFilter.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import React, { useMemo, useState } from 'react'; -import { TextFilter as TextFilterType, TextFilterData } from '$app/application/database'; -import { TextField } from '@mui/material'; -import { useTranslation } from 'react-i18next'; -import { TextFilterConditionPB } from '@/services/backend'; -import debounce from 'lodash-es/debounce'; - -interface Props { - filter: TextFilterType; - onChange: (filterData: TextFilterData) => void; -} - -const DELAY = 500; - -function TextFilter({ filter, onChange }: Props) { - const { t } = useTranslation(); - const [content, setContext] = useState(filter.data.content); - const condition = filter.data.condition; - const showField = - condition !== TextFilterConditionPB.TextIsEmpty && condition !== TextFilterConditionPB.TextIsNotEmpty; - - const onConditionChange = useMemo(() => { - return debounce((content: string) => { - onChange({ - content, - condition, - }); - }, DELAY); - }, [condition, onChange]); - - if (!showField) return null; - return ( - { - setContext(e.target.value); - onConditionChange(e.target.value ?? ''); - }} - /> - ); -} - -export default TextFilter; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/text_filter/TextFilterValue.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/text_filter/TextFilterValue.tsx deleted file mode 100644 index 5718a3e2b8..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/filter/text_filter/TextFilterValue.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { useMemo } from 'react'; -import { TextFilterData } from '$app/application/database'; -import { TextFilterConditionPB } from '@/services/backend'; -import { useTranslation } from 'react-i18next'; - -function TextFilterValue({ data }: { data: TextFilterData }) { - const { t } = useTranslation(); - - const value = useMemo(() => { - if (!data.content) return ''; - switch (data.condition) { - case TextFilterConditionPB.TextContains: - case TextFilterConditionPB.TextIs: - return `: ${data.content}`; - case TextFilterConditionPB.TextDoesNotContain: - case TextFilterConditionPB.TextIsNot: - return `: ${t('grid.textFilter.choicechipPrefix.isNot')} ${data.content}`; - case TextFilterConditionPB.TextStartsWith: - return `: ${t('grid.textFilter.choicechipPrefix.startWith')} ${data.content}`; - case TextFilterConditionPB.TextEndsWith: - return `: ${t('grid.textFilter.choicechipPrefix.endWith')} ${data.content}`; - case TextFilterConditionPB.TextIsEmpty: - return `: ${t('grid.textFilter.choicechipPrefix.isEmpty')}`; - case TextFilterConditionPB.TextIsNotEmpty: - return `: ${t('grid.textFilter.choicechipPrefix.isNotEmpty')}`; - default: - return ''; - } - }, [t, data]); - - return <>{value}; -} - -export default TextFilterValue; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/index.ts deleted file mode 100644 index 7da8d0eab4..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './tab_bar'; -export * from './cell'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/NewProperty.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/NewProperty.tsx deleted file mode 100644 index bb71befa8d..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/NewProperty.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React, { useCallback } from 'react'; -import { fieldService } from '$app/application/database'; -import { FieldType } from '@/services/backend'; -import { useTranslation } from 'react-i18next'; -import Button from '@mui/material/Button'; -import { ReactComponent as AddSvg } from '$app/assets/add.svg'; -import { useViewId } from '$app/hooks'; - -interface NewPropertyProps { - onInserted?: (id: string) => void; -} -function NewProperty({ onInserted }: NewPropertyProps) { - const viewId = useViewId(); - const { t } = useTranslation(); - - const handleClick = useCallback(async () => { - try { - const field = await fieldService.createField({ - viewId, - fieldType: FieldType.RichText, - }); - - onInserted?.(field.id); - } catch (e) { - // toast.error(t('grid.field.newPropertyFail')); - } - }, [onInserted, viewId]); - - return ( - - ); -} - -export default NewProperty; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/PropertiesList.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/PropertiesList.tsx deleted file mode 100644 index a9865c467f..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/PropertiesList.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import React, { useCallback, useMemo, useRef, useState } from 'react'; -import { OutlinedInput } from '@mui/material'; -import { Property } from '$app/components/database/components/property/Property'; -import { Field as FieldType } from '$app/application/database'; -import { useDatabase } from '$app/components/database'; -import KeyboardNavigation from '$app/components/_shared/keyboard_navigation/KeyboardNavigation'; - -interface FieldListProps { - searchPlaceholder?: string; - showSearch?: boolean; - onItemClick?: (field: FieldType) => void; - onClose?: () => void; -} - -function PropertiesList({ onClose, showSearch, onItemClick, searchPlaceholder }: FieldListProps) { - const { fields } = useDatabase(); - const [fieldsResult, setFieldsResult] = useState(fields as FieldType[]); - - const onInputChange = useCallback( - (event: React.ChangeEvent) => { - const value = event.target.value; - const result = fields.filter((field) => field.name.toLowerCase().includes(value.toLowerCase())); - - setFieldsResult(result); - }, - [fields] - ); - - const inputRef = useRef(null); - - const searchInput = useMemo(() => { - return showSearch ? ( -
- -
- ) : null; - }, [onInputChange, searchPlaceholder, showSearch]); - - const scrollRef = useRef(null); - - const options = useMemo(() => { - return fieldsResult.map((field) => { - return { - key: field.id, - content: ( -
- -
- ), - }; - }); - }, [fieldsResult]); - - const onConfirm = useCallback( - (key: string) => { - const field = fields.find((field) => field.id === key); - - onItemClick?.(field as FieldType); - }, - [fields, onItemClick] - ); - - return ( -
- {searchInput} -
- -
-
- ); -} - -export default PropertiesList; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/Property.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/Property.tsx deleted file mode 100644 index 3091ba4ea1..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/Property.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { FC, useEffect, useRef, useState } from 'react'; -import { Field as FieldType } from '$app/application/database'; -import { ProppertyTypeSvg } from './property_type/ProppertyTypeSvg'; -import { PropertyMenu } from '$app/components/database/components/property/PropertyMenu'; -import usePopoverAutoPosition from '$app/components/_shared/popover/Popover.hooks'; -import { PopoverOrigin } from '@mui/material/Popover/Popover'; - -export interface FieldProps { - field: FieldType; - menuOpened?: boolean; - onOpenMenu?: (id: string) => void; - onCloseMenu?: (id: string) => void; - className?: string; -} - -const initialAnchorOrigin: PopoverOrigin = { - vertical: 'bottom', - horizontal: 'right', -}; - -const initialTransformOrigin: PopoverOrigin = { - vertical: 'top', - horizontal: 'center', -}; - -export const Property: FC = ({ field, onCloseMenu, className, menuOpened }) => { - const ref = useRef(null); - const [anchorPosition, setAnchorPosition] = useState< - | { - top: number; - left: number; - height: number; - } - | undefined - >(undefined); - - const open = Boolean(anchorPosition && menuOpened); - - useEffect(() => { - if (menuOpened) { - const rect = ref.current?.getBoundingClientRect(); - - if (rect) { - setAnchorPosition({ - top: rect.top + 28, - left: rect.left, - height: rect.height, - }); - return; - } - } - - setAnchorPosition(undefined); - }, [menuOpened]); - - const { paperHeight, paperWidth, transformOrigin, anchorOrigin, isEntered } = usePopoverAutoPosition({ - initialPaperWidth: 300, - initialPaperHeight: 400, - anchorPosition, - initialAnchorOrigin, - initialTransformOrigin, - open, - }); - - return ( - <> -
- - {field.name} -
- - {open && ( - { - onCloseMenu?.(field.id); - }} - transformOrigin={transformOrigin} - anchorOrigin={anchorOrigin} - PaperProps={{ - style: { - maxHeight: paperHeight, - width: paperWidth, - height: 'auto', - }, - className: 'flex h-full flex-col overflow-hidden', - }} - anchorPosition={anchorPosition} - anchorReference={'anchorPosition'} - /> - )} - - ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/PropertyActions.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/PropertyActions.tsx deleted file mode 100644 index b319940996..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/PropertyActions.tsx +++ /dev/null @@ -1,271 +0,0 @@ -import React, { RefObject, useCallback, useMemo, useState } from 'react'; - -import { ReactComponent as EditSvg } from '$app/assets/edit.svg'; -import { ReactComponent as HideSvg } from '$app/assets/hide.svg'; -import { ReactComponent as ShowSvg } from '$app/assets/eye_open.svg'; - -import { ReactComponent as CopySvg } from '$app/assets/copy.svg'; -import { ReactComponent as DeleteSvg } from '$app/assets/delete.svg'; -import { ReactComponent as LeftSvg } from '$app/assets/left.svg'; -import { ReactComponent as RightSvg } from '$app/assets/right.svg'; -import { useViewId } from '$app/hooks'; -import { fieldService } from '$app/application/database'; -import { OrderObjectPositionTypePB, FieldVisibility } from '@/services/backend'; -import DeleteConfirmDialog from '$app/components/_shared/confirm_dialog/DeleteConfirmDialog'; -import { useTranslation } from 'react-i18next'; -import KeyboardNavigation, { - KeyboardNavigationOption, -} from '$app/components/_shared/keyboard_navigation/KeyboardNavigation'; -import { notify } from 'src/appflowy_app/components/_shared/notify'; - -export enum FieldAction { - EditProperty, - Hide, - Show, - Duplicate, - Delete, - InsertLeft, - InsertRight, -} - -const FieldActionSvgMap = { - [FieldAction.EditProperty]: EditSvg, - [FieldAction.Hide]: HideSvg, - [FieldAction.Show]: ShowSvg, - [FieldAction.Duplicate]: CopySvg, - [FieldAction.Delete]: DeleteSvg, - [FieldAction.InsertLeft]: LeftSvg, - [FieldAction.InsertRight]: RightSvg, -}; - -const defaultActions: FieldAction[] = [ - FieldAction.EditProperty, - FieldAction.InsertLeft, - FieldAction.InsertRight, - FieldAction.Hide, - FieldAction.Duplicate, - FieldAction.Delete, -]; - -// prevent default actions for primary fields -const primaryPreventDefaultActions = [FieldAction.Hide, FieldAction.Delete, FieldAction.Duplicate]; - -interface PropertyActionsProps { - fieldId: string; - actions?: FieldAction[]; - isPrimary?: boolean; - inputRef?: RefObject; - onClose?: () => void; - onMenuItemClick?: (action: FieldAction, newFieldId?: string) => void; -} - -function PropertyActions({ - onClose, - inputRef, - fieldId, - onMenuItemClick, - isPrimary, - actions = defaultActions, -}: PropertyActionsProps) { - const viewId = useViewId(); - const { t } = useTranslation(); - const [openConfirm, setOpenConfirm] = useState(false); - const [focusMenu, setFocusMenu] = useState(false); - const menuTextMap = useMemo( - () => ({ - [FieldAction.EditProperty]: t('grid.field.editProperty'), - [FieldAction.Hide]: t('grid.field.hide'), - [FieldAction.Show]: t('grid.field.show'), - [FieldAction.Duplicate]: t('grid.field.duplicate'), - [FieldAction.Delete]: t('grid.field.delete'), - [FieldAction.InsertLeft]: t('grid.field.insertLeft'), - [FieldAction.InsertRight]: t('grid.field.insertRight'), - }), - [t] - ); - - const handleOpenConfirm = () => { - setOpenConfirm(true); - }; - - const handleMenuItemClick = async (action: FieldAction) => { - const preventDefault = isPrimary && primaryPreventDefaultActions.includes(action); - - if (preventDefault) { - return; - } - - switch (action) { - case FieldAction.EditProperty: - break; - case FieldAction.InsertLeft: - case FieldAction.InsertRight: { - const fieldPosition = - action === FieldAction.InsertLeft ? OrderObjectPositionTypePB.Before : OrderObjectPositionTypePB.After; - - const field = await fieldService.createField({ - viewId, - fieldPosition, - targetFieldId: fieldId, - }); - - onMenuItemClick?.(action, field.id); - return; - } - - case FieldAction.Hide: - await fieldService.updateFieldSetting(viewId, fieldId, { - visibility: FieldVisibility.AlwaysHidden, - }); - break; - case FieldAction.Show: - await fieldService.updateFieldSetting(viewId, fieldId, { - visibility: FieldVisibility.AlwaysShown, - }); - break; - case FieldAction.Duplicate: - await fieldService.duplicateField(viewId, fieldId); - break; - case FieldAction.Delete: - handleOpenConfirm(); - return; - } - - onMenuItemClick?.(action); - }; - - const renderActionContent = useCallback((item: { text: string; Icon: React.FC> }) => { - const { Icon, text } = item; - - return ( -
- -
{text}
-
- ); - }, []); - - const options: KeyboardNavigationOption[] = useMemo( - () => - [ - { - key: FieldAction.EditProperty, - content: renderActionContent({ - text: menuTextMap[FieldAction.EditProperty], - Icon: FieldActionSvgMap[FieldAction.EditProperty], - }), - disabled: isPrimary && primaryPreventDefaultActions.includes(FieldAction.EditProperty), - }, - { - key: FieldAction.InsertLeft, - content: renderActionContent({ - text: menuTextMap[FieldAction.InsertLeft], - Icon: FieldActionSvgMap[FieldAction.InsertLeft], - }), - disabled: isPrimary && primaryPreventDefaultActions.includes(FieldAction.InsertLeft), - }, - { - key: FieldAction.InsertRight, - content: renderActionContent({ - text: menuTextMap[FieldAction.InsertRight], - Icon: FieldActionSvgMap[FieldAction.InsertRight], - }), - disabled: isPrimary && primaryPreventDefaultActions.includes(FieldAction.InsertRight), - }, - { - key: FieldAction.Hide, - content: renderActionContent({ - text: menuTextMap[FieldAction.Hide], - Icon: FieldActionSvgMap[FieldAction.Hide], - }), - disabled: isPrimary && primaryPreventDefaultActions.includes(FieldAction.Hide), - }, - { - key: FieldAction.Show, - content: renderActionContent({ - text: menuTextMap[FieldAction.Show], - Icon: FieldActionSvgMap[FieldAction.Show], - }), - }, - { - key: FieldAction.Duplicate, - content: renderActionContent({ - text: menuTextMap[FieldAction.Duplicate], - Icon: FieldActionSvgMap[FieldAction.Duplicate], - }), - disabled: isPrimary && primaryPreventDefaultActions.includes(FieldAction.Duplicate), - }, - { - key: FieldAction.Delete, - content: renderActionContent({ - text: menuTextMap[FieldAction.Delete], - Icon: FieldActionSvgMap[FieldAction.Delete], - }), - disabled: isPrimary && primaryPreventDefaultActions.includes(FieldAction.Delete), - }, - ].filter((option) => actions.includes(option.key)), - [renderActionContent, menuTextMap, isPrimary, actions] - ); - - const handleKeyDown = useCallback( - (e: KeyboardEvent) => { - const isTab = e.key === 'Tab'; - - if (!focusMenu && (e.key === 'ArrowDown' || e.key === 'ArrowUp')) { - e.stopPropagation(); - notify.clear(); - notify.info(`Press Tab to focus on the menu`); - return; - } - - if (isTab) { - e.preventDefault(); - e.stopPropagation(); - if (focusMenu) { - inputRef?.current?.focus(); - setFocusMenu(false); - } else { - inputRef?.current?.blur(); - setFocusMenu(true); - } - - return; - } - }, - [focusMenu, inputRef] - ); - - return ( - <> - { - setFocusMenu(true); - }} - onBlur={() => { - setFocusMenu(false); - }} - onKeyDown={handleKeyDown} - onConfirm={handleMenuItemClick} - /> - { - await fieldService.deleteField(viewId, fieldId); - }} - onClose={() => { - setOpenConfirm(false); - onClose?.(); - }} - /> - - ); -} - -export default PropertyActions; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/PropertyMenu.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/PropertyMenu.tsx deleted file mode 100644 index 55b314b821..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/PropertyMenu.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { Divider } from '@mui/material'; -import { FC, useCallback, useMemo, useRef } from 'react'; -import { useViewId } from '$app/hooks'; -import { Field, fieldService } from '$app/application/database'; -import PropertyTypeMenuExtension from '$app/components/database/components/property/property_type/PropertyTypeMenuExtension'; -import PropertyTypeSelect from '$app/components/database/components/property/property_type/PropertyTypeSelect'; -import { FieldType, FieldVisibility } from '@/services/backend'; -import { Log } from '$app/utils/log'; -import Popover, { PopoverProps } from '@mui/material/Popover'; -import PropertyNameInput from '$app/components/database/components/property/PropertyNameInput'; -import PropertyActions, { FieldAction } from '$app/components/database/components/property/PropertyActions'; - -export interface GridFieldMenuProps extends PopoverProps { - field: Field; -} - -export const PropertyMenu: FC = ({ field, ...props }) => { - const viewId = useViewId(); - const inputRef = useRef(null); - - const isPrimary = field.isPrimary; - const actions = useMemo(() => { - const keys = [FieldAction.Duplicate, FieldAction.Delete]; - - if (field.visibility === FieldVisibility.AlwaysHidden) { - keys.unshift(FieldAction.Show); - } else { - keys.unshift(FieldAction.Hide); - } - - return keys; - }, [field.visibility]); - - const onUpdateFieldType = useCallback( - async (type: FieldType) => { - try { - await fieldService.updateFieldType(viewId, field.id, type); - } catch (e) { - // TODO - Log.error(`change field ${field.id} type from '${field.type}' to ${type} fail`, e); - } - }, - [viewId, field] - ); - - return ( - e.stopPropagation()} - onKeyDown={(e) => { - if (e.key === 'Escape') { - e.stopPropagation(); - e.preventDefault(); - props.onClose?.({}, 'escapeKeyDown'); - } - }} - onMouseDown={(e) => { - const isInput = inputRef.current?.contains(e.target as Node); - - if (isInput) return; - - e.stopPropagation(); - e.preventDefault(); - }} - {...props} - > - -
- {!isPrimary && ( -
- - -
- )} - - props.onClose?.({}, 'backdropClick')} - isPrimary={isPrimary} - actions={actions} - onMenuItemClick={() => { - props.onClose?.({}, 'backdropClick'); - }} - fieldId={field.id} - /> -
-
- ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/PropertyNameInput.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/PropertyNameInput.tsx deleted file mode 100644 index 4e20531335..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/PropertyNameInput.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React, { ChangeEventHandler, useCallback, useMemo, useState } from 'react'; -import { useViewId } from '$app/hooks'; -import { fieldService } from '$app/application/database'; -import { Log } from '$app/utils/log'; -import TextField from '@mui/material/TextField'; -import debounce from 'lodash-es/debounce'; - -const PropertyNameInput = React.forwardRef(({ id, name }, ref) => { - const viewId = useViewId(); - const [inputtingName, setInputtingName] = useState(name); - - const handleSubmit = useCallback( - async (newName: string) => { - if (newName !== name) { - try { - await fieldService.updateField(viewId, id, { - name: newName, - }); - } catch (e) { - // TODO - Log.error(`change field ${id} name from '${name}' to ${newName} fail`, e); - } - } - }, - [viewId, id, name] - ); - - const debouncedHandleSubmit = useMemo(() => debounce(handleSubmit, 500), [handleSubmit]); - const handleInput = useCallback>( - (e) => { - setInputtingName(e.target.value); - void debouncedHandleSubmit(e.target.value); - }, - [debouncedHandleSubmit] - ); - - return ( - - ); -}); - -export default PropertyNameInput; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/PropertySelect.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/PropertySelect.tsx deleted file mode 100644 index 0741bbc05b..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/PropertySelect.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { FC, useCallback, useMemo, useRef, useState } from 'react'; -import { Field as FieldType } from '$app/application/database'; -import { useDatabase } from '../../Database.hooks'; -import { Property } from './Property'; -import KeyboardNavigation, { - KeyboardNavigationOption, -} from '$app/components/_shared/keyboard_navigation/KeyboardNavigation'; -import { ReactComponent as DropDownSvg } from '$app/assets/more.svg'; -import Popover from '@mui/material/Popover'; - -export interface FieldSelectProps { - onChange?: (field: FieldType | undefined) => void; - value?: string; -} - -export const PropertySelect: FC = ({ value, onChange }) => { - const { fields } = useDatabase(); - - const scrollRef = useRef(null); - const ref = useRef(null); - const [open, setOpen] = useState(false); - const handleClose = () => { - setOpen(false); - }; - - const options: KeyboardNavigationOption[] = useMemo( - () => - fields.map((field) => { - return { - key: field.id, - content: , - }; - }), - [fields] - ); - - const onConfirm = useCallback( - (optionKey: string) => { - onChange?.(fields.find((field) => field.id === optionKey)); - }, - [onChange, fields] - ); - - const selectedField = useMemo(() => fields.find((field) => field.id === value), [fields, value]); - - return ( - <> -
{ - setOpen(true); - }} - > -
{selectedField ? : null}
- -
- {open && ( - -
- -
-
- )} - - ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/index.ts deleted file mode 100644 index 0b338836d6..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './Property'; -export * from './PropertySelect'; -export * from './property_type/PropertyTypeText'; -export * from './property_type/ProppertyTypeSvg'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/property_type/PropertyTypeMenu.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/property_type/PropertyTypeMenu.tsx deleted file mode 100644 index e3021249ee..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/property_type/PropertyTypeMenu.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import { Menu, MenuProps } from '@mui/material'; -import { FC, useCallback, useMemo } from 'react'; -import { FieldType } from '@/services/backend'; -import { PropertyTypeText, ProppertyTypeSvg } from '$app/components/database/components/property'; -import { Field } from '$app/application/database'; -import { ReactComponent as SelectCheckSvg } from '$app/assets/select-check.svg'; -import KeyboardNavigation, { - KeyboardNavigationOption, -} from '$app/components/_shared/keyboard_navigation/KeyboardNavigation'; -import Typography from '@mui/material/Typography'; - -export const PropertyTypeMenu: FC< - MenuProps & { - field: Field; - onClickItem?: (type: FieldType) => void; - } -> = ({ field, onClickItem, ...props }) => { - const PopoverClasses = useMemo( - () => ({ - ...props.PopoverClasses, - paper: ['w-56', props.PopoverClasses?.paper].join(' '), - }), - [props.PopoverClasses] - ); - - const renderGroupContent = useCallback((title: string) => { - return ( - - {title} - - ); - }, []); - - const renderContent = useCallback( - (type: FieldType) => { - return ( - <> - - - - - {type === field.type && } - - ); - }, - [field.type] - ); - - const options: KeyboardNavigationOption[] = useMemo(() => { - return [ - { - key: 100, - content: renderGroupContent('Basic'), - children: [ - { - key: FieldType.RichText, - content: renderContent(FieldType.RichText), - }, - { - key: FieldType.Number, - content: renderContent(FieldType.Number), - }, - { - key: FieldType.SingleSelect, - content: renderContent(FieldType.SingleSelect), - }, - { - key: FieldType.MultiSelect, - content: renderContent(FieldType.MultiSelect), - }, - { - key: FieldType.DateTime, - content: renderContent(FieldType.DateTime), - }, - { - key: FieldType.Checkbox, - content: renderContent(FieldType.Checkbox), - }, - { - key: FieldType.Checklist, - content: renderContent(FieldType.Checklist), - }, - { - key: FieldType.URL, - content: renderContent(FieldType.URL), - }, - ], - }, - { - key: 101, - content:
, - children: [], - }, - { - key: 102, - content: renderGroupContent('Advanced'), - children: [ - { - key: FieldType.LastEditedTime, - content: renderContent(FieldType.LastEditedTime), - }, - { - key: FieldType.CreatedTime, - content: renderContent(FieldType.CreatedTime), - }, - ], - }, - ]; - }, [renderContent, renderGroupContent]); - - return ( - - props?.onClose?.({}, 'escapeKeyDown')} - options={options} - disableFocus={true} - onConfirm={onClickItem} - /> - - ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/property_type/PropertyTypeMenuExtension.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/property_type/PropertyTypeMenuExtension.tsx deleted file mode 100644 index b45b670757..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/property_type/PropertyTypeMenuExtension.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React, { useMemo } from 'react'; -import { FieldType } from '@/services/backend'; -import { DateTimeField, Field, NumberField, SelectField } from '$app/application/database'; -import SelectFieldActions from '$app/components/database/components/field_types/select/select_field_actions/SelectFieldActions'; -import NumberFieldActions from '$app/components/database/components/field_types/number/NumberFieldActions'; -import DateTimeFieldActions from '$app/components/database/components/field_types/date/DateTimeFieldActions'; - -function PropertyTypeMenuExtension({ field }: { field: Field }) { - return useMemo(() => { - switch (field.type) { - case FieldType.SingleSelect: - case FieldType.MultiSelect: - return ; - case FieldType.Number: - return ; - case FieldType.DateTime: - case FieldType.CreatedTime: - case FieldType.LastEditedTime: - return ; - default: - return null; - } - }, [field]); -} - -export default PropertyTypeMenuExtension; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/property_type/PropertyTypeSelect.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/property_type/PropertyTypeSelect.tsx deleted file mode 100644 index 28d62b82c6..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/property_type/PropertyTypeSelect.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import React, { useRef, useState } from 'react'; -import { ProppertyTypeSvg } from '$app/components/database/components/property/property_type/ProppertyTypeSvg'; -import { MenuItem } from '@mui/material'; -import { Field } from '$app/application/database'; -import { ReactComponent as MoreSvg } from '$app/assets/more.svg'; -import { PropertyTypeMenu } from '$app/components/database/components/property/property_type/PropertyTypeMenu'; -import { FieldType } from '@/services/backend'; -import { PropertyTypeText } from '$app/components/database/components/property/property_type/PropertyTypeText'; - -interface Props { - field: Field; - onUpdateFieldType: (type: FieldType) => void; -} -function PropertyTypeSelect({ field, onUpdateFieldType }: Props) { - const [expanded, setExpanded] = useState(false); - const ref = useRef(null); - - return ( -
- { - setExpanded(!expanded); - }} - className={'mx-0 rounded-none px-0'} - > -
- - - - - -
-
- {expanded && ( - { - setExpanded(false); - }} - /> - )} -
- ); -} - -export default PropertyTypeSelect; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/property_type/PropertyTypeText.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/property_type/PropertyTypeText.tsx deleted file mode 100644 index daae232fde..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/property_type/PropertyTypeText.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { FieldType } from '@/services/backend'; -import { useTranslation } from 'react-i18next'; -import { useMemo } from 'react'; - -export const PropertyTypeText = ({ type }: { type: FieldType }) => { - const { t } = useTranslation(); - - const text = useMemo(() => { - const map = { - [FieldType.RichText]: t('grid.field.textFieldName'), - [FieldType.Number]: t('grid.field.numberFieldName'), - [FieldType.DateTime]: t('grid.field.dateFieldName'), - [FieldType.SingleSelect]: t('grid.field.singleSelectFieldName'), - [FieldType.MultiSelect]: t('grid.field.multiSelectFieldName'), - [FieldType.Checkbox]: t('grid.field.checkboxFieldName'), - [FieldType.URL]: t('grid.field.urlFieldName'), - [FieldType.Checklist]: t('grid.field.checklistFieldName'), - [FieldType.LastEditedTime]: t('grid.field.updatedAtFieldName'), - [FieldType.CreatedTime]: t('grid.field.createdAtFieldName'), - [FieldType.Relation]: t('grid.field.relationFieldName'), - }; - - return map[type] || 'unknown'; - }, [t, type]); - - return
{text}
; -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/property_type/ProppertyTypeSvg.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/property_type/ProppertyTypeSvg.tsx deleted file mode 100644 index 7ee4e6f83d..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/property/property_type/ProppertyTypeSvg.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { FC, memo } from 'react'; -import { FieldType } from '@/services/backend'; -import { ReactComponent as TextSvg } from '$app/assets/database/field-type-text.svg'; -import { ReactComponent as NumberSvg } from '$app/assets/database/field-type-number.svg'; -import { ReactComponent as DateSvg } from '$app/assets/database/field-type-date.svg'; -import { ReactComponent as SingleSelectSvg } from '$app/assets/database/field-type-single-select.svg'; -import { ReactComponent as MultiSelectSvg } from '$app/assets/database/field-type-multi-select.svg'; -import { ReactComponent as ChecklistSvg } from '$app/assets/database/field-type-checklist.svg'; -import { ReactComponent as CheckboxSvg } from '$app/assets/database/field-type-checkbox.svg'; -import { ReactComponent as URLSvg } from '$app/assets/database/field-type-url.svg'; -import { ReactComponent as LastEditedTimeSvg } from '$app/assets/database/field-type-last-edited-time.svg'; -import { ReactComponent as RelationSvg } from '$app/assets/database/field-type-relation.svg'; - -export const FieldTypeSvgMap: Record>> = { - [FieldType.RichText]: TextSvg, - [FieldType.Number]: NumberSvg, - [FieldType.DateTime]: DateSvg, - [FieldType.SingleSelect]: SingleSelectSvg, - [FieldType.MultiSelect]: MultiSelectSvg, - [FieldType.Checkbox]: CheckboxSvg, - [FieldType.URL]: URLSvg, - [FieldType.Checklist]: ChecklistSvg, - [FieldType.LastEditedTime]: LastEditedTimeSvg, - [FieldType.CreatedTime]: LastEditedTimeSvg, - [FieldType.Relation]: RelationSvg, -}; - -export const ProppertyTypeSvg: FC<{ type: FieldType; className?: string }> = memo(({ type, ...props }) => { - const Svg = FieldTypeSvgMap[type]; - - return ; -}); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/sort/SortConditionSelect.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/sort/SortConditionSelect.tsx deleted file mode 100644 index fdb508cb8f..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/sort/SortConditionSelect.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { FC, useMemo, useRef, useState } from 'react'; -import { SortConditionPB } from '@/services/backend'; -import KeyboardNavigation, { - KeyboardNavigationOption, -} from '$app/components/_shared/keyboard_navigation/KeyboardNavigation'; -import { Popover } from '@mui/material'; -import { ReactComponent as DropDownSvg } from '$app/assets/more.svg'; -import { useTranslation } from 'react-i18next'; - -export const SortConditionSelect: FC<{ - onChange?: (value: SortConditionPB) => void; - value?: SortConditionPB; -}> = ({ onChange, value }) => { - const { t } = useTranslation(); - const ref = useRef(null); - const [open, setOpen] = useState(false); - const handleClose = () => { - setOpen(false); - }; - - const options: KeyboardNavigationOption[] = useMemo(() => { - return [ - { - key: SortConditionPB.Ascending, - content: t('grid.sort.ascending'), - }, - { - key: SortConditionPB.Descending, - content: t('grid.sort.descending'), - }, - ]; - }, [t]); - - const onConfirm = (optionKey: SortConditionPB) => { - onChange?.(optionKey); - handleClose(); - }; - - const selectedField = useMemo(() => options.find((option) => option.key === value), [options, value]); - - return ( - <> -
{ - setOpen(true); - }} - > -
{selectedField?.content}
- -
- {open && ( - -
- -
-
- )} - - ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/sort/SortFieldsMenu.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/sort/SortFieldsMenu.tsx deleted file mode 100644 index 724c28467a..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/sort/SortFieldsMenu.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React, { FC, useCallback } from 'react'; -import { MenuProps } from '@mui/material'; -import PropertiesList from '$app/components/database/components/property/PropertiesList'; -import { Field, sortService } from '$app/application/database'; -import { SortConditionPB } from '@/services/backend'; -import { useTranslation } from 'react-i18next'; -import { useViewId } from '$app/hooks'; -import Popover from '@mui/material/Popover'; - -const SortFieldsMenu: FC< - MenuProps & { - onInserted?: () => void; - } -> = ({ onInserted, ...props }) => { - const { t } = useTranslation(); - const viewId = useViewId(); - const addSort = useCallback( - async (field: Field) => { - await sortService.insertSort(viewId, { - fieldId: field.id, - condition: SortConditionPB.Ascending, - }); - props.onClose?.({}, 'backdropClick'); - onInserted?.(); - }, - [props, viewId, onInserted] - ); - - return ( - { - if (e.key === 'Escape') { - e.preventDefault(); - e.stopPropagation(); - props.onClose?.({}, 'escapeKeyDown'); - } - }} - keepMounted={false} - {...props} - > - { - props.onClose?.({}, 'escapeKeyDown'); - }} - showSearch={true} - onItemClick={addSort} - searchPlaceholder={t('grid.settings.sortBy')} - /> - - ); -}; - -export default SortFieldsMenu; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/sort/SortItem.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/sort/SortItem.tsx deleted file mode 100644 index fe1074bbde..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/sort/SortItem.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { IconButton, Stack } from '@mui/material'; -import { FC, useCallback } from 'react'; -import { ReactComponent as CloseSvg } from '$app/assets/close.svg'; -import { Field, Sort, sortService } from '$app/application/database'; -import { PropertySelect } from '../property'; -import { SortConditionSelect } from './SortConditionSelect'; -import { useViewId } from '@/appflowy_app/hooks'; -import { SortConditionPB } from '@/services/backend'; - -export interface SortItemProps { - className?: string; - sort: Sort; -} - -export const SortItem: FC = ({ className, sort }) => { - const viewId = useViewId(); - - const handleFieldChange = useCallback( - (field: Field | undefined) => { - if (field) { - void sortService.updateSort(viewId, { - ...sort, - fieldId: field.id, - }); - } - }, - [viewId, sort] - ); - - const handleConditionChange = useCallback( - (value: SortConditionPB) => { - void sortService.updateSort(viewId, { - ...sort, - condition: value, - }); - }, - [viewId, sort] - ); - - const handleClick = useCallback(() => { - void sortService.deleteSort(viewId, sort); - }, [viewId, sort]); - - return ( - - - -
- - - -
-
- ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/sort/SortMenu.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/sort/SortMenu.tsx deleted file mode 100644 index 88df70b2e4..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/sort/SortMenu.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { Menu, MenuProps } from '@mui/material'; -import { FC, MouseEventHandler, useCallback, useState } from 'react'; -import { useViewId } from '$app/hooks'; -import { sortService } from '$app/application/database'; -import { useDatabaseSorts } from '../../Database.hooks'; -import { SortItem } from './SortItem'; - -import { useTranslation } from 'react-i18next'; -import { ReactComponent as AddSvg } from '$app/assets/add.svg'; -import { ReactComponent as DeleteSvg } from '$app/assets/delete.svg'; -import SortFieldsMenu from '$app/components/database/components/sort/SortFieldsMenu'; -import Button from '@mui/material/Button'; - -export const SortMenu: FC = (props) => { - const { onClose } = props; - const { t } = useTranslation(); - const viewId = useViewId(); - const sorts = useDatabaseSorts(); - const [anchorEl, setAnchorEl] = useState(null); - const openFieldListMenu = Boolean(anchorEl); - const handleClick = useCallback>((event) => { - setAnchorEl(event.currentTarget); - }, []); - - const deleteAllSorts = useCallback(() => { - void sortService.deleteAllSorts(viewId); - onClose?.({}, 'backdropClick'); - }, [viewId, onClose]); - - return ( - <> - { - if (e.key === 'Escape') { - e.preventDefault(); - e.stopPropagation(); - props.onClose?.({}, 'escapeKeyDown'); - } - }} - keepMounted={false} - MenuListProps={{ - className: 'py-1 w-[360px]', - }} - {...props} - onClose={onClose} - > -
-
- {sorts.map((sort) => ( - - ))} -
- -
- - -
-
-
- - { - setAnchorEl(null); - }} - anchorOrigin={{ - vertical: 'bottom', - horizontal: 'left', - }} - /> - - ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/sort/Sorts.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/sort/Sorts.tsx deleted file mode 100644 index 7a4fa57a6f..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/sort/Sorts.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { Chip, Divider } from '@mui/material'; -import React, { MouseEventHandler, useCallback, useEffect, useState } from 'react'; -import { SortMenu } from './SortMenu'; -import { useTranslation } from 'react-i18next'; -import { ReactComponent as SortSvg } from '$app/assets/sort.svg'; -import { ReactComponent as DropDownSvg } from '$app/assets/dropdown.svg'; -import { useDatabase } from '$app/components/database'; - -export const Sorts = () => { - const { t } = useTranslation(); - const { sorts } = useDatabase(); - - const showSorts = sorts && sorts.length > 0; - const [anchorEl, setAnchorEl] = useState(null); - - const handleClick = useCallback>((event) => { - setAnchorEl(event.currentTarget); - }, []); - - const label = ( -
- - {t('grid.settings.sort')} - -
- ); - - const menuOpen = Boolean(anchorEl); - - useEffect(() => { - if (!showSorts) { - setAnchorEl(null); - } - }, [showSorts]); - - if (!showSorts) return null; - - return ( -
- - - setAnchorEl(null)} /> -
- ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/sort/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/sort/index.ts deleted file mode 100644 index e64dba3a6e..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/sort/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './SortMenu'; -export * from './Sorts'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/tab_bar/AddViewBtn.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/tab_bar/AddViewBtn.tsx deleted file mode 100644 index 717bf1eb18..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/tab_bar/AddViewBtn.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import { IconButton } from '@mui/material'; -import { ReactComponent as AddSvg } from '$app/assets/add.svg'; -import { useTranslation } from 'react-i18next'; -import { ViewLayoutPB } from '@/services/backend'; -import { createDatabaseView } from '$app/application/database/database_view/database_view_service'; - -function AddViewBtn({ pageId, onCreated }: { pageId: string; onCreated: (id: string) => void }) { - const { t } = useTranslation(); - const onClick = async () => { - try { - const view = await createDatabaseView(pageId, ViewLayoutPB.Grid, t('editor.table')); - - onCreated(view.id); - } catch (e) { - console.error(e); - } - }; - - return ( -
- - - -
- ); -} - -export default AddViewBtn; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/tab_bar/DatabaseTabBar.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/tab_bar/DatabaseTabBar.tsx deleted file mode 100644 index f7375e0c70..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/tab_bar/DatabaseTabBar.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { forwardRef, FunctionComponent, SVGProps, useEffect, useMemo, useState } from 'react'; -import { ViewTabs, ViewTab } from './ViewTabs'; -import { useTranslation } from 'react-i18next'; -import AddViewBtn from '$app/components/database/components/tab_bar/AddViewBtn'; -import { ViewLayoutPB } from '@/services/backend'; -import { ReactComponent as GridSvg } from '$app/assets/grid.svg'; -import { ReactComponent as BoardSvg } from '$app/assets/board.svg'; -import { ReactComponent as DocumentSvg } from '$app/assets/document.svg'; -import ViewActions from '$app/components/database/components/tab_bar/ViewActions'; -import { Page } from '$app_reducers/pages/slice'; - -export interface DatabaseTabBarProps { - childViews: Page[]; - selectedViewId?: string; - setSelectedViewId?: (viewId: string) => void; - pageId: string; -} - -const DatabaseIcons: { - [key in ViewLayoutPB]: FunctionComponent & { title?: string | undefined }>; -} = { - [ViewLayoutPB.Document]: DocumentSvg, - [ViewLayoutPB.Grid]: GridSvg, - [ViewLayoutPB.Board]: BoardSvg, - [ViewLayoutPB.Calendar]: GridSvg, -}; - -export const DatabaseTabBar = forwardRef( - ({ pageId, childViews, selectedViewId, setSelectedViewId }, ref) => { - const { t } = useTranslation(); - const [contextMenuAnchorEl, setContextMenuAnchorEl] = useState(null); - const [contextMenuView, setContextMenuView] = useState(null); - const open = Boolean(contextMenuAnchorEl); - - const handleChange = (_: React.SyntheticEvent, newValue: string) => { - setSelectedViewId?.(newValue); - }; - - useEffect(() => { - if (selectedViewId === undefined && childViews.length > 0) { - setSelectedViewId?.(childViews[0].id); - } - }, [selectedViewId, setSelectedViewId, childViews]); - - const openMenu = (view: Page) => { - return (e: React.MouseEvent) => { - e.preventDefault(); - e.stopPropagation(); - setContextMenuView(view); - setContextMenuAnchorEl(e.currentTarget); - }; - }; - - const isSelected = useMemo( - () => childViews.some((view) => view.id === selectedViewId), - [childViews, selectedViewId] - ); - - if (childViews.length === 0) return null; - return ( -
-
- - {childViews.map((view) => { - const Icon = DatabaseIcons[view.layout]; - - return ( - } - iconPosition='start' - color='inherit' - label={view.name || t('grid.title.placeholder')} - value={view.id} - /> - ); - })} - - setSelectedViewId?.(id)} /> -
- {open && contextMenuView && ( - { - setContextMenuAnchorEl(null); - setContextMenuView(null); - }} - /> - )} -
- ); - } -); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/tab_bar/TextButton.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/tab_bar/TextButton.tsx deleted file mode 100644 index 60dfaa2e53..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/tab_bar/TextButton.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { Button, ButtonProps, styled } from '@mui/material'; - -export const TextButton = styled(Button)(() => ({ - padding: '2px 6px', - fontSize: '0.75rem', - lineHeight: '1rem', - fontWeight: 400, - minWidth: 'unset', -})); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/tab_bar/ViewActions.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/tab_bar/ViewActions.tsx deleted file mode 100644 index be545e51e3..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/tab_bar/ViewActions.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import React, { useCallback, useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import { ReactComponent as DeleteSvg } from '$app/assets/delete.svg'; -import { ReactComponent as EditSvg } from '$app/assets/edit.svg'; -import { deleteView } from '$app/application/database/database_view/database_view_service'; -import { MenuProps, Menu } from '@mui/material'; -import RenameDialog from '$app/components/_shared/confirm_dialog/RenameDialog'; -import { Page } from '$app_reducers/pages/slice'; -import { useAppDispatch } from '$app/stores/store'; -import { updatePageName } from '$app_reducers/pages/async_actions'; -import KeyboardNavigation from '$app/components/_shared/keyboard_navigation/KeyboardNavigation'; - -enum ViewAction { - Rename, - Delete, -} - -function ViewActions({ view, pageId, ...props }: { pageId: string; view: Page } & MenuProps) { - const { t } = useTranslation(); - const viewId = view.id; - const dispatch = useAppDispatch(); - const [openRenameDialog, setOpenRenameDialog] = useState(false); - const renderContent = useCallback((title: string, Icon: React.FC>) => { - return ( -
- -
{title}
-
- ); - }, []); - - const onConfirm = useCallback( - async (key: ViewAction) => { - switch (key) { - case ViewAction.Rename: - setOpenRenameDialog(true); - break; - case ViewAction.Delete: - try { - await deleteView(viewId); - props.onClose?.({}, 'backdropClick'); - } catch (e) { - // toast.error(t('error.deleteView')); - } - - break; - default: - break; - } - }, - [viewId, props] - ); - const options = [ - { - key: ViewAction.Rename, - content: renderContent(t('button.rename'), EditSvg), - }, - - { - key: ViewAction.Delete, - content: renderContent(t('button.delete'), DeleteSvg), - disabled: viewId === pageId, - }, - ]; - - return ( - <> - - { - props.onClose?.({}, 'escapeKeyDown'); - }} - /> - - {openRenameDialog && ( - setOpenRenameDialog(false)} - onOk={async (val) => { - try { - await dispatch( - updatePageName({ - id: viewId, - name: val, - immediate: true, - }) - ); - setOpenRenameDialog(false); - props.onClose?.({}, 'backdropClick'); - } catch (e) { - // toast.error(t('error.renameView')); - } - }} - defaultValue={view.name} - /> - )} - - ); -} - -export default ViewActions; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/tab_bar/ViewTabs.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/tab_bar/ViewTabs.tsx deleted file mode 100644 index e2ff336c73..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/tab_bar/ViewTabs.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { styled, Tab, TabProps, Tabs, TabsProps } from '@mui/material'; -import { HTMLAttributes } from 'react'; - -export const ViewTabs = styled((props: TabsProps) => )({ - minHeight: '28px', - - '& .MuiTabs-scroller': { - paddingBottom: '2px', - }, -}); - -export const ViewTab = styled((props: TabProps) => )({ - padding: '6px 12px', - minHeight: '28px', - fontSize: '12px', - lineHeight: '16px', - minWidth: 'unset', - margin: '4px 0', - - '&.Mui-selected': { - color: 'inherit', - }, -}); - -interface TabPanelProps extends HTMLAttributes { - children?: React.ReactNode; - index: number; - value: number; -} - -export function TabPanel(props: TabPanelProps) { - const { children, value, index, ...other } = props; - - const isActivated = value === index; - - return ( - - ); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/tab_bar/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/components/tab_bar/index.ts deleted file mode 100644 index fc0c62963e..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/components/tab_bar/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './DatabaseTabBar'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/database.scss b/frontend/appflowy_tauri/src/appflowy_app/components/database/database.scss deleted file mode 100644 index 492ff2a713..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/database.scss +++ /dev/null @@ -1,19 +0,0 @@ -.database-collection { - ::-webkit-scrollbar { - width: 0px; - height: 0px; - } -} - -.checklist-item { - @apply my-1; - .delete-option-button { - display: none; - } - &:hover, &.selected { - background-color: var(--fill-list-hover); - .delete-option-button { - display: block; - } - } -} \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/Grid.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/Grid.tsx deleted file mode 100644 index beb90c66dc..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/Grid.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { FC } from 'react'; -import { GridTable, GridTableProps } from './grid_table'; - -export const Grid: FC = (props) => { - return ; -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/constants.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/constants.ts deleted file mode 100644 index eadfadaa89..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/constants.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { Field, RowMeta } from '$app/application/database'; - -export const GridCalculateCountHeight = 40; - -export const GRID_ACTIONS_WIDTH = 64; - -export const DEFAULT_FIELD_WIDTH = 150; - -export enum RenderRowType { - Fields = 'fields', - Row = 'row', - NewRow = 'new-row', - CalculateRow = 'calculate-row', -} - -export interface CalculateRenderRow { - type: RenderRowType.CalculateRow; -} - -export interface FieldRenderRow { - type: RenderRowType.Fields; -} - -export interface CellRenderRow { - type: RenderRowType.Row; - data: { - meta: RowMeta; - }; -} - -export interface NewRenderRow { - type: RenderRowType.NewRow; - data: { - groupId?: string; - }; -} - -export type RenderRow = FieldRenderRow | CellRenderRow | NewRenderRow | CalculateRenderRow; - -export const fieldsToColumns = (fields: Field[]): GridColumn[] => { - return [ - { - type: GridColumnType.Action, - width: GRID_ACTIONS_WIDTH, - }, - ...fields.map((field) => ({ - field, - width: field.width || DEFAULT_FIELD_WIDTH, - type: GridColumnType.Field, - })), - { - type: GridColumnType.NewProperty, - width: DEFAULT_FIELD_WIDTH, - }, - ]; -}; - -export const rowMetasToRenderRow = (rowMetas: RowMeta[]): RenderRow[] => { - return [ - ...rowMetas.map((rowMeta) => ({ - type: RenderRowType.Row, - data: { - meta: rowMeta, - }, - })), - { - type: RenderRowType.NewRow, - data: {}, - }, - { - type: RenderRowType.CalculateRow, - }, - ]; -}; - -export enum GridColumnType { - Action, - Field, - NewProperty, -} - -export interface GridColumn { - field?: Field; - width: number; - type: GridColumnType; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_calculate/GridCalculate.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_calculate/GridCalculate.tsx deleted file mode 100644 index beed71fca4..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_calculate/GridCalculate.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react'; -import { useDatabaseVisibilityRows } from '$app/components/database'; -import { Field } from '$app/application/database'; -import { DEFAULT_FIELD_WIDTH, GRID_ACTIONS_WIDTH } from '$app/components/database/grid/constants'; -import { useTranslation } from 'react-i18next'; - -interface Props { - field: Field; - index: number; - getContainerRef?: () => React.RefObject; -} - -export function GridCalculate({ field, index }: Props) { - const rowMetas = useDatabaseVisibilityRows(); - const count = rowMetas.length; - const width = index === 0 ? GRID_ACTIONS_WIDTH : field.width ?? DEFAULT_FIELD_WIDTH; - const { t } = useTranslation(); - - return ( -
- {field.isPrimary ? ( - <> - {t('grid.calculationTypeLabel.count')} - {count} - - ) : null} -
- ); -} - -export default GridCalculate; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_calculate/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_calculate/index.ts deleted file mode 100644 index 2bd3b71b1e..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_calculate/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './GridCalculate'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_cell/GridCell.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_cell/GridCell.tsx deleted file mode 100644 index 042ba1777d..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_cell/GridCell.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import React, { CSSProperties, memo } from 'react'; -import { GridColumn, RenderRow, RenderRowType } from '../constants'; -import GridNewRow from '$app/components/database/grid/grid_new_row/GridNewRow'; -import { GridCalculate } from '$app/components/database/grid/grid_calculate'; -import { areEqual } from 'react-window'; -import { Cell } from '$app/components/database/components'; -import { PrimaryCell } from '$app/components/database/grid/grid_cell'; - -const getRenderRowKey = (row: RenderRow) => { - if (row.type === RenderRowType.Row) { - return `row:${row.data.meta.id}`; - } - - return row.type; -}; - -interface GridCellProps { - row: RenderRow; - column: GridColumn; - columnIndex: number; - style: CSSProperties; - onEditRecord?: (rowId: string) => void; - getContainerRef?: () => React.RefObject; -} - -export const GridCell = memo(({ row, column, columnIndex, style, onEditRecord, getContainerRef }: GridCellProps) => { - const key = getRenderRowKey(row); - - const field = column.field; - - if (!field) return
; - - switch (row.type) { - case RenderRowType.Row: { - const { id: rowId, icon: rowIcon } = row.data.meta; - const renderRowCell = ; - - return ( -
- {field.isPrimary ? ( - - {renderRowCell} - - ) : ( - renderRowCell - )} -
- ); - } - - case RenderRowType.NewRow: - return ( -
- -
- ); - case RenderRowType.CalculateRow: - return ( -
- -
- ); - default: - return null; - } -}, areEqual); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_cell/PrimaryCell.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_cell/PrimaryCell.tsx deleted file mode 100644 index b9a734de7b..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_cell/PrimaryCell.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React, { Suspense, useMemo, useRef } from 'react'; -import { ReactComponent as OpenIcon } from '$app/assets/open.svg'; -import { IconButton } from '@mui/material'; - -import { useGridTableHoverState } from '$app/components/database/grid/grid_row_actions'; - -export function PrimaryCell({ - onEditRecord, - icon, - getContainerRef, - rowId, - children, -}: { - rowId: string; - icon?: string; - onEditRecord?: (rowId: string) => void; - getContainerRef?: () => React.RefObject; - children?: React.ReactNode; -}) { - const cellRef = useRef(null); - - const containerRef = getContainerRef?.(); - const { hoverRowId } = useGridTableHoverState(containerRef); - - const showExpandIcon = useMemo(() => { - return hoverRowId === rowId; - }, [hoverRowId, rowId]); - - return ( -
- {icon &&
{icon}
} - {children} - - {showExpandIcon && ( -
- onEditRecord?.(rowId)} className={'h-6 w-6 text-sm'}> - - -
- )} -
-
- ); -} - -export default PrimaryCell; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_cell/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_cell/index.ts deleted file mode 100644 index 949d5054bf..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_cell/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './GridCell'; -export * from './PrimaryCell'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_field/GridField.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_field/GridField.tsx deleted file mode 100644 index 3c3921abf7..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_field/GridField.tsx +++ /dev/null @@ -1,179 +0,0 @@ -import { Button } from '@mui/material'; -import { DragEventHandler, FC, HTMLAttributes, memo, useCallback, useEffect, useMemo, useState } from 'react'; -import { useViewId } from '$app/hooks'; -import { DragItem, DropPosition, DragType, useDraggable, useDroppable, ScrollDirection } from '../../_shared'; -import { fieldService, Field } from '$app/application/database'; -import { Property } from '$app/components/database/components/property'; -import { GridResizer, GridFieldMenu } from '$app/components/database/grid/grid_field'; -import { areEqual } from 'react-window'; -import { useOpenMenu } from '$app/components/database/grid/grid_sticky_header/GridStickyHeader.hooks'; -import throttle from 'lodash-es/throttle'; - -export interface GridFieldProps extends HTMLAttributes { - field: Field; - onOpenMenu?: (id: string) => void; - onCloseMenu?: (id: string) => void; - resizeColumnWidth?: (width: number) => void; - getScrollElement?: () => HTMLElement | null; -} - -export const GridField: FC = memo( - ({ getScrollElement, resizeColumnWidth, onOpenMenu, onCloseMenu, field, ...props }) => { - const menuOpened = useOpenMenu(field.id); - const viewId = useViewId(); - const [propertyMenuOpened, setPropertyMenuOpened] = useState(false); - const [dropPosition, setDropPosition] = useState(DropPosition.Before); - - const draggingData = useMemo( - () => ({ - field, - }), - [field] - ); - - const { isDragging, attributes, listeners, setPreviewRef, previewRef } = useDraggable({ - type: DragType.Field, - data: draggingData, - scrollOnEdge: { - direction: ScrollDirection.Horizontal, - getScrollElement, - edgeGap: 80, - }, - }); - - const onDragOver = useMemo(() => { - return throttle((event) => { - const element = previewRef.current; - - if (!element) { - return; - } - - const { left, right } = element.getBoundingClientRect(); - const middle = (left + right) / 2; - - setDropPosition(event.clientX < middle ? DropPosition.Before : DropPosition.After); - }, 20); - }, [previewRef]); - - const onDrop = useCallback( - ({ data }: DragItem) => { - const dragField = data.field as Field; - - if (dragField.id === field.id) { - return; - } - - void fieldService.moveField(viewId, dragField.id, field.id); - }, - [viewId, field] - ); - - const { isOver, listeners: dropListeners } = useDroppable({ - accept: DragType.Field, - disabled: isDragging, - onDragOver, - onDrop, - }); - - const [menuAnchorPosition, setMenuAnchorPosition] = useState< - | { - top: number; - left: number; - } - | undefined - >(undefined); - - const open = Boolean(menuAnchorPosition) && menuOpened; - - const handleClick = useCallback(() => { - onOpenMenu?.(field.id); - }, [onOpenMenu, field.id]); - - const handleMenuClose = useCallback(() => { - onCloseMenu?.(field.id); - }, [onCloseMenu, field.id]); - - useEffect(() => { - if (!menuOpened) { - setMenuAnchorPosition(undefined); - return; - } - - const anchorElement = previewRef.current; - - if (!anchorElement) { - setMenuAnchorPosition(undefined); - return; - } - - anchorElement.scrollIntoView({ block: 'nearest' }); - - const rect = anchorElement.getBoundingClientRect(); - - setMenuAnchorPosition({ - top: rect.top + rect.height, - left: rect.left, - }); - }, [menuOpened, previewRef]); - - const handlePropertyMenuOpen = useCallback(() => { - setPropertyMenuOpened(true); - }, []); - - const handlePropertyMenuClose = useCallback(() => { - setPropertyMenuOpened(false); - }, []); - - return ( -
- - {open && ( - - )} -
- ); - }, - areEqual -); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_field/GridFieldMenu.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_field/GridFieldMenu.tsx deleted file mode 100644 index 1407fe30c2..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_field/GridFieldMenu.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import React, { useRef } from 'react'; -import Popover, { PopoverProps } from '@mui/material/Popover'; -import { Field } from '$app/application/database'; -import PropertyNameInput from '$app/components/database/components/property/PropertyNameInput'; -import { MenuList } from '@mui/material'; -import PropertyActions, { FieldAction } from '$app/components/database/components/property/PropertyActions'; - -interface Props extends PopoverProps { - field: Field; - onOpenPropertyMenu?: () => void; - onOpenMenu?: (fieldId: string) => void; -} - -export function GridFieldMenu({ field, onOpenPropertyMenu, onOpenMenu, onClose, ...props }: Props) { - const inputRef = useRef(null); - - return ( - e.stopPropagation()} - {...props} - onClose={onClose} - keepMounted={false} - onMouseDown={(e) => { - const isInput = inputRef.current?.contains(e.target as Node); - - if (isInput) return; - - e.stopPropagation(); - e.preventDefault(); - }} - > - - - onClose?.({}, 'backdropClick')} - onMenuItemClick={(action, newFieldId?: string) => { - if (action === FieldAction.EditProperty) { - onOpenPropertyMenu?.(); - } else if (newFieldId && (action === FieldAction.InsertLeft || action === FieldAction.InsertRight)) { - onOpenMenu?.(newFieldId); - } - - onClose?.({}, 'backdropClick'); - }} - fieldId={field.id} - /> - - - ); -} - -export default GridFieldMenu; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_field/GridNewField.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_field/GridNewField.tsx deleted file mode 100644 index d0b739298a..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_field/GridNewField.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React, { useCallback } from 'react'; -import { useViewId } from '$app/hooks'; -import { useTranslation } from 'react-i18next'; -import { fieldService } from '$app/application/database'; -import { FieldType } from '@/services/backend'; -import Button from '@mui/material/Button'; -import { ReactComponent as AddSvg } from '$app/assets/add.svg'; - -export function GridNewField({ onInserted }: { onInserted?: (id: string) => void }) { - const viewId = useViewId(); - const { t } = useTranslation(); - - const handleClick = useCallback(async () => { - try { - const field = await fieldService.createField({ - viewId, - fieldType: FieldType.RichText, - }); - - onInserted?.(field.id); - } catch (e) { - // toast.error(t('grid.field.newPropertyFail')); - } - }, [onInserted, viewId]); - - return ( - <> - - - ); -} - -export default GridNewField; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_field/GridResizer.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_field/GridResizer.tsx deleted file mode 100644 index 12aef74996..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_field/GridResizer.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import React, { useCallback, useRef, useState } from 'react'; -import { Field, fieldService } from '$app/application/database'; -import { useViewId } from '$app/hooks'; - -interface GridResizerProps { - field: Field; - onWidthChange?: (width: number) => void; -} - -const minWidth = 150; - -export function GridResizer({ field, onWidthChange }: GridResizerProps) { - const viewId = useViewId(); - const fieldId = field.id; - const width = field.width || 0; - const [isResizing, setIsResizing] = useState(false); - const [hover, setHover] = useState(false); - const startX = useRef(0); - const newWidthRef = useRef(width); - const onResize = useCallback( - (e: MouseEvent) => { - const diff = e.clientX - startX.current; - const newWidth = width + diff; - - if (newWidth < minWidth) { - return; - } - - newWidthRef.current = newWidth; - onWidthChange?.(newWidth); - }, - [width, onWidthChange] - ); - - const onResizeEnd = useCallback(() => { - setIsResizing(false); - - void fieldService.updateFieldSetting(viewId, fieldId, { - width: newWidthRef.current, - }); - document.removeEventListener('mousemove', onResize); - document.removeEventListener('mouseup', onResizeEnd); - }, [fieldId, onResize, viewId]); - - const onResizeStart = useCallback( - (e: React.MouseEvent) => { - e.stopPropagation(); - e.preventDefault(); - startX.current = e.clientX; - setIsResizing(true); - document.addEventListener('mousemove', onResize); - document.addEventListener('mouseup', onResizeEnd); - }, - [onResize, onResizeEnd] - ); - - return ( -
{ - e.stopPropagation(); - }} - onMouseEnter={() => { - setHover(true); - }} - onMouseLeave={() => { - setHover(false); - }} - style={{ - right: `-3px`, - }} - className={'absolute top-0 z-10 h-full cursor-col-resize'} - > -
-
- ); -} - -export default GridResizer; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_field/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_field/index.ts deleted file mode 100644 index 384ee2af3b..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_field/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './GridField'; -export * from './GridFieldMenu'; -export * from './GridNewField'; -export * from './GridResizer'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_new_row/GridNewRow.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_new_row/GridNewRow.tsx deleted file mode 100644 index 4dc70e21dc..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_new_row/GridNewRow.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import React, { useCallback } from 'react'; -import { rowService } from '$app/application/database'; -import { useViewId } from '$app/hooks'; -import { ReactComponent as AddSvg } from '$app/assets/add.svg'; -import { useTranslation } from 'react-i18next'; - -interface Props { - index: number; - groupId?: string; - getContainerRef?: () => React.RefObject; -} - -const CSS_HIGHLIGHT_PROPERTY = 'bg-content-blue-50'; - -function GridNewRow({ index, groupId, getContainerRef }: Props) { - const viewId = useViewId(); - - const { t } = useTranslation(); - const handleClick = useCallback(() => { - void rowService.createRow(viewId, { - groupId, - }); - }, [viewId, groupId]); - - const toggleCssProperty = useCallback( - (status: boolean) => { - const container = getContainerRef?.()?.current; - - if (!container) return; - - const newRowCells = container.querySelectorAll('.grid-new-row'); - - newRowCells.forEach((cell) => { - if (status) { - cell.classList.add(CSS_HIGHLIGHT_PROPERTY); - } else { - cell.classList.remove(CSS_HIGHLIGHT_PROPERTY); - } - }); - }, - [getContainerRef] - ); - - return ( -
{ - toggleCssProperty(true); - }} - onMouseLeave={() => { - toggleCssProperty(false); - }} - onClick={handleClick} - className={'grid-new-row flex grow cursor-pointer text-text-title'} - > - - - {t('grid.row.newRow')} - -
- ); -} - -export default GridNewRow; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_overlay/GridTableOverlay.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_overlay/GridTableOverlay.tsx deleted file mode 100644 index 07ece5dec2..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_overlay/GridTableOverlay.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import React, { useCallback, useEffect, useState } from 'react'; -import { - GridRowContextMenu, - GridRowActions, - useGridTableHoverState, -} from '$app/components/database/grid/grid_row_actions'; -import DeleteConfirmDialog from '$app/components/_shared/confirm_dialog/DeleteConfirmDialog'; -import { useTranslation } from 'react-i18next'; - -function GridTableOverlay({ - containerRef, - getScrollElement, -}: { - containerRef: React.MutableRefObject; - getScrollElement: () => HTMLDivElement | null; -}) { - const [hoverRowTop, setHoverRowTop] = useState(); - - const { t } = useTranslation(); - const [openConfirm, setOpenConfirm] = useState(false); - const [confirmModalProps, setConfirmModalProps] = useState< - | { - onOk: () => Promise; - onCancel: () => void; - } - | undefined - >(undefined); - - const { hoverRowId } = useGridTableHoverState(containerRef); - - const handleOpenConfirm = useCallback((onOk: () => Promise, onCancel: () => void) => { - setOpenConfirm(true); - setConfirmModalProps({ onOk, onCancel }); - }, []); - - useEffect(() => { - const container = containerRef.current; - - if (!container) return; - - const cell = container.querySelector(`[data-key="row:${hoverRowId}"]`); - - if (!cell) return; - const top = (cell as HTMLDivElement).style.top; - - setHoverRowTop(top); - }, [containerRef, hoverRowId]); - - return ( -
- - - {openConfirm && ( - { - setOpenConfirm(false); - }} - {...confirmModalProps} - /> - )} -
- ); -} - -export default GridTableOverlay; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_row_actions/GridRowActions.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_row_actions/GridRowActions.hooks.ts deleted file mode 100644 index a4251c9ed5..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_row_actions/GridRowActions.hooks.ts +++ /dev/null @@ -1,244 +0,0 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { useViewId } from '$app/hooks'; -import { rowService } from '$app/application/database'; -import { autoScrollOnEdge, ScrollDirection } from '$app/components/database/_shared/dnd/utils'; -import { useSortsCount } from '$app/components/database'; -import { deleteAllSorts } from '$app/application/database/sort/sort_service'; - -export function getCellsWithRowId(rowId: string, container: HTMLDivElement) { - return Array.from(container.querySelectorAll(`[data-key^="row:${rowId}"]`)); -} - -const SELECTED_ROW_CSS_PROPERTY = 'bg-content-blue-50'; - -export function toggleProperty( - container: HTMLDivElement, - rowId: string, - status: boolean, - property = SELECTED_ROW_CSS_PROPERTY -) { - const rowColumns = getCellsWithRowId(rowId, container); - - rowColumns.forEach((column, index) => { - if (index === 0) return; - if (status) { - column.classList.add(property); - } else { - column.classList.remove(property); - } - }); -} - -function createVirtualDragElement(rowId: string, container: HTMLDivElement) { - const cells = getCellsWithRowId(rowId, container); - - const cell = cells[0] as HTMLDivElement; - - if (!cell) return null; - - const row = document.createElement('div'); - - row.style.display = 'flex'; - row.style.position = 'absolute'; - row.style.top = cell.style.top; - const left = Number(cell.style.left.split('px')[0]) + 64; - - row.style.left = `${left}px`; - row.style.background = 'var(--content-blue-50)'; - cells.forEach((cell) => { - const node = cell.cloneNode(true) as HTMLDivElement; - - if (!node.classList.contains('grid-cell')) return; - - node.style.top = ''; - node.style.position = ''; - node.style.left = ''; - node.style.width = (cell as HTMLDivElement).style.width; - node.style.height = (cell as HTMLDivElement).style.height; - node.className = 'flex items-center border-r border-b border-divider-line opacity-50'; - row.appendChild(node); - }); - - cell.parentElement?.appendChild(row); - return row; -} - -export function useDraggableGridRow( - rowId: string, - containerRef: React.RefObject, - getScrollElement: () => HTMLDivElement | null, - onOpenConfirm: (onOk: () => Promise, onCancel: () => void) => void -) { - const viewId = useViewId(); - const sortsCount = useSortsCount(); - - const [isDragging, setIsDragging] = useState(false); - const dropRowIdRef = useRef(undefined); - const previewRef = useRef(); - - const onDragStart = useCallback( - (e: React.DragEvent) => { - e.dataTransfer.effectAllowed = 'move'; - e.dataTransfer.dropEffect = 'move'; - const container = containerRef.current; - - if (container) { - const row = createVirtualDragElement(rowId, container); - - if (row) { - previewRef.current = row; - e.dataTransfer.setDragImage(row, 0, 0); - } - } - - const scrollParent = getScrollElement(); - - if (scrollParent) { - autoScrollOnEdge({ - element: scrollParent, - direction: ScrollDirection.Vertical, - edgeGap: 20, - }); - } - - setIsDragging(true); - }, - [containerRef, rowId, getScrollElement] - ); - - const moveRowTo = useCallback( - async (toRowId: string) => { - return rowService.moveRow(viewId, rowId, toRowId); - }, - [viewId, rowId] - ); - - useEffect(() => { - if (!isDragging) { - if (previewRef.current) { - const row = previewRef.current; - - previewRef.current = undefined; - row?.remove(); - } - - return; - } - - const container = containerRef.current; - - if (!container) { - return; - } - - const onDragOver = (e: DragEvent) => { - e.preventDefault(); - const target = e.target as HTMLElement; - const cell = target.closest('[data-key]'); - const rowId = cell?.getAttribute('data-key')?.split(':')[1]; - - const oldRowId = dropRowIdRef.current; - - if (oldRowId) { - toggleProperty(container, oldRowId, false); - } - - if (!rowId) return; - - const rowColumns = getCellsWithRowId(rowId, container); - - dropRowIdRef.current = rowId; - if (!rowColumns.length) return; - - toggleProperty(container, rowId, true); - }; - - const onDragEnd = () => { - const oldRowId = dropRowIdRef.current; - - if (oldRowId) { - toggleProperty(container, oldRowId, false); - } - - dropRowIdRef.current = undefined; - setIsDragging(false); - }; - - const onDrop = async (e: DragEvent) => { - e.preventDefault(); - e.stopPropagation(); - const dropRowId = dropRowIdRef.current; - - toggleProperty(container, rowId, false); - if (dropRowId) { - if (sortsCount > 0) { - onOpenConfirm( - async () => { - await deleteAllSorts(viewId); - await moveRowTo(dropRowId); - }, - () => { - void moveRowTo(dropRowId); - } - ); - } else { - void moveRowTo(dropRowId); - } - - toggleProperty(container, dropRowId, false); - } - - setIsDragging(false); - container.removeEventListener('dragover', onDragOver); - container.removeEventListener('dragend', onDragEnd); - container.removeEventListener('drop', onDrop); - }; - - container.addEventListener('dragover', onDragOver); - container.addEventListener('dragend', onDragEnd); - container.addEventListener('drop', onDrop); - }, [isDragging, containerRef, moveRowTo, onOpenConfirm, rowId, sortsCount, viewId]); - - return { - isDragging, - onDragStart, - }; -} - -export function useGridTableHoverState(containerRef?: React.RefObject) { - const [hoverRowId, setHoverRowId] = useState(undefined); - - useEffect(() => { - const container = containerRef?.current; - - if (!container) return; - const onMouseMove = (e: MouseEvent) => { - const target = e.target as HTMLElement; - const cell = target.closest('[data-key]'); - - if (!cell) { - return; - } - - const hoverRowId = cell.getAttribute('data-key')?.split(':')[1]; - - setHoverRowId(hoverRowId); - }; - - const onMouseLeave = () => { - setHoverRowId(undefined); - }; - - container.addEventListener('mousemove', onMouseMove); - container.addEventListener('mouseleave', onMouseLeave); - - return () => { - container.removeEventListener('mousemove', onMouseMove); - container.removeEventListener('mouseleave', onMouseLeave); - }; - }, [containerRef]); - - return { - hoverRowId, - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_row_actions/GridRowActions.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_row_actions/GridRowActions.tsx deleted file mode 100644 index f4b39e2561..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_row_actions/GridRowActions.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import React, { useCallback, useState } from 'react'; -import { IconButton, Tooltip } from '@mui/material'; -import { ReactComponent as AddSvg } from '$app/assets/add.svg'; -import { GRID_ACTIONS_WIDTH } from '$app/components/database/grid/constants'; -import { rowService } from '$app/application/database'; -import { useViewId } from '$app/hooks'; -import { GridRowDragButton, GridRowMenu, toggleProperty } from '$app/components/database/grid/grid_row_actions'; -import { OrderObjectPositionTypePB } from '@/services/backend'; -import { useSortsCount } from '$app/components/database'; -import { useTranslation } from 'react-i18next'; -import { deleteAllSorts } from '$app/application/database/sort/sort_service'; - -export function GridRowActions({ - rowId, - rowTop, - containerRef, - getScrollElement, - onOpenConfirm, -}: { - onOpenConfirm: (onOk: () => Promise, onCancel: () => void) => void; - rowId?: string; - rowTop?: string; - containerRef: React.MutableRefObject; - getScrollElement: () => HTMLDivElement | null; -}) { - const { t } = useTranslation(); - const viewId = useViewId(); - const sortsCount = useSortsCount(); - const [menuRowId, setMenuRowId] = useState(undefined); - const [menuPosition, setMenuPosition] = useState< - | { - top: number; - left: number; - } - | undefined - >(undefined); - - const openMenu = Boolean(menuPosition); - - const handleCloseMenu = useCallback(() => { - setMenuPosition(undefined); - if (containerRef.current && menuRowId) { - toggleProperty(containerRef.current, menuRowId, false); - } - }, [containerRef, menuRowId]); - - const handleInsertRecordBelow = useCallback( - async (rowId: string) => { - await rowService.createRow(viewId, { - position: OrderObjectPositionTypePB.After, - rowId: rowId, - }); - handleCloseMenu(); - }, - [viewId, handleCloseMenu] - ); - - const handleOpenMenu = (e: React.MouseEvent) => { - const target = e.target as HTMLButtonElement; - const rect = target.getBoundingClientRect(); - - if (containerRef.current && rowId) { - toggleProperty(containerRef.current, rowId, true); - } - - setMenuRowId(rowId); - setMenuPosition({ - top: rect.top + rect.height / 2, - left: rect.left + rect.width, - }); - }; - - return ( - <> - {rowId && rowTop && ( -
- - { - if (sortsCount > 0) { - onOpenConfirm( - async () => { - await deleteAllSorts(viewId); - void handleInsertRecordBelow(rowId); - }, - () => { - void handleInsertRecordBelow(rowId); - } - ); - } else { - void handleInsertRecordBelow(rowId); - } - }} - > - - - - -
- )} - {menuRowId && ( - - )} - - ); -} - -export default GridRowActions; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_row_actions/GridRowContextMenu.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_row_actions/GridRowContextMenu.tsx deleted file mode 100644 index a93188ddc4..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_row_actions/GridRowContextMenu.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import GridRowMenu from './GridRowMenu'; -import { toggleProperty } from './GridRowActions.hooks'; - -export function GridRowContextMenu({ - containerRef, - hoverRowId, - onOpenConfirm, -}: { - hoverRowId?: string; - onOpenConfirm: (onOk: () => Promise, onCancel: () => void) => void; - containerRef: React.MutableRefObject; -}) { - const [position, setPosition] = useState<{ left: number; top: number } | undefined>(); - - const [rowId, setRowId] = useState(); - - const isContextMenuOpen = useMemo(() => { - return !!position; - }, [position]); - - const closeContextMenu = useCallback(() => { - setPosition(undefined); - const container = containerRef.current; - - if (!container || !rowId) return; - toggleProperty(container, rowId, false); - // setRowId(undefined); - }, [rowId, containerRef]); - - const openContextMenu = useCallback( - (event: MouseEvent) => { - event.preventDefault(); - event.stopPropagation(); - const container = containerRef.current; - - if (!container || !hoverRowId) return; - toggleProperty(container, hoverRowId, true); - setRowId(hoverRowId); - setPosition({ - left: event.clientX, - top: event.clientY, - }); - }, - [containerRef, hoverRowId] - ); - - useEffect(() => { - const container = containerRef.current; - - if (!container) { - return; - } - - container.addEventListener('contextmenu', openContextMenu); - return () => { - container.removeEventListener('contextmenu', openContextMenu); - }; - }, [containerRef, openContextMenu]); - - return rowId ? ( - - ) : null; -} - -export default GridRowContextMenu; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_row_actions/GridRowDragButton.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_row_actions/GridRowDragButton.tsx deleted file mode 100644 index 0790e48183..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_row_actions/GridRowDragButton.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { useDraggableGridRow } from './GridRowActions.hooks'; -import { IconButton, Tooltip } from '@mui/material'; -import { ReactComponent as DragSvg } from '$app/assets/drag.svg'; -import { useTranslation } from 'react-i18next'; - -export function GridRowDragButton({ - rowId, - containerRef, - onClick, - getScrollElement, - onOpenConfirm, -}: { - onOpenConfirm: (onOk: () => Promise, onCancel: () => void) => void; - rowId: string; - onClick?: (e: React.MouseEvent) => void; - containerRef: React.MutableRefObject; - getScrollElement: () => HTMLDivElement | null; -}) { - const { t } = useTranslation(); - - const [openTooltip, setOpenTooltip] = useState(false); - const { onDragStart, isDragging } = useDraggableGridRow(rowId, containerRef, getScrollElement, onOpenConfirm); - - useEffect(() => { - if (isDragging) { - setOpenTooltip(false); - } - }, [isDragging]); - - return ( - <> - { - setOpenTooltip(true); - }} - onClose={() => { - setOpenTooltip(false); - }} - placement='top' - disableInteractive={true} - title={t('grid.row.dragAndClick')} - > - - - - - - ); -} - -export default GridRowDragButton; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_row_actions/GridRowMenu.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_row_actions/GridRowMenu.tsx deleted file mode 100644 index 2190e8739b..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_row_actions/GridRowMenu.tsx +++ /dev/null @@ -1,160 +0,0 @@ -import React, { useCallback, useMemo } from 'react'; -import { ReactComponent as UpSvg } from '$app/assets/up.svg'; -import { ReactComponent as AddSvg } from '$app/assets/add.svg'; -import { ReactComponent as DelSvg } from '$app/assets/delete.svg'; -import { ReactComponent as CopySvg } from '$app/assets/copy.svg'; -import Popover, { PopoverProps } from '@mui/material/Popover'; -import { useViewId } from '$app/hooks'; -import { useTranslation } from 'react-i18next'; -import { rowService } from '$app/application/database'; -import { OrderObjectPositionTypePB } from '@/services/backend'; -import KeyboardNavigation, { - KeyboardNavigationOption, -} from '$app/components/_shared/keyboard_navigation/KeyboardNavigation'; -import { useSortsCount } from '$app/components/database'; -import { deleteAllSorts } from '$app/application/database/sort/sort_service'; - -enum RowAction { - InsertAbove, - InsertBelow, - Duplicate, - Delete, -} -interface Props extends PopoverProps { - rowId: string; - onOpenConfirm?: (onOk: () => Promise, onCancel: () => void) => void; -} - -export function GridRowMenu({ onOpenConfirm, rowId, onClose, ...props }: Props) { - const viewId = useViewId(); - const sortsCount = useSortsCount(); - - const { t } = useTranslation(); - - const handleInsertRecordBelow = useCallback(() => { - void rowService.createRow(viewId, { - position: OrderObjectPositionTypePB.After, - rowId: rowId, - }); - }, [viewId, rowId]); - - const handleInsertRecordAbove = useCallback(() => { - void rowService.createRow(viewId, { - position: OrderObjectPositionTypePB.Before, - rowId: rowId, - }); - }, [rowId, viewId]); - - const handleDelRow = useCallback(() => { - void rowService.deleteRow(viewId, rowId); - }, [viewId, rowId]); - - const handleDuplicateRow = useCallback(() => { - void rowService.duplicateRow(viewId, rowId); - }, [viewId, rowId]); - - const renderContent = useCallback((title: string, Icon: React.FC>) => { - return ( -
- -
{title}
-
- ); - }, []); - - const handleAction = useCallback( - (confirmKey?: RowAction) => { - switch (confirmKey) { - case RowAction.InsertAbove: - handleInsertRecordAbove(); - break; - case RowAction.InsertBelow: - handleInsertRecordBelow(); - break; - case RowAction.Duplicate: - handleDuplicateRow(); - break; - case RowAction.Delete: - handleDelRow(); - break; - default: - break; - } - }, - [handleDelRow, handleDuplicateRow, handleInsertRecordAbove, handleInsertRecordBelow] - ); - - const onConfirm = useCallback( - (key: RowAction) => { - if (sortsCount > 0) { - onOpenConfirm?.( - async () => { - await deleteAllSorts(viewId); - handleAction(key); - }, - () => { - handleAction(key); - } - ); - } else { - handleAction(key); - } - - onClose?.({}, 'backdropClick'); - }, - [handleAction, onClose, onOpenConfirm, sortsCount, viewId] - ); - - const options: KeyboardNavigationOption[] = useMemo( - () => [ - { - key: RowAction.InsertAbove, - content: renderContent(t('grid.row.insertRecordAbove'), UpSvg), - }, - { - key: RowAction.InsertBelow, - content: renderContent(t('grid.row.insertRecordBelow'), AddSvg), - }, - { - key: RowAction.Duplicate, - content: renderContent(t('grid.row.duplicate'), CopySvg), - }, - - { - key: 100, - content:
, - children: [], - }, - { - key: RowAction.Delete, - content: renderContent(t('grid.row.delete'), DelSvg), - }, - ], - [renderContent, t] - ); - - return ( - <> - -
- { - onClose?.({}, 'escapeKeyDown'); - }} - /> -
-
- - ); -} - -export default GridRowMenu; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_row_actions/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_row_actions/index.ts deleted file mode 100644 index fb50b6248c..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_row_actions/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './GridRowActions.hooks'; -export * from './GridRowActions'; -export * from './GridRowContextMenu'; -export * from './GridRowDragButton'; -export * from './GridRowMenu'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_sticky_header/GridStickyHeader.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_sticky_header/GridStickyHeader.hooks.ts deleted file mode 100644 index ac5c0688b9..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_sticky_header/GridStickyHeader.hooks.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { createContext, useContext } from 'react'; - -export const OpenMenuContext = createContext(null); - -export const useOpenMenu = (id: string) => { - const context = useContext(OpenMenuContext); - - return context === id; -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_sticky_header/GridStickyHeader.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_sticky_header/GridStickyHeader.tsx deleted file mode 100644 index e9d01508b1..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_sticky_header/GridStickyHeader.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import React, { useCallback, useState } from 'react'; -import { GridChildComponentProps, GridOnScrollProps, VariableSizeGrid as Grid } from 'react-window'; -import AutoSizer from 'react-virtualized-auto-sizer'; -import { useGridColumn } from '$app/components/database/grid/grid_table'; -import { GridField } from 'src/appflowy_app/components/database/grid/grid_field'; -import NewProperty from '$app/components/database/components/property/NewProperty'; -import { GridColumn, GridColumnType, RenderRow } from '$app/components/database/grid/constants'; -import { OpenMenuContext } from '$app/components/database/grid/grid_sticky_header/GridStickyHeader.hooks'; - -const GridStickyHeader = React.forwardRef< - Grid | null, - { - columns: GridColumn[]; - getScrollElement?: () => HTMLDivElement | null; - onScroll?: (props: GridOnScrollProps) => void; - } ->(({ onScroll, columns, getScrollElement }, ref) => { - const { columnWidth, resizeColumnWidth } = useGridColumn( - columns, - ref as React.MutableRefObject | null> - ); - - const [openMenuId, setOpenMenuId] = useState(null); - - const handleOpenMenu = useCallback((id: string) => { - setOpenMenuId(id); - }, []); - - const handleCloseMenu = useCallback((id: string) => { - setOpenMenuId((prev) => { - if (prev === id) { - return null; - } - - return prev; - }); - }, []); - - const Cell = useCallback( - ({ columnIndex, style, data }: GridChildComponentProps) => { - const column = data[columnIndex]; - - if (!column || column.type === GridColumnType.Action) return
; - if (column.type === GridColumnType.NewProperty) { - const width = (style.width || 0) as number; - - return ( -
- -
- ); - } - - const field = column.field; - - if (!field) return
; - - return ( - resizeColumnWidth(columnIndex, width)} - field={field} - getScrollElement={getScrollElement} - /> - ); - }, - [handleCloseMenu, handleOpenMenu, resizeColumnWidth, getScrollElement] - ); - - return ( - - - {({ height, width }: { height: number; width: number }) => { - return ( - 36} - rowCount={1} - columnCount={columns.length} - columnWidth={columnWidth} - ref={ref} - onScroll={onScroll} - itemData={columns} - style={{ overscrollBehavior: 'none' }} - > - {Cell} - - ); - }} - - - ); -}); - -export default GridStickyHeader; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_table/GridTable.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_table/GridTable.hooks.ts deleted file mode 100644 index 0d676f3bb2..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_table/GridTable.hooks.ts +++ /dev/null @@ -1,67 +0,0 @@ -import React, { useCallback, useEffect, useState } from 'react'; -import { DEFAULT_FIELD_WIDTH, GRID_ACTIONS_WIDTH, GridColumn, RenderRow } from '$app/components/database/grid/constants'; -import { VariableSizeGrid as Grid } from 'react-window'; - -export function useGridRow() { - const rowHeight = useCallback(() => { - return 36; - }, []); - - return { - rowHeight, - }; -} - -export function useGridColumn( - columns: GridColumn[], - ref: React.RefObject | null> -) { - const [columnWidths, setColumnWidths] = useState([]); - - useEffect(() => { - setColumnWidths( - columns.map((field, index) => (index === 0 ? GRID_ACTIONS_WIDTH : field.width || DEFAULT_FIELD_WIDTH)) - ); - ref.current?.resetAfterColumnIndex(0); - }, [columns, ref]); - - const resizeColumnWidth = useCallback( - (index: number, width: number) => { - setColumnWidths((columnWidths) => { - if (columnWidths[index] === width) { - return columnWidths; - } - - const newColumnWidths = [...columnWidths]; - - newColumnWidths[index] = width; - - return newColumnWidths; - }); - - if (ref.current) { - ref.current.resetAfterColumnIndex(index); - } - }, - [ref] - ); - - const columnWidth = useCallback( - (index: number) => { - if (index === 0) return GRID_ACTIONS_WIDTH; - return columnWidths[index] || DEFAULT_FIELD_WIDTH; - }, - [columnWidths] - ); - - return { - columnWidth, - resizeColumnWidth, - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_table/GridTable.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_table/GridTable.tsx deleted file mode 100644 index 0cd17d6a05..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_table/GridTable.tsx +++ /dev/null @@ -1,161 +0,0 @@ -import React, { FC, useCallback, useMemo, useRef } from 'react'; -import { RowMeta } from '$app/application/database'; -import { useDatabaseRendered, useDatabaseVisibilityFields, useDatabaseVisibilityRows } from '../../Database.hooks'; -import { fieldsToColumns, GridColumn, RenderRow, RenderRowType, rowMetasToRenderRow } from '../constants'; -import { CircularProgress } from '@mui/material'; -import { GridChildComponentProps, GridOnScrollProps, VariableSizeGrid as Grid } from 'react-window'; -import AutoSizer from 'react-virtualized-auto-sizer'; -import { GridCell } from 'src/appflowy_app/components/database/grid/grid_cell'; -import { useGridColumn, useGridRow } from './GridTable.hooks'; -import GridStickyHeader from '$app/components/database/grid/grid_sticky_header/GridStickyHeader'; -import GridTableOverlay from '$app/components/database/grid/grid_overlay/GridTableOverlay'; -import ReactDOM from 'react-dom'; -import { useViewId } from '$app/hooks'; - -export interface GridTableProps { - onEditRecord: (rowId: string) => void; -} - -export const GridTable: FC = React.memo(({ onEditRecord }) => { - const rowMetas = useDatabaseVisibilityRows(); - const fields = useDatabaseVisibilityFields(); - const renderRows = useMemo(() => rowMetasToRenderRow(rowMetas as RowMeta[]), [rowMetas]); - const columns = useMemo(() => fieldsToColumns(fields), [fields]); - const ref = useRef< - Grid<{ - columns: GridColumn[]; - renderRows: RenderRow[]; - }> - >(null); - const { columnWidth } = useGridColumn( - columns, - ref as React.MutableRefObject | null> - ); - const viewId = useViewId(); - const { rowHeight } = useGridRow(); - const onRendered = useDatabaseRendered(); - - const getItemKey = useCallback( - ({ columnIndex, rowIndex }: { columnIndex: number; rowIndex: number }) => { - const row = renderRows[rowIndex]; - const column = columns[columnIndex]; - - const field = column.field; - - if (row.type === RenderRowType.Row) { - if (field) { - return `${row.data.meta.id}:${field.id}`; - } - - return `${row.data.meta.id}:${column.type}`; - } - - if (field) { - return `${row.type}:${field.id}`; - } - - return `${row.type}:${column.type}`; - }, - [columns, renderRows] - ); - - const getContainerRef = useCallback(() => { - return containerRef; - }, []); - - const Cell = useCallback( - ({ columnIndex, rowIndex, style, data }: GridChildComponentProps) => { - const row = data.renderRows[rowIndex]; - const column = data.columns[columnIndex]; - - return ( - - ); - }, - [getContainerRef, onEditRecord] - ); - - const staticGrid = useRef | null>(null); - - const onScroll = useCallback(({ scrollLeft, scrollUpdateWasRequested }: GridOnScrollProps) => { - if (!scrollUpdateWasRequested) { - staticGrid.current?.scrollTo({ scrollLeft, scrollTop: 0 }); - } - }, []); - - const onHeaderScroll = useCallback(({ scrollLeft }: GridOnScrollProps) => { - ref.current?.scrollTo({ scrollLeft }); - }, []); - - const containerRef = useRef(null); - const scrollElementRef = useRef(null); - - const getScrollElement = useCallback(() => { - return scrollElementRef.current; - }, []); - - return ( -
- {fields.length === 0 && ( -
- -
- )} -
- -
- -
- - {({ height, width }: { height: number; width: number }) => ( - { - scrollElementRef.current = el; - onRendered(viewId); - }} - innerRef={containerRef} - > - {Cell} - - )} - - {containerRef.current - ? ReactDOM.createPortal( - , - containerRef.current - ) - : null} -
-
- ); -}); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_table/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_table/index.ts deleted file mode 100644 index dfdb9b7949..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/grid_table/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './GridTable'; -export * from './GridTable.hooks'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/index.ts deleted file mode 100644 index 762542e7cb..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/grid/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Grid'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/database/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/database/index.ts deleted file mode 100644 index 42a6f31592..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/database/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './Database.hooks'; -export * from './Database'; -export * from './DatabaseTitle'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/Document.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/Document.tsx deleted file mode 100644 index 079a6fd75f..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/Document.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import Editor from '$app/components/editor/Editor'; -import { DocumentHeader } from 'src/appflowy_app/components/document/document_header'; -import { useAppDispatch, useAppSelector } from '$app/stores/store'; -import { updatePageName } from '$app_reducers/pages/async_actions'; -import { PageCover } from '$app_reducers/pages/slice'; - -export function Document({ id }: { id: string }) { - const page = useAppSelector((state) => state.pages.pageMap[id]); - - const [cover, setCover] = useState(undefined); - const dispatch = useAppDispatch(); - - const onTitleChange = useCallback( - (newTitle: string) => { - void dispatch( - updatePageName({ - id, - name: newTitle, - }) - ); - }, - [dispatch, id] - ); - - const view = useMemo(() => { - return { - ...page, - cover, - }; - }, [page, cover]); - - useEffect(() => { - return () => { - setCover(undefined); - }; - }, [id]); - - if (!page) return null; - - return ( -
- -
-
- -
-
-
- ); -} - -export default Document; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/document_header/DocumentHeader.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/document/document_header/DocumentHeader.tsx deleted file mode 100644 index f6e8736c54..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/document_header/DocumentHeader.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import React, { memo, useCallback, useEffect, useRef, useState } from 'react'; -import { Page, PageCover, PageIcon } from '$app_reducers/pages/slice'; -import ViewTitle from '$app/components/_shared/view_title/ViewTitle'; -import { updatePageIcon } from '$app/application/folder/page.service'; - -interface DocumentHeaderProps { - page: Page; - onUpdateCover: (cover?: PageCover) => void; -} - -export function DocumentHeader({ page, onUpdateCover }: DocumentHeaderProps) { - const pageId = page.id; - const ref = useRef(null); - - const [forceHover, setForceHover] = useState(false); - const onUpdateIcon = useCallback( - async (icon: PageIcon) => { - await updatePageIcon(pageId, icon.value ? icon : undefined); - }, - [pageId] - ); - - useEffect(() => { - const parent = ref.current?.parentElement; - - if (!parent) return; - - const documentDom = parent.querySelector('.appflowy-editor') as HTMLElement; - - if (!documentDom) return; - - const handleMouseMove = (e: MouseEvent) => { - const isMoveInTitle = Boolean(e.target instanceof HTMLElement && e.target.closest('.document-title')); - const isMoveInHeader = Boolean(e.target instanceof HTMLElement && e.target.closest('.document-header')); - - setForceHover(isMoveInTitle || isMoveInHeader); - }; - - documentDom.addEventListener('mousemove', handleMouseMove); - return () => { - documentDom.removeEventListener('mousemove', handleMouseMove); - }; - }, []); - - if (!page) return null; - return ( -
- -
- ); -} - -export default memo(DocumentHeader); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/document_header/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/document/document_header/index.ts deleted file mode 100644 index 00f48716bf..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/document_header/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './DocumentHeader'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/document/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/document/index.ts deleted file mode 100644 index a844aa51ad..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/document/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Document'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/Editor.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/Editor.hooks.ts deleted file mode 100644 index 1fc25346d2..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/Editor.hooks.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { createContext, useContext } from 'react'; - -export const EditorIdContext = createContext(''); - -export const EditorIdProvider = EditorIdContext.Provider; - -export function useEditorId() { - return useContext(EditorIdContext); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/Editor.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/Editor.tsx deleted file mode 100644 index 879dc5f9c0..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/Editor.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React, { memo } from 'react'; -import { EditorProps } from '../../application/document/document.types'; - -import { CollaborativeEditor } from '$app/components/editor/components/editor'; -import { EditorIdProvider } from '$app/components/editor/Editor.hooks'; -import './editor.scss'; -import withErrorBoundary from '$app/components/_shared/error_boundary/withError'; - -export function Editor(props: EditorProps) { - return ( -
- - - -
- ); -} - -export default withErrorBoundary(memo(Editor)); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/command/formula.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/command/formula.ts deleted file mode 100644 index 04a2e7c0f1..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/command/formula.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { ReactEditor } from 'slate-react'; -import { Editor, Element, Element as SlateElement, NodeEntry, Range, Transforms } from 'slate'; -import { EditorInlineNodeType, FormulaNode } from '$app/application/document/document.types'; - -export function insertFormula(editor: ReactEditor, formula?: string) { - if (editor.selection) { - wrapFormula(editor, formula); - } -} - -export function updateFormula(editor: ReactEditor, formula: string) { - if (isFormulaActive(editor)) { - Transforms.delete(editor); - wrapFormula(editor, formula); - } -} - -export function deleteFormula(editor: ReactEditor) { - if (isFormulaActive(editor)) { - Transforms.delete(editor); - } -} - -export function wrapFormula(editor: ReactEditor, formula?: string) { - if (isFormulaActive(editor)) { - unwrapFormula(editor); - } - - const { selection } = editor; - - if (!selection) return; - const isCollapsed = selection && Range.isCollapsed(selection); - - const data = formula || editor.string(selection); - const formulaElement = { - type: EditorInlineNodeType.Formula, - data, - children: [ - { - text: '$', - }, - ], - }; - - if (!isCollapsed) { - Transforms.delete(editor); - } - - Transforms.insertNodes(editor, formulaElement, { - select: true, - }); - - const path = editor.selection?.anchor.path; - - if (path) { - editor.select(path); - } -} - -export function unwrapFormula(editor: ReactEditor) { - const [match] = Editor.nodes(editor, { - match: (n) => !Editor.isEditor(n) && SlateElement.isElement(n) && n.type === EditorInlineNodeType.Formula, - }); - - if (!match) return; - - const [node, path] = match as NodeEntry; - const formula = node.data; - const range = Editor.range(editor, match[1]); - const beforePoint = Editor.before(editor, path, { unit: 'character' }); - - Transforms.select(editor, range); - Transforms.delete(editor); - - Transforms.insertText(editor, formula); - - if (!beforePoint) return; - Transforms.select(editor, { - anchor: beforePoint, - focus: { - ...beforePoint, - offset: beforePoint.offset + formula.length, - }, - }); -} - -export function isFormulaActive(editor: ReactEditor) { - const [match] = editor.nodes({ - match: (n) => { - return !Editor.isEditor(n) && Element.isElement(n) && n.type === EditorInlineNodeType.Formula; - }, - }); - - return Boolean(match); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/command/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/command/index.ts deleted file mode 100644 index 557b91f936..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/command/index.ts +++ /dev/null @@ -1,715 +0,0 @@ -import { ReactEditor } from 'slate-react'; -import { - Editor, - Element, - Node, - NodeEntry, - Point, - Range, - Transforms, - Location, - Path, - EditorBeforeOptions, - Text, - addMark, -} from 'slate'; -import { LIST_TYPES, tabBackward, tabForward } from '$app/components/editor/command/tab'; -import { getAllMarks, isMarkActive, removeMarks, toggleMark } from '$app/components/editor/command/mark'; -import { - deleteFormula, - insertFormula, - isFormulaActive, - unwrapFormula, - updateFormula, -} from '$app/components/editor/command/formula'; -import { - EditorInlineNodeType, - EditorNodeType, - CalloutNode, - Mention, - TodoListNode, - ToggleListNode, - inlineNodeTypes, - FormulaNode, - ImageNode, - EditorMarkFormat, -} from '$app/application/document/document.types'; -import cloneDeep from 'lodash-es/cloneDeep'; -import { generateId } from '$app/components/editor/provider/utils/convert'; -import { YjsEditor } from '@slate-yjs/core'; - -export const EmbedTypes: string[] = [ - EditorNodeType.DividerBlock, - EditorNodeType.EquationBlock, - EditorNodeType.GridBlock, - EditorNodeType.ImageBlock, -]; - -export const CustomEditor = { - getBlock: (editor: ReactEditor, at?: Location): NodeEntry | undefined => { - return Editor.above(editor, { - at, - match: (n) => !Editor.isEditor(n) && Element.isElement(n) && n.blockId !== undefined, - }); - }, - - isInlineNode: (editor: ReactEditor, point: Point): boolean => { - return Boolean( - editor.above({ - at: point, - match: (n) => { - return !Editor.isEditor(n) && Element.isElement(n) && inlineNodeTypes.includes(n.type as EditorInlineNodeType); - }, - }) - ); - }, - - beforeIsInlineNode: (editor: ReactEditor, at: Location, opts?: EditorBeforeOptions): boolean => { - const beforePoint = Editor.before(editor, at, opts); - - if (!beforePoint) return false; - return CustomEditor.isInlineNode(editor, beforePoint); - }, - - afterIsInlineNode: (editor: ReactEditor, at: Location, opts?: EditorBeforeOptions): boolean => { - const afterPoint = Editor.after(editor, at, opts); - - if (!afterPoint) return false; - return CustomEditor.isInlineNode(editor, afterPoint); - }, - - /** - * judge if the selection is multiple block - * @param editor - * @param filterEmptyEndSelection if the filterEmptyEndSelection is true, the function will filter the empty end selection - */ - isMultipleBlockSelected: (editor: ReactEditor, filterEmptyEndSelection?: boolean): boolean => { - const { selection } = editor; - - if (!selection) return false; - - if (Range.isCollapsed(selection)) return false; - const start = Range.start(selection); - const end = Range.end(selection); - const isBackward = Range.isBackward(selection); - const startBlock = CustomEditor.getBlock(editor, start); - const endBlock = CustomEditor.getBlock(editor, end); - - if (!startBlock || !endBlock) return false; - - const [, startPath] = startBlock; - const [, endPath] = endBlock; - - const isSomePath = Path.equals(startPath, endPath); - - // if the start and end path is the same, return false - if (isSomePath) { - return false; - } - - if (!filterEmptyEndSelection) { - return true; - } - - // The end point is at the start of the end block - const focusEndStart = Point.equals(end, editor.start(endPath)); - - if (!focusEndStart) { - return true; - } - - // find the previous block - const previous = editor.previous({ - at: endPath, - match: (n) => Element.isElement(n) && n.blockId !== undefined, - }); - - if (!previous) { - return true; - } - - // backward selection - const newEnd = editor.end(editor.range(previous[1])); - - editor.select({ - anchor: isBackward ? newEnd : start, - focus: isBackward ? start : newEnd, - }); - - return false; - }, - - /** - * turn the current block to a new block - * 1. clone the current block to a new block - * 2. lift the children of the current block if the current block doesn't allow has children - * 3. remove the old block - * 4. insert the new block - * @param editor - * @param newProperties - */ - turnToBlock: (editor: ReactEditor, newProperties: Partial) => { - const selection = editor.selection; - - if (!selection) return; - const match = CustomEditor.getBlock(editor); - - if (!match) return; - - const [node, path] = match as NodeEntry; - - const cloneNode = CustomEditor.cloneBlock(editor, node); - - Object.assign(cloneNode, newProperties); - cloneNode.data = { - ...(node.data || {}), - ...(newProperties.data || {}), - }; - - const isEmbed = editor.isEmbed(cloneNode); - - if (isEmbed) { - editor.splitNodes({ - always: true, - }); - cloneNode.children = []; - - Transforms.removeNodes(editor, { - at: path, - }); - Transforms.insertNodes(editor, cloneNode, { at: path }); - return cloneNode; - } - - const isListType = LIST_TYPES.includes(cloneNode.type as EditorNodeType); - - // if node doesn't allow has children, lift the children before insert the new node and remove the old node - if (!isListType) { - const [textNode, ...children] = cloneNode.children; - - const length = children.length; - - for (let i = 0; i < length; i++) { - editor.liftNodes({ - at: [...path, length - i], - }); - } - - cloneNode.children = [textNode]; - } - - Transforms.removeNodes(editor, { - at: path, - }); - - Transforms.insertNodes(editor, cloneNode, { at: path }); - if (selection) { - editor.select(selection); - } - - return cloneNode; - }, - tabForward, - tabBackward, - toggleMark, - removeMarks, - isMarkActive, - isFormulaActive, - insertFormula, - updateFormula, - deleteFormula, - toggleFormula: (editor: ReactEditor) => { - if (isFormulaActive(editor)) { - unwrapFormula(editor); - } else { - insertFormula(editor); - } - }, - - isBlockActive(editor: ReactEditor, format?: string) { - const match = CustomEditor.getBlock(editor); - - if (match && format !== undefined) { - return match[0].type === format; - } - - return !!match; - }, - - toggleAlign(editor: ReactEditor, format: string) { - const isIncludeRoot = CustomEditor.selectionIncludeRoot(editor); - - if (isIncludeRoot) return; - - const matchNodes = Array.from( - Editor.nodes(editor, { - // Note: we need to select the text node instead of the element node, otherwise the parent node will be selected - match: (n) => Element.isElement(n) && n.type === EditorNodeType.Text, - }) - ); - - if (!matchNodes) return; - - matchNodes.forEach((match) => { - const [, textPath] = match as NodeEntry; - const [node] = editor.parent(textPath) as NodeEntry< - Element & { - data: { - align?: string; - }; - } - >; - const path = ReactEditor.findPath(editor, node); - - const data = (node.data as { align?: string }) || {}; - const newProperties = { - data: { - ...data, - align: data.align === format ? undefined : format, - }, - } as Partial; - - Transforms.setNodes(editor, newProperties, { at: path }); - }); - }, - - getAlign(editor: ReactEditor) { - const match = CustomEditor.getBlock(editor); - - if (!match) return undefined; - - const [node] = match as NodeEntry; - - return (node.data as { align?: string })?.align; - }, - - isInlineActive(editor: ReactEditor) { - const [match] = editor.nodes({ - match: (n) => { - return !Editor.isEditor(n) && Element.isElement(n) && inlineNodeTypes.includes(n.type as EditorInlineNodeType); - }, - }); - - return !!match; - }, - - formulaActiveNode(editor: ReactEditor) { - const [match] = editor.nodes({ - match: (n) => { - return !Editor.isEditor(n) && Element.isElement(n) && n.type === EditorInlineNodeType.Formula; - }, - }); - - return match ? (match as NodeEntry) : undefined; - }, - - isMentionActive(editor: ReactEditor) { - const [match] = editor.nodes({ - match: (n) => { - return !Editor.isEditor(n) && Element.isElement(n) && n.type === EditorInlineNodeType.Mention; - }, - }); - - return Boolean(match); - }, - - insertMention(editor: ReactEditor, mention: Mention) { - const mentionElement = [ - { - type: EditorInlineNodeType.Mention, - children: [{ text: '$' }], - data: { - ...mention, - }, - }, - ]; - - Transforms.insertNodes(editor, mentionElement, { - select: true, - }); - - editor.collapse({ - edge: 'end', - }); - }, - - toggleTodo(editor: ReactEditor, at?: Location) { - const selection = at || editor.selection; - - if (!selection) return; - - const nodes = Array.from( - editor.nodes({ - at: selection, - match: (n) => Element.isElement(n) && n.type === EditorNodeType.TodoListBlock, - }) - ); - - const matchUnChecked = nodes.some(([node]) => { - return !(node as TodoListNode).data.checked; - }); - - const checked = Boolean(matchUnChecked); - - nodes.forEach(([node, path]) => { - const data = (node as TodoListNode).data || {}; - const newProperties = { - data: { - ...data, - checked: checked, - }, - } as Partial; - - Transforms.setNodes(editor, newProperties, { at: path }); - }); - }, - - toggleToggleList(editor: ReactEditor, node: ToggleListNode) { - const collapsed = node.data.collapsed; - const path = ReactEditor.findPath(editor, node); - const data = node.data || {}; - const newProperties = { - data: { - ...data, - collapsed: !collapsed, - }, - } as Partial; - - const selectMatch = Editor.above(editor, { - match: (n) => Element.isElement(n) && n.blockId !== undefined, - }); - - Transforms.setNodes(editor, newProperties, { at: path }); - - if (selectMatch) { - const [selectNode] = selectMatch; - const selectNodePath = ReactEditor.findPath(editor, selectNode); - - if (Path.isAncestor(path, selectNodePath)) { - editor.select(path); - editor.collapse({ - edge: 'start', - }); - } - } - }, - - setCalloutIcon(editor: ReactEditor, node: CalloutNode, newIcon: string) { - const path = ReactEditor.findPath(editor, node); - const data = node.data || {}; - const newProperties = { - data: { - ...data, - icon: newIcon, - }, - } as Partial; - - Transforms.setNodes(editor, newProperties, { at: path }); - }, - - setMathEquationBlockFormula(editor: ReactEditor, node: Element, newFormula: string) { - const path = ReactEditor.findPath(editor, node); - const data = node.data || {}; - const newProperties = { - data: { - ...data, - formula: newFormula, - }, - } as Partial; - - Transforms.setNodes(editor, newProperties, { at: path }); - }, - - setGridBlockViewId(editor: ReactEditor, node: Element, newViewId: string) { - const path = ReactEditor.findPath(editor, node); - const data = node.data || {}; - const newProperties = { - data: { - ...data, - viewId: newViewId, - }, - } as Partial; - - Transforms.setNodes(editor, newProperties, { at: path }); - }, - - setImageBlockData(editor: ReactEditor, node: Element, newData: ImageNode['data']) { - const path = ReactEditor.findPath(editor, node); - const data = node.data || {}; - const newProperties = { - data: { - ...data, - ...newData, - }, - } as Partial; - - Transforms.setNodes(editor, newProperties, { at: path }); - }, - - cloneBlock(editor: ReactEditor, block: Element): Element { - const cloneNode: Element = { - ...cloneDeep(block), - blockId: generateId(), - type: block.type === EditorNodeType.Page ? EditorNodeType.Paragraph : block.type, - children: [], - }; - const isEmbed = editor.isEmbed(cloneNode); - - if (isEmbed) { - return cloneNode; - } - - const [firstTextNode, ...children] = block.children as Element[]; - - const textNode = - firstTextNode && firstTextNode.type === EditorNodeType.Text - ? { - textId: generateId(), - type: EditorNodeType.Text, - children: cloneDeep(firstTextNode.children), - } - : undefined; - - if (textNode) { - cloneNode.children.push(textNode); - } - - const cloneChildren = children.map((child) => { - return CustomEditor.cloneBlock(editor, child); - }); - - cloneNode.children.push(...cloneChildren); - - return cloneNode; - }, - - duplicateNode(editor: ReactEditor, node: Element) { - const cloneNode = CustomEditor.cloneBlock(editor, node); - - const path = ReactEditor.findPath(editor, node); - - const nextPath = Path.next(path); - - Transforms.insertNodes(editor, cloneNode, { at: nextPath }); - return cloneNode; - }, - - deleteNode(editor: ReactEditor, node: Node) { - const path = ReactEditor.findPath(editor, node); - - Transforms.removeNodes(editor, { - at: path, - }); - editor.collapse({ - edge: 'start', - }); - }, - - getBlockType: (editor: ReactEditor) => { - const match = CustomEditor.getBlock(editor); - - if (!match) return null; - - const [node] = match as NodeEntry; - - return node.type as EditorNodeType; - }, - - selectionIncludeRoot: (editor: ReactEditor) => { - const [match] = Editor.nodes(editor, { - match: (n) => Element.isElement(n) && n.blockId !== undefined && n.type === EditorNodeType.Page, - }); - - return Boolean(match); - }, - - isCodeBlock: (editor: ReactEditor) => { - return CustomEditor.getBlockType(editor) === EditorNodeType.CodeBlock; - }, - - insertEmptyLine: (editor: ReactEditor & YjsEditor, path: Path) => { - editor.insertNode( - { - type: EditorNodeType.Paragraph, - data: {}, - blockId: generateId(), - children: [ - { - type: EditorNodeType.Text, - textId: generateId(), - children: [ - { - text: '', - }, - ], - }, - ], - }, - { - select: true, - at: path, - } - ); - ReactEditor.focus(editor); - Transforms.move(editor); - }, - - insertEmptyLineAtEnd: (editor: ReactEditor & YjsEditor) => { - CustomEditor.insertEmptyLine(editor, [editor.children.length]); - }, - - focusAtStartOfBlock(editor: ReactEditor) { - const { selection } = editor; - - if (selection && Range.isCollapsed(selection)) { - const match = CustomEditor.getBlock(editor); - const [, path] = match as NodeEntry; - const start = Editor.start(editor, path); - - return match && Point.equals(selection.anchor, start); - } - - return false; - }, - - setBlockColor( - editor: ReactEditor, - node: Element, - data: { - font_color?: string; - bg_color?: string; - } - ) { - const path = ReactEditor.findPath(editor, node); - - const nodeData = node.data || {}; - const newProperties = { - data: { - ...nodeData, - ...data, - }, - } as Partial; - - Transforms.setNodes(editor, newProperties, { at: path }); - editor.select(path); - }, - - deleteAllText(editor: ReactEditor, node: Element) { - const [textNode] = (node.children || []) as Element[]; - const hasTextNode = textNode && textNode.type === EditorNodeType.Text; - - if (!hasTextNode) return; - const path = ReactEditor.findPath(editor, textNode); - const textLength = editor.string(path).length; - const start = Editor.start(editor, path); - - for (let i = 0; i < textLength; i++) { - editor.select(start); - editor.deleteForward('character'); - } - }, - - getNodeText: (editor: ReactEditor, node: Element) => { - const [textNode] = (node.children || []) as Element[]; - const hasTextNode = textNode && textNode.type === EditorNodeType.Text; - - if (!hasTextNode) return ''; - - const path = ReactEditor.findPath(editor, textNode); - - return editor.string(path); - }, - - isEmptyText: (editor: ReactEditor, node: Element) => { - const [textNode] = (node.children || []) as Element[]; - const hasTextNode = textNode && textNode.type === EditorNodeType.Text; - - if (!hasTextNode) return false; - - return editor.isEmpty(textNode); - }, - - includeInlineBlocks: (editor: ReactEditor) => { - const [match] = Editor.nodes(editor, { - match: (n) => Element.isElement(n) && editor.isInline(n), - }); - - return Boolean(match); - }, - - getNodeTextContent(node: Node): string { - if (Element.isElement(node) && node.type === EditorInlineNodeType.Formula) { - return (node as FormulaNode).data || ''; - } - - if (Text.isText(node)) { - return node.text || ''; - } - - return node.children.map((n) => CustomEditor.getNodeTextContent(n)).join(''); - }, - - isEmbedNode(node: Element): boolean { - return EmbedTypes.includes(node.type); - }, - - getListLevel(editor: ReactEditor, type: EditorNodeType, path: Path) { - let level = 0; - let currentPath = path; - - while (currentPath.length > 0) { - const parent = editor.parent(currentPath); - - if (!parent) { - break; - } - - const [parentNode, parentPath] = parent as NodeEntry; - - if (parentNode.type !== type) { - break; - } - - level += 1; - currentPath = parentPath; - } - - return level; - }, - - getLinks(editor: ReactEditor): string[] { - const marks = getAllMarks(editor); - - if (!marks) return []; - - return Object.entries(marks) - .filter(([key]) => key === 'href') - .map(([_, val]) => val as string); - }, - - extendLineBackward(editor: ReactEditor) { - Transforms.move(editor, { - unit: 'line', - edge: 'focus', - reverse: true, - }); - }, - - extendLineForward(editor: ReactEditor) { - Transforms.move(editor, { unit: 'line', edge: 'focus' }); - }, - - insertPlainText(editor: ReactEditor, text: string) { - const [appendText, ...lines] = text.split('\n'); - - editor.insertText(appendText); - lines.forEach((line) => { - editor.insertBreak(); - editor.insertText(line); - }); - }, - - highlight(editor: ReactEditor) { - addMark(editor, EditorMarkFormat.BgColor, 'appflowy_them_color_tint5'); - }, -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/command/mark.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/command/mark.ts deleted file mode 100644 index 649eaca564..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/command/mark.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { ReactEditor } from 'slate-react'; -import { Editor, Text, Range, Element } from 'slate'; -import { EditorInlineNodeType, EditorMarkFormat } from '$app/application/document/document.types'; -import { CustomEditor } from '$app/components/editor/command/index'; - -export function toggleMark( - editor: ReactEditor, - mark: { - key: EditorMarkFormat; - value: string | boolean; - } -) { - if (CustomEditor.selectionIncludeRoot(editor)) { - return; - } - - const { key, value } = mark; - - const isActive = isMarkActive(editor, key); - - if (isActive || !value) { - Editor.removeMark(editor, key as string); - } else if (value) { - Editor.addMark(editor, key as string, value); - } -} - -/** - * Check if the every text in the selection has the mark. - * @param editor - * @param format - */ -export function isMarkActive(editor: ReactEditor, format: EditorMarkFormat | EditorInlineNodeType) { - const selection = editor.selection; - - if (!selection) return false; - - const isExpanded = Range.isExpanded(selection); - - if (isExpanded) { - const texts = getSelectionTexts(editor); - - return texts.every((node) => { - const { text, ...attributes } = node; - - if (!text) return true; - return Boolean((attributes as Record)[format]); - }); - } - - const marks = Editor.marks(editor) as Record | null; - - return marks ? !!marks[format] : false; -} - -export function getSelectionTexts(editor: ReactEditor) { - const selection = editor.selection; - - if (!selection) return []; - - const texts: Text[] = []; - - const isExpanded = Range.isExpanded(selection); - - if (isExpanded) { - let anchor = Range.start(selection); - const focus = Range.end(selection); - const isEnd = Editor.isEnd(editor, anchor, anchor.path); - - if (isEnd) { - const after = Editor.after(editor, anchor); - - if (after) { - anchor = after; - } - } - - Array.from( - Editor.nodes(editor, { - at: { - anchor, - focus, - }, - }) - ).forEach((match) => { - const node = match[0] as Element; - - if (Text.isText(node)) { - texts.push(node); - } else if (Editor.isInline(editor, node)) { - texts.push(...(node.children as Text[])); - } - }); - } - - return texts; -} - -/** - * Get all marks in the current selection. - * @param editor - */ -export function getAllMarks(editor: ReactEditor) { - const selection = editor.selection; - - if (!selection) return null; - - const isExpanded = Range.isExpanded(selection); - - if (isExpanded) { - const texts = getSelectionTexts(editor); - - const marks: Record = {}; - - texts.forEach((node) => { - Object.entries(node).forEach(([key, value]) => { - if (key !== 'text') { - marks[key] = value; - } - }); - }); - - return marks; - } - - return Editor.marks(editor) as Record | null; -} - -export function removeMarks(editor: ReactEditor) { - const marks = getAllMarks(editor); - - if (!marks) return; - - for (const key in marks) { - Editor.removeMark(editor, key); - } -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/command/tab.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/command/tab.ts deleted file mode 100644 index 819596f92f..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/command/tab.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { Path, Element, NodeEntry } from 'slate'; -import { ReactEditor } from 'slate-react'; -import { EditorNodeType } from '$app/application/document/document.types'; -import { CustomEditor } from '$app/components/editor/command/index'; - -export const LIST_TYPES = [ - EditorNodeType.NumberedListBlock, - EditorNodeType.BulletedListBlock, - EditorNodeType.TodoListBlock, - EditorNodeType.ToggleListBlock, - EditorNodeType.QuoteBlock, - EditorNodeType.Paragraph, -]; - -/** - * Indent the current list item - * Conditions: - * 1. The current node must be a list item - * 2. The previous node must be a list - * 3. The previous node must be the same level as the current node - * Result: - * 1. The current node will be the child of the previous node - * 2. The current node will be indented - * 3. The children of the current node will be moved to the children of the previous node - * @param editor - */ -export function tabForward(editor: ReactEditor) { - const match = CustomEditor.getBlock(editor); - - if (!match) return; - - const [node, path] = match as NodeEntry; - - const hasPrevious = Path.hasPrevious(path); - - if (!hasPrevious) return; - - const previousPath = Path.previous(path); - - const previous = editor.node(previousPath); - const [previousNode] = previous as NodeEntry; - - if (!previousNode) return; - - const type = previousNode.type as EditorNodeType; - - if (type === EditorNodeType.Page) return; - // the previous node is not a list - if (!LIST_TYPES.includes(type)) return; - - const toPath = [...previousPath, previousNode.children.length]; - - editor.moveNodes({ - at: path, - to: toPath, - }); - - const length = node.children.length; - - for (let i = length - 1; i > 0; i--) { - editor.liftNodes({ - at: [...toPath, i], - }); - } -} - -export function tabBackward(editor: ReactEditor) { - const match = CustomEditor.getBlock(editor); - - if (!match) return; - - const [node, path] = match as NodeEntry; - - const depth = path.length; - - if (node.type === EditorNodeType.Page) return; - - if (depth === 1) return; - editor.liftNodes({ - at: path, - }); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/_shared/Placeholder.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/_shared/Placeholder.tsx deleted file mode 100644 index d9a60f09ad..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/_shared/Placeholder.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from 'react'; -import { Element } from 'slate'; -import PlaceholderContent from '$app/components/editor/components/blocks/_shared/PlaceholderContent'; - -function Placeholder({ node, isEmpty }: { node: Element; isEmpty: boolean }) { - if (!isEmpty) { - return null; - } - - return ; -} - -export default React.memo(Placeholder); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/_shared/PlaceholderContent.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/_shared/PlaceholderContent.tsx deleted file mode 100644 index 91645e0051..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/_shared/PlaceholderContent.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import React, { CSSProperties, useEffect, useMemo, useState } from 'react'; -import { ReactEditor, useSelected, useSlate } from 'slate-react'; -import { Editor, Element, Range } from 'slate'; -import { EditorNodeType, HeadingNode } from '$app/application/document/document.types'; -import { useTranslation } from 'react-i18next'; - -function PlaceholderContent({ node, ...attributes }: { node: Element; className?: string; style?: CSSProperties }) { - const { t } = useTranslation(); - const editor = useSlate(); - const selected = useSelected() && !!editor.selection && Range.isCollapsed(editor.selection); - const [isComposing, setIsComposing] = useState(false); - const block = useMemo(() => { - const path = ReactEditor.findPath(editor, node); - const match = Editor.above(editor, { - match: (n) => { - return !Editor.isEditor(n) && Element.isElement(n) && n.blockId !== undefined && n.type !== undefined; - }, - at: path, - }); - - if (!match) return null; - - return match[0] as Element; - }, [editor, node]); - - const className = useMemo(() => { - return `text-placeholder select-none ${attributes.className ?? ''}`; - }, [attributes.className]); - - const unSelectedPlaceholder = useMemo(() => { - switch (block?.type) { - case EditorNodeType.Paragraph: { - if (editor.children.length === 1) { - return t('editor.slashPlaceHolder'); - } - - return ''; - } - - case EditorNodeType.ToggleListBlock: - return t('blockPlaceholders.bulletList'); - case EditorNodeType.QuoteBlock: - return t('blockPlaceholders.quote'); - case EditorNodeType.TodoListBlock: - return t('blockPlaceholders.todoList'); - case EditorNodeType.NumberedListBlock: - return t('blockPlaceholders.numberList'); - case EditorNodeType.BulletedListBlock: - return t('blockPlaceholders.bulletList'); - case EditorNodeType.HeadingBlock: { - const level = (block as HeadingNode).data.level; - - switch (level) { - case 1: - return t('editor.mobileHeading1'); - case 2: - return t('editor.mobileHeading2'); - case 3: - return t('editor.mobileHeading3'); - default: - return ''; - } - } - - case EditorNodeType.Page: - return t('document.title.placeholder'); - case EditorNodeType.CalloutBlock: - case EditorNodeType.CodeBlock: - return t('editor.typeSomething'); - default: - return ''; - } - }, [block, t, editor.children.length]); - - const selectedPlaceholder = useMemo(() => { - switch (block?.type) { - case EditorNodeType.HeadingBlock: - return unSelectedPlaceholder; - case EditorNodeType.Page: - return t('document.title.placeholder'); - case EditorNodeType.GridBlock: - case EditorNodeType.EquationBlock: - case EditorNodeType.CodeBlock: - case EditorNodeType.DividerBlock: - return ''; - - default: - return t('editor.slashPlaceHolder'); - } - }, [block?.type, t, unSelectedPlaceholder]); - - useEffect(() => { - if (!selected) return; - - const handleCompositionStart = () => { - setIsComposing(true); - }; - - const handleCompositionEnd = () => { - setIsComposing(false); - }; - - const editorDom = ReactEditor.toDOMNode(editor, editor); - - // placeholder should be hidden when composing - editorDom.addEventListener('compositionstart', handleCompositionStart); - editorDom.addEventListener('compositionend', handleCompositionEnd); - editorDom.addEventListener('compositionupdate', handleCompositionStart); - return () => { - editorDom.removeEventListener('compositionstart', handleCompositionStart); - editorDom.removeEventListener('compositionend', handleCompositionEnd); - editorDom.removeEventListener('compositionupdate', handleCompositionStart); - }; - }, [editor, selected]); - - if (isComposing) { - return null; - } - - return ( - - ); -} - -export default PlaceholderContent; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/_shared/unSupportBlock.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/_shared/unSupportBlock.tsx deleted file mode 100644 index 9e9e4fcb38..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/_shared/unSupportBlock.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React, { forwardRef } from 'react'; -import { Alert } from '@mui/material'; - -export const UnSupportBlock = forwardRef((_, ref) => { - return ( -
- -
- ); -}); - -export default UnSupportBlock; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/bulleted_list/BulletedList.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/bulleted_list/BulletedList.tsx deleted file mode 100644 index 41fce1c9dc..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/bulleted_list/BulletedList.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React, { forwardRef, memo } from 'react'; -import { EditorElementProps, BulletedListNode } from '$app/application/document/document.types'; - -export const BulletedList = memo( - forwardRef>( - ({ node: _, children, className, ...attributes }, ref) => { - return ( -
- {children} -
- ); - } - ) -); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/bulleted_list/BulletedListIcon.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/bulleted_list/BulletedListIcon.tsx deleted file mode 100644 index ea0de80f55..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/bulleted_list/BulletedListIcon.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React, { useMemo } from 'react'; -import { BulletedListNode } from '$app/application/document/document.types'; -import { ReactEditor, useSlateStatic } from 'slate-react'; -import { CustomEditor } from '$app/components/editor/command'; - -enum Letter { - Disc, - Circle, - Square, -} - -function BulletedListIcon({ block, className }: { block: BulletedListNode; className: string }) { - const staticEditor = useSlateStatic(); - const path = ReactEditor.findPath(staticEditor, block); - - const letter = useMemo(() => { - const level = CustomEditor.getListLevel(staticEditor, block.type, path); - - if (level % 3 === 0) { - return Letter.Disc; - } else if (level % 3 === 1) { - return Letter.Circle; - } else { - return Letter.Square; - } - }, [block.type, staticEditor, path]); - - const dataLetter = useMemo(() => { - switch (letter) { - case Letter.Disc: - return '•'; - case Letter.Circle: - return '◦'; - case Letter.Square: - return '▪'; - } - }, [letter]); - - return ( - { - e.preventDefault(); - }} - data-letter={dataLetter} - contentEditable={false} - className={`${className} bulleted-icon flex min-w-[24px] justify-center pr-1 font-medium`} - /> - ); -} - -export default BulletedListIcon; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/bulleted_list/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/bulleted_list/index.ts deleted file mode 100644 index 2095dff308..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/bulleted_list/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './BulletedList'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/callout/Callout.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/callout/Callout.tsx deleted file mode 100644 index a20300bbc2..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/callout/Callout.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React, { forwardRef, memo } from 'react'; -import { EditorElementProps, CalloutNode } from '$app/application/document/document.types'; -import CalloutIcon from '$app/components/editor/components/blocks/callout/CalloutIcon'; - -export const Callout = memo( - forwardRef>(({ node, children, ...attributes }, ref) => { - return ( - <> -
- -
-
-
- {children} -
-
- - ); - }) -); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/callout/CalloutIcon.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/callout/CalloutIcon.tsx deleted file mode 100644 index e9bba448a7..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/callout/CalloutIcon.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import React, { useCallback, useRef, useState } from 'react'; -import { IconButton } from '@mui/material'; -import { CalloutNode } from '$app/application/document/document.types'; -import EmojiPicker from '$app/components/_shared/emoji_picker/EmojiPicker'; -import Popover from '@mui/material/Popover'; -import { PopoverCommonProps } from '$app/components/editor/components/tools/popover'; -import { ReactEditor, useSlateStatic } from 'slate-react'; -import { CustomEditor } from '$app/components/editor/command'; - -function CalloutIcon({ node }: { node: CalloutNode }) { - const ref = useRef(null); - const [open, setOpen] = useState(false); - const editor = useSlateStatic(); - - const handleClose = useCallback(() => { - setOpen(false); - const path = ReactEditor.findPath(editor, node); - - ReactEditor.focus(editor); - editor.select(path); - editor.collapse({ - edge: 'start', - }); - }, [editor, node]); - const handleEmojiSelect = useCallback( - (emoji: string) => { - CustomEditor.setCalloutIcon(editor, node, emoji); - handleClose(); - }, - [editor, node, handleClose] - ); - - return ( - <> - { - setOpen(true); - }} - className={`h-8 w-8 p-1`} - > - {node.data.icon} - - {open && ( - - - - )} - - ); -} - -export default React.memo(CalloutIcon); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/callout/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/callout/index.ts deleted file mode 100644 index 4ca74e4be8..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/callout/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Callout'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/code/Code.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/code/Code.hooks.ts deleted file mode 100644 index 0b043f4579..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/code/Code.hooks.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { useCallback } from 'react'; -import { ReactEditor, useSlateStatic } from 'slate-react'; -import { Element as SlateElement, Transforms } from 'slate'; -import { CodeNode } from '$app/application/document/document.types'; - -export function useCodeBlock(node: CodeNode) { - const language = node.data.language; - const editor = useSlateStatic() as ReactEditor; - const handleChangeLanguage = useCallback( - (newLang: string) => { - const path = ReactEditor.findPath(editor, node); - const newProperties = { - data: { - language: newLang, - }, - } as Partial; - - Transforms.setNodes(editor, newProperties, { at: path }); - }, - [editor, node] - ); - - return { - language, - handleChangeLanguage, - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/code/Code.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/code/Code.tsx deleted file mode 100644 index 7fe7b205f4..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/code/Code.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { forwardRef, memo, useCallback } from 'react'; -import { EditorElementProps, CodeNode } from '$app/application/document/document.types'; -import LanguageSelect from './SelectLanguage'; - -import { useCodeBlock } from '$app/components/editor/components/blocks/code/Code.hooks'; -import { ReactEditor, useSlateStatic } from 'slate-react'; - -export const Code = memo( - forwardRef>(({ node, children, ...attributes }, ref) => { - const { language, handleChangeLanguage } = useCodeBlock(node); - - const editor = useSlateStatic(); - const onBlur = useCallback(() => { - const path = ReactEditor.findPath(editor, node); - - ReactEditor.focus(editor); - editor.select(path); - editor.collapse({ - edge: 'start', - }); - }, [editor, node]); - - return ( - <> -
- -
-
-
-            {children}
-          
-
- - ); - }) -); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/code/SelectLanguage.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/code/SelectLanguage.tsx deleted file mode 100644 index 4805233e1d..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/code/SelectLanguage.tsx +++ /dev/null @@ -1,168 +0,0 @@ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { TextField, Popover } from '@mui/material'; -import { useTranslation } from 'react-i18next'; -import { supportLanguage } from './constants'; -import KeyboardNavigation, { - KeyboardNavigationOption, -} from '$app/components/_shared/keyboard_navigation/KeyboardNavigation'; -import usePopoverAutoPosition from '$app/components/_shared/popover/Popover.hooks'; -import { PopoverOrigin } from '@mui/material/Popover/Popover'; - -const initialOrigin: { - transformOrigin: PopoverOrigin; - anchorOrigin: PopoverOrigin; -} = { - transformOrigin: { - vertical: 'top', - horizontal: 'left', - }, - anchorOrigin: { - vertical: 'bottom', - horizontal: 'left', - }, -}; - -function SelectLanguage({ - language = 'json', - onChangeLanguage, - onBlur, -}: { - language: string; - onChangeLanguage: (language: string) => void; - onBlur?: () => void; -}) { - const { t } = useTranslation(); - const ref = useRef(null); - const [open, setOpen] = useState(false); - const [search, setSearch] = useState(''); - - const searchRef = useRef(null); - const scrollRef = useRef(null); - const options: KeyboardNavigationOption[] = useMemo(() => { - return supportLanguage - .map((item) => ({ - key: item.id, - content: item.title, - })) - .filter((item) => { - return item.content?.toLowerCase().includes(search.toLowerCase()); - }); - }, [search]); - - const handleClose = useCallback(() => { - setOpen(false); - setSearch(''); - }, []); - - const handleConfirm = useCallback( - (key: string) => { - onChangeLanguage(key); - handleClose(); - }, - [onChangeLanguage, handleClose] - ); - - useEffect(() => { - const element = ref.current; - - if (!element) return; - const handleKeyDown = (e: KeyboardEvent) => { - e.stopPropagation(); - e.preventDefault(); - - if (e.key === 'Enter') { - setOpen(true); - return; - } - - onBlur?.(); - }; - - element.addEventListener('keydown', handleKeyDown); - - return () => { - element.removeEventListener('keydown', handleKeyDown); - }; - }, [onBlur]); - - const { paperHeight, transformOrigin, anchorOrigin, isEntered } = usePopoverAutoPosition({ - initialPaperWidth: 200, - initialPaperHeight: 220, - anchorEl: ref.current, - initialAnchorOrigin: initialOrigin.anchorOrigin, - initialTransformOrigin: initialOrigin.transformOrigin, - open, - }); - - return ( - <> - { - setOpen(true); - }} - InputProps={{ - readOnly: true, - }} - placeholder={t('document.codeBlock.language.placeholder')} - label={t('document.codeBlock.language.label')} - /> - - {open && ( - -
- setSearch(e.target.value)} - size={'small'} - autoFocus={true} - variant={'standard'} - className={'px-2 text-xs'} - placeholder={t('search.label')} - /> -
- -
-
-
- )} - - ); -} - -export default SelectLanguage; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/code/constants.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/code/constants.ts deleted file mode 100644 index dee71624db..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/code/constants.ts +++ /dev/null @@ -1,154 +0,0 @@ -export const supportLanguage = [ - { - id: 'bash', - title: 'Bash', - }, - { - id: 'basic', - title: 'Basic', - }, - { - id: 'c', - title: 'C', - }, - { - id: 'clojure', - title: 'Clojure', - }, - { - id: 'cpp', - title: 'C++', - }, - { - id: 'cs', - title: 'CS', - }, - { - id: 'css', - title: 'CSS', - }, - { - id: 'dart', - title: 'Dart', - }, - { - id: 'elixir', - title: 'Elixir', - }, - { - id: 'elm', - title: 'Elm', - }, - { - id: 'erlang', - title: 'Erlang', - }, - { - id: 'fortran', - title: 'Fortran', - }, - { - id: 'go', - title: 'Go', - }, - { - id: 'graphql', - title: 'GraphQL', - }, - { - id: 'haskell', - title: 'Haskell', - }, - { - id: 'java', - title: 'Java', - }, - { - id: 'javascript', - title: 'JavaScript', - }, - { - id: 'json', - title: 'JSON', - }, - { - id: 'kotlin', - title: 'Kotlin', - }, - { - id: 'lisp', - title: 'Lisp', - }, - { - id: 'lua', - title: 'Lua', - }, - { - id: 'markdown', - title: 'Markdown', - }, - { - id: 'matlab', - title: 'Matlab', - }, - { - id: 'ocaml', - title: 'OCaml', - }, - { - id: 'perl', - title: 'Perl', - }, - { - id: 'php', - title: 'PHP', - }, - { - id: 'powershell', - title: 'Powershell', - }, - { - id: 'python', - title: 'Python', - }, - { - id: 'r', - title: 'R', - }, - { - id: 'ruby', - title: 'Ruby', - }, - { - id: 'rust', - title: 'Rust', - }, - { - id: 'scala', - title: 'Scala', - }, - { - id: 'shell', - title: 'Shell', - }, - { - id: 'sql', - title: 'SQL', - }, - { - id: 'swift', - title: 'Swift', - }, - { - id: 'typescript', - title: 'TypeScript', - }, - { - id: 'xml', - title: 'XML', - }, - { - id: 'yaml', - title: 'YAML', - }, -]; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/code/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/code/index.ts deleted file mode 100644 index c3aa9443d1..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/code/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Code'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/code/utils.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/code/utils.ts deleted file mode 100644 index 52eeebc8c4..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/code/utils.ts +++ /dev/null @@ -1,132 +0,0 @@ -import Prism from 'prismjs'; - -import 'prismjs/components/prism-bash'; -import 'prismjs/components/prism-basic'; -import 'prismjs/components/prism-c'; -import 'prismjs/components/prism-clojure'; -import 'prismjs/components/prism-cpp'; -import 'prismjs/components/prism-csp'; -import 'prismjs/components/prism-css'; -import 'prismjs/components/prism-dart'; -import 'prismjs/components/prism-elixir'; -import 'prismjs/components/prism-elm'; -import 'prismjs/components/prism-erlang'; -import 'prismjs/components/prism-fortran'; -import 'prismjs/components/prism-go'; -import 'prismjs/components/prism-graphql'; -import 'prismjs/components/prism-haskell'; -import 'prismjs/components/prism-java'; -import 'prismjs/components/prism-javascript'; -import 'prismjs/components/prism-json'; -import 'prismjs/components/prism-kotlin'; -import 'prismjs/components/prism-lisp'; -import 'prismjs/components/prism-lua'; -import 'prismjs/components/prism-markdown'; -import 'prismjs/components/prism-matlab'; -import 'prismjs/components/prism-ocaml'; -import 'prismjs/components/prism-perl'; -import 'prismjs/components/prism-php'; -import 'prismjs/components/prism-powershell'; -import 'prismjs/components/prism-python'; -import 'prismjs/components/prism-r'; -import 'prismjs/components/prism-ruby'; -import 'prismjs/components/prism-rust'; -import 'prismjs/components/prism-scala'; -import 'prismjs/components/prism-shell-session'; -import 'prismjs/components/prism-sql'; -import 'prismjs/components/prism-swift'; -import 'prismjs/components/prism-typescript'; -import 'prismjs/components/prism-xml-doc'; -import 'prismjs/components/prism-yaml'; - -import { BaseRange, NodeEntry, Text, Path } from 'slate'; - -const push_string = ( - token: string | Prism.Token, - path: Path, - start: number, - ranges: BaseRange[], - token_type = 'text' -) => { - let newStart = start; - - ranges.push({ - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - prism_token: token_type, - anchor: { path, offset: newStart }, - focus: { path, offset: newStart + token.length }, - }); - newStart += token.length; - return newStart; -}; - -// This recurses through the Prism.tokenizes result and creates stylized ranges based on the token type -const recurseTokenize = ( - token: string | Prism.Token, - path: Path, - ranges: BaseRange[], - start: number, - parent_tag?: string -) => { - // Uses the parent's token type if a Token only has a string as its content - if (typeof token === 'string') { - return push_string(token, path, start, ranges, parent_tag); - } - - if ('content' in token) { - if (token.content instanceof Array) { - // Calls recurseTokenize on nested Tokens in content - let newStart = start; - - for (const subToken of token.content) { - newStart = recurseTokenize(subToken, path, ranges, newStart, token.type) || 0; - } - - return newStart; - } - - return push_string(token.content, path, start, ranges, token.type); - } -}; - -function switchCodeTheme(isDark: boolean) { - const link = document.getElementById('prism-css'); - - if (link) { - document.head.removeChild(link); - } - - const newLink = document.createElement('link'); - - newLink.rel = 'stylesheet'; - newLink.href = isDark - ? 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/themes/prism-dark.min.css' - : 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/themes/prism.min.css'; - newLink.id = 'prism-css'; - document.head.appendChild(newLink); -} - -export const decorateCode = ([node, path]: NodeEntry, language: string, isDark: boolean) => { - switchCodeTheme(isDark); - - const ranges: BaseRange[] = []; - - if (!Text.isText(node)) { - return ranges; - } - - try { - const tokens = Prism.tokenize(node.text, Prism.languages[language]); - - let start = 0; - - for (const token of tokens) { - start = recurseTokenize(token, path, ranges, start) || 0; - } - - return ranges; - } catch { - return ranges; - } -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/database/DatabaseEmpty.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/database/DatabaseEmpty.tsx deleted file mode 100644 index a0f50016e1..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/database/DatabaseEmpty.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React, { useCallback, useRef } from 'react'; -import CreateNewFolderIcon from '@mui/icons-material/CreateNewFolder'; - -import { GridNode } from '$app/application/document/document.types'; -import { useTranslation } from 'react-i18next'; - -import Drawer from '$app/components/editor/components/blocks/database/Drawer'; - -function DatabaseEmpty({ node }: { node: GridNode }) { - const { t } = useTranslation(); - const ref = useRef(null); - - const [open, setOpen] = React.useState(false); - - const toggleDrawer = useCallback((open: boolean) => { - return (e: React.MouseEvent | KeyboardEvent | React.FocusEvent) => { - e.stopPropagation(); - setOpen(open); - }; - }, []); - - return ( -
- -
{t('document.plugins.database.noDataSource')}
-
- - {t('document.plugins.database.selectADataSource')} - - {t('document.plugins.database.toContinue')} -
- - -
- ); -} - -export default React.memo(DatabaseEmpty); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/database/DatabaseList.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/database/DatabaseList.hooks.ts deleted file mode 100644 index 543b9900ca..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/database/DatabaseList.hooks.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { useAppSelector } from '$app/stores/store'; -import { ViewLayoutPB } from '@/services/backend'; - -export function useLoadDatabaseList({ searchText, layout }: { searchText: string; layout: ViewLayoutPB }) { - const list = useAppSelector((state) => { - const workspaces = state.workspace.workspaces.map((item) => item.id) ?? []; - - return Object.values(state.pages.pageMap).filter((page) => { - if (page.layout !== layout) return false; - const parentId = page.parentId; - - if (!parentId) return false; - - const parent = state.pages.pageMap[parentId]; - const parentLayout = parent?.layout; - - if (!workspaces.includes(parentId) && parentLayout !== ViewLayoutPB.Document) return false; - - return page.name.toLowerCase().includes(searchText.toLowerCase()); - }); - }); - - return { - list, - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/database/DatabaseList.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/database/DatabaseList.tsx deleted file mode 100644 index 5d06a13c06..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/database/DatabaseList.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import React, { useCallback, useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; -import { TextField } from '@mui/material'; -import { useLoadDatabaseList } from '$app/components/editor/components/blocks/database/DatabaseList.hooks'; -import { ViewLayoutPB } from '@/services/backend'; -import { ReactComponent as GridSvg } from '$app/assets/grid.svg'; -import { useSlateStatic } from 'slate-react'; -import { CustomEditor } from '$app/components/editor/command'; -import { GridNode } from '$app/application/document/document.types'; -import KeyboardNavigation, { - KeyboardNavigationOption, -} from '$app/components/_shared/keyboard_navigation/KeyboardNavigation'; -import { Page } from '$app_reducers/pages/slice'; - -function DatabaseList({ - node, - toggleDrawer, -}: { - node: GridNode; - toggleDrawer: (open: boolean) => (e: React.MouseEvent | KeyboardEvent | React.FocusEvent) => void; -}) { - const scrollRef = React.useRef(null); - - const inputRef = React.useRef(null); - const editor = useSlateStatic(); - const { t } = useTranslation(); - const [searchText, setSearchText] = React.useState(''); - const { list } = useLoadDatabaseList({ - searchText: searchText || '', - layout: ViewLayoutPB.Grid, - }); - - const renderItem = useCallback( - (item: Page) => { - return ( -
- -
{item.name.trim() || t('menuAppHeader.defaultNewPageName')}
-
- ); - }, - [t] - ); - - const options: KeyboardNavigationOption[] = useMemo(() => { - return list.map((item) => { - return { - key: item.id, - content: renderItem(item), - }; - }); - }, [list, renderItem]); - - const handleSelected = useCallback( - (id: string) => { - CustomEditor.setGridBlockViewId(editor, node, id); - }, - [editor, node] - ); - - const handleKeyDown = useCallback( - (e: KeyboardEvent) => { - if (e.key === 'Escape') { - e.stopPropagation(); - e.preventDefault(); - toggleDrawer(false)(e); - } - }, - [toggleDrawer] - ); - - return ( -
- { - setSearchText((e.currentTarget as HTMLInputElement).value); - }} - inputProps={{ - className: 'py-2 text-sm', - }} - placeholder={t('document.plugins.database.linkToDatabase')} - /> -
- -
-
- ); -} - -export default DatabaseList; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/database/Drawer.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/database/Drawer.tsx deleted file mode 100644 index 54c0005027..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/database/Drawer.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import React, { useCallback } from 'react'; -import { Button, IconButton } from '@mui/material'; -import { useTranslation } from 'react-i18next'; -import { createGrid } from '$app/components/editor/components/blocks/database/utils'; -import { CustomEditor } from '$app/components/editor/command'; -import { useSlateStatic } from 'slate-react'; -import { useEditorId } from '$app/components/editor/Editor.hooks'; -import { GridNode } from '$app/application/document/document.types'; -import { ReactComponent as CloseSvg } from '$app/assets/close.svg'; -import { ReactComponent as AddSvg } from '$app/assets/add.svg'; -import DatabaseList from '$app/components/editor/components/blocks/database/DatabaseList'; - -function Drawer({ - open, - toggleDrawer, - node, -}: { - open: boolean; - toggleDrawer: (open: boolean) => (e: React.MouseEvent | KeyboardEvent | React.FocusEvent) => void; - node: GridNode; -}) { - const editor = useSlateStatic(); - const id = useEditorId(); - const { t } = useTranslation(); - const handleCreateGrid = useCallback(async () => { - const gridId = await createGrid(id); - - CustomEditor.setGridBlockViewId(editor, node, gridId); - }, [id, editor, node]); - - return ( -
{ - e.stopPropagation(); - }} - className={'absolute right-0 top-0 h-full transform overflow-hidden'} - style={{ - width: open ? '250px' : '0px', - transition: 'width 0.3s ease-in-out', - }} - onMouseDown={(e) => { - const isInput = (e.target as HTMLElement).closest('input'); - - if (isInput) return; - e.stopPropagation(); - e.preventDefault(); - }} - > -
-
-
{t('document.plugins.database.selectDataSource')}
- - - -
-
- {open && } -
- -
- -
-
-
- ); -} - -export default Drawer; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/database/GridBlock.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/database/GridBlock.tsx deleted file mode 100644 index 936da9c2c8..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/database/GridBlock.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React, { forwardRef, memo } from 'react'; -import { EditorElementProps, GridNode } from '$app/application/document/document.types'; - -import GridView from '$app/components/editor/components/blocks/database/GridView'; -import DatabaseEmpty from '$app/components/editor/components/blocks/database/DatabaseEmpty'; -import { useSelected } from 'slate-react'; - -export const GridBlock = memo( - forwardRef>(({ node, children, className = '', ...attributes }, ref) => { - const viewId = node.data.viewId; - const selected = useSelected(); - - return ( -
-
- {children} -
-
- {viewId ? : } -
-
- ); - }) -); - -export default GridBlock; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/database/GridView.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/database/GridView.tsx deleted file mode 100644 index 695482bbd8..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/database/GridView.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { Database, DatabaseRenderedProvider } from '$app/components/database'; -import { ViewIdProvider } from '$app/hooks'; - -function GridView({ viewId }: { viewId: string }) { - const [selectedViewId, onChangeSelectedViewId] = useState(viewId); - - const ref = useRef(null); - - const [rendered, setRendered] = useState<{ viewId: string; rendered: boolean } | undefined>(undefined); - - // delegate wheel event to layout when grid is scrolled to top or bottom - useEffect(() => { - const element = ref.current; - - const viewId = rendered?.viewId; - - if (!viewId || !element) { - return; - } - - const gridScroller = element.querySelector(`[data-view-id="${viewId}"] .grid-scroll-container`) as HTMLDivElement; - - const scrollLayout = gridScroller?.closest('.appflowy-scroll-container') as HTMLDivElement; - - if (!gridScroller || !scrollLayout) { - return; - } - - const onWheel = (event: WheelEvent) => { - const deltaY = event.deltaY; - const deltaX = event.deltaX; - - if (Math.abs(deltaX) > 8) { - return; - } - - const { scrollTop, scrollHeight, clientHeight } = gridScroller; - - const atTop = deltaY < 0 && scrollTop === 0; - const atBottom = deltaY > 0 && scrollTop + clientHeight >= scrollHeight; - - // if at top or bottom, prevent default to allow layout to scroll - if (atTop || atBottom) { - scrollLayout.scrollTop += deltaY; - } - }; - - gridScroller.addEventListener('wheel', onWheel, { passive: false }); - return () => { - gridScroller.removeEventListener('wheel', onWheel); - }; - }, [rendered]); - - const onRendered = useCallback((viewId: string) => { - setRendered({ - viewId, - rendered: true, - }); - }, []); - - return ( - - - - - - ); -} - -export default React.memo(GridView); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/database/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/database/index.ts deleted file mode 100644 index 986343f9df..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/database/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './GridBlock'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/database/utils.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/database/utils.ts deleted file mode 100644 index 032502b415..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/database/utils.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ViewLayoutPB } from '@/services/backend'; -import { createPage } from '$app/application/folder/page.service'; - -export async function createGrid(pageId: string) { - const newViewId = await createPage({ - layout: ViewLayoutPB.Grid, - name: '', - parent_view_id: pageId, - }); - - return newViewId; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/divider/DividerNode.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/divider/DividerNode.tsx deleted file mode 100644 index d7d475199b..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/divider/DividerNode.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React, { forwardRef, memo, useMemo } from 'react'; -import { EditorElementProps, DividerNode as DividerNodeType } from '$app/application/document/document.types'; -import { useSelected } from 'slate-react'; - -export const DividerNode = memo( - forwardRef>( - ({ node: _node, children: children, ...attributes }, ref) => { - const selected = useSelected(); - - const className = useMemo(() => { - return `${attributes.className ?? ''} divider-node relative w-full rounded ${ - selected ? 'bg-content-blue-100' : '' - }`; - }, [attributes.className, selected]); - - return ( -
-
-
-
-
- {children} -
-
- ); - } - ) -); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/divider/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/divider/index.ts deleted file mode 100644 index 8f6141749a..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/divider/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './DividerNode'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/heading/Heading.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/heading/Heading.tsx deleted file mode 100644 index 4d23069c46..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/heading/Heading.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React, { forwardRef, memo } from 'react'; -import { EditorElementProps, HeadingNode } from '$app/application/document/document.types'; -import { getHeadingCssProperty } from '$app/components/editor/plugins/utils'; - -export const Heading = memo( - forwardRef>(({ node, children, ...attributes }, ref) => { - const level = node.data.level; - const fontSizeCssProperty = getHeadingCssProperty(level); - - const className = `${attributes.className ?? ''} ${fontSizeCssProperty}`; - - return ( -
- {children} -
- ); - }) -); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/heading/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/heading/index.ts deleted file mode 100644 index 6406e7b07f..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/heading/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Heading'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/image/ImageActions.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/image/ImageActions.tsx deleted file mode 100644 index b3d3575af2..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/image/ImageActions.tsx +++ /dev/null @@ -1,163 +0,0 @@ -import React, { useMemo, useState } from 'react'; -import { ImageNode } from '$app/application/document/document.types'; -import { ReactComponent as CopyIcon } from '$app/assets/copy.svg'; -import { ReactComponent as AlignLeftIcon } from '$app/assets/align-left.svg'; -import { ReactComponent as AlignCenterIcon } from '$app/assets/align-center.svg'; -import { ReactComponent as AlignRightIcon } from '$app/assets/align-right.svg'; -import { ReactComponent as DeleteIcon } from '$app/assets/delete.svg'; -import { useTranslation } from 'react-i18next'; -import { IconButton } from '@mui/material'; -import { notify } from '$app/components/_shared/notify'; -import { CustomEditor } from '$app/components/editor/command'; -import { useSlateStatic } from 'slate-react'; -import Popover from '@mui/material/Popover'; -import Tooltip from '@mui/material/Tooltip'; - -enum ImageAction { - Copy = 'copy', - AlignLeft = 'left', - AlignCenter = 'center', - AlignRight = 'right', - Delete = 'delete', -} - -function ImageActions({ node }: { node: ImageNode }) { - const { t } = useTranslation(); - const align = node.data.align; - const editor = useSlateStatic(); - const [alignAnchorEl, setAlignAnchorEl] = useState(null); - const alignOptions = useMemo(() => { - return [ - { - key: ImageAction.AlignLeft, - Icon: AlignLeftIcon, - onClick: () => { - CustomEditor.setImageBlockData(editor, node, { align: 'left' }); - setAlignAnchorEl(null); - }, - }, - { - key: ImageAction.AlignCenter, - Icon: AlignCenterIcon, - onClick: () => { - CustomEditor.setImageBlockData(editor, node, { align: 'center' }); - setAlignAnchorEl(null); - }, - }, - { - key: ImageAction.AlignRight, - Icon: AlignRightIcon, - onClick: () => { - CustomEditor.setImageBlockData(editor, node, { align: 'right' }); - setAlignAnchorEl(null); - }, - }, - ]; - }, [editor, node]); - const options = useMemo(() => { - return [ - { - key: ImageAction.Copy, - Icon: CopyIcon, - tooltip: t('button.copyLink'), - onClick: () => { - if (!node.data.url) return; - void navigator.clipboard.writeText(node.data.url); - notify.success(t('message.copy.success')); - }, - }, - (!align || align === 'left') && { - key: ImageAction.AlignLeft, - Icon: AlignLeftIcon, - tooltip: t('button.align'), - onClick: (e: React.MouseEvent) => { - setAlignAnchorEl(e.currentTarget); - }, - }, - align === 'center' && { - key: ImageAction.AlignCenter, - Icon: AlignCenterIcon, - tooltip: t('button.align'), - onClick: (e: React.MouseEvent) => { - setAlignAnchorEl(e.currentTarget); - }, - }, - align === 'right' && { - key: ImageAction.AlignRight, - Icon: AlignRightIcon, - tooltip: t('button.align'), - onClick: (e: React.MouseEvent) => { - setAlignAnchorEl(e.currentTarget); - }, - }, - { - key: ImageAction.Delete, - Icon: DeleteIcon, - tooltip: t('button.delete'), - onClick: () => { - CustomEditor.deleteNode(editor, node); - }, - }, - ].filter(Boolean) as { - key: ImageAction; - Icon: React.FC>; - tooltip: string; - onClick: (e: React.MouseEvent) => void; - }[]; - }, [align, node, t, editor]); - - return ( -
- {options.map((option) => { - const { key, Icon, tooltip, onClick } = option; - - return ( - - - - - - ); - })} - {!!alignAnchorEl && ( - setAlignAnchorEl(null)} - > - {alignOptions.map((option) => { - const { key, Icon, onClick } = option; - - return ( - - - - ); - })} - - )} -
- ); -} - -export default ImageActions; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/image/ImageBlock.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/image/ImageBlock.tsx deleted file mode 100644 index 661eb3e3de..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/image/ImageBlock.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React, { forwardRef, memo, useCallback, useMemo, useRef } from 'react'; -import { EditorElementProps, ImageNode } from '$app/application/document/document.types'; -import { ReactEditor, useSelected, useSlateStatic } from 'slate-react'; -import ImageRender from '$app/components/editor/components/blocks/image/ImageRender'; -import ImageEmpty from '$app/components/editor/components/blocks/image/ImageEmpty'; - -export const ImageBlock = memo( - forwardRef>(({ node, children, className, ...attributes }, ref) => { - const selected = useSelected(); - const { url, align } = useMemo(() => node.data || {}, [node.data]); - const containerRef = useRef(null); - const editor = useSlateStatic(); - const onFocusNode = useCallback(() => { - ReactEditor.focus(editor); - const path = ReactEditor.findPath(editor, node); - - editor.select(path); - }, [editor, node]); - - return ( -
{ - if (!selected) onFocusNode(); - }} - className={`${className} image-block relative w-full cursor-pointer py-1`} - > -
- {children} -
-
- {url ? ( - - ) : ( - - )} -
-
- ); - }) -); - -export default ImageBlock; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/image/ImageEmpty.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/image/ImageEmpty.tsx deleted file mode 100644 index e0b649939e..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/image/ImageEmpty.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React, { useEffect } from 'react'; -import { ReactComponent as ImageIcon } from '$app/assets/image.svg'; -import { useTranslation } from 'react-i18next'; -import UploadPopover from '$app/components/editor/components/blocks/image/UploadPopover'; -import { EditorNodeType, ImageNode } from '$app/application/document/document.types'; -import { useEditorBlockDispatch, useEditorBlockState } from '$app/components/editor/stores/block'; - -function ImageEmpty({ - containerRef, - onEscape, - node, -}: { - containerRef: React.RefObject; - onEscape: () => void; - node: ImageNode; -}) { - const { t } = useTranslation(); - const state = useEditorBlockState(EditorNodeType.ImageBlock); - const open = Boolean(state?.popoverOpen && state?.blockId === node.blockId && containerRef.current); - const { openPopover, closePopover } = useEditorBlockDispatch(); - - useEffect(() => { - const container = containerRef.current; - - if (!container) { - return; - } - - const handleClick = () => { - openPopover(EditorNodeType.ImageBlock, node.blockId); - }; - - container.addEventListener('click', handleClick); - return () => { - container.removeEventListener('click', handleClick); - }; - }, [containerRef, node.blockId, openPopover]); - return ( - <> -
- - {t('document.plugins.image.addAnImage')} -
- {open && ( - { - closePopover(EditorNodeType.ImageBlock); - onEscape(); - }} - /> - )} - - ); -} - -export default ImageEmpty; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/image/ImageRender.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/image/ImageRender.tsx deleted file mode 100644 index 07310b05be..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/image/ImageRender.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { ImageNode, ImageType } from '$app/application/document/document.types'; -import { useTranslation } from 'react-i18next'; -import { CircularProgress } from '@mui/material'; -import { ErrorOutline } from '@mui/icons-material'; -import ImageResizer from '$app/components/editor/components/blocks/image/ImageResizer'; -import { CustomEditor } from '$app/components/editor/command'; -import { useSlateStatic } from 'slate-react'; -import ImageActions from '$app/components/editor/components/blocks/image/ImageActions'; -import { LocalImage } from '$app/components/_shared/image_upload'; -import debounce from 'lodash-es/debounce'; - -const MIN_WIDTH = 100; - -const DELAY = 300; - -function ImageRender({ selected, node }: { selected: boolean; node: ImageNode }) { - const [loading, setLoading] = useState(true); - const [hasError, setHasError] = useState(false); - - const imgRef = useRef(null); - const editor = useSlateStatic(); - const { url = '', width: imageWidth, image_type: source } = useMemo(() => node.data || {}, [node.data]); - const { t } = useTranslation(); - const blockId = node.blockId; - - const [showActions, setShowActions] = useState(false); - const [initialWidth, setInitialWidth] = useState(null); - const [newWidth, setNewWidth] = useState(imageWidth ?? null); - - const debounceSubmitWidth = useMemo(() => { - return debounce((newWidth: number) => { - CustomEditor.setImageBlockData(editor, node, { - width: newWidth, - }); - }, DELAY); - }, [editor, node]); - - const handleWidthChange = useCallback( - (newWidth: number) => { - setNewWidth(newWidth); - debounceSubmitWidth(newWidth); - }, - [debounceSubmitWidth] - ); - - useEffect(() => { - if (!loading && !hasError && initialWidth === null && imgRef.current) { - setInitialWidth(imgRef.current.offsetWidth); - } - }, [hasError, initialWidth, loading]); - const imageProps: React.ImgHTMLAttributes = useMemo(() => { - return { - style: { width: loading || hasError ? '0' : newWidth ?? '100%', opacity: selected ? 0.8 : 1 }, - className: 'object-cover', - ref: imgRef, - src: url, - draggable: false, - onLoad: () => { - setHasError(false); - setLoading(false); - }, - onError: () => { - setHasError(true); - setLoading(false); - }, - }; - }, [url, newWidth, loading, hasError, selected]); - - const renderErrorNode = useCallback(() => { - return ( -
- -
{t('editor.imageLoadFailed')}
-
- ); - }, [t]); - - if (!url) return null; - - return ( -
{ - setShowActions(true); - }} - onMouseLeave={() => { - setShowActions(false); - }} - style={{ - minWidth: MIN_WIDTH, - width: 'fit-content', - }} - className={`image-render relative min-h-[48px] ${ - hasError || (loading && source !== ImageType.Local) ? 'w-full' : '' - }`} - > - {source === ImageType.Local ? ( - { - setHasError(true); - return null; - }} - loading={'lazy'} - /> - ) : ( - {`image-${blockId}`} - )} - - {initialWidth && ( - <> - - - - )} - {showActions && } - {hasError ? ( - renderErrorNode() - ) : loading && source !== ImageType.Local ? ( -
- -
{t('editor.loading')}
-
- ) : null} -
- ); -} - -export default ImageRender; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/image/ImageResizer.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/image/ImageResizer.tsx deleted file mode 100644 index e0d272acf3..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/image/ImageResizer.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import React, { useCallback, useRef } from 'react'; - -function ImageResizer({ - minWidth, - width, - onWidthChange, - isLeft, -}: { - isLeft?: boolean; - minWidth: number; - width: number; - onWidthChange: (newWidth: number) => void; -}) { - const originalWidth = useRef(width); - const startX = useRef(0); - - const onResize = useCallback( - (e: MouseEvent) => { - e.preventDefault(); - const diff = isLeft ? startX.current - e.clientX : e.clientX - startX.current; - const newWidth = originalWidth.current + diff; - - if (newWidth < minWidth) { - return; - } - - onWidthChange(newWidth); - }, - [isLeft, minWidth, onWidthChange] - ); - - const onResizeEnd = useCallback(() => { - document.removeEventListener('mousemove', onResize); - document.removeEventListener('mouseup', onResizeEnd); - }, [onResize]); - - const onResizeStart = useCallback( - (e: React.MouseEvent) => { - startX.current = e.clientX; - originalWidth.current = width; - document.addEventListener('mousemove', onResize); - document.addEventListener('mouseup', onResizeEnd); - }, - [onResize, onResizeEnd, width] - ); - - return ( -
-
-
- ); -} - -export default ImageResizer; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/image/UploadPopover.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/image/UploadPopover.tsx deleted file mode 100644 index 0aff9fb0cc..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/image/UploadPopover.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import React, { useMemo } from 'react'; -import { PopoverOrigin } from '@mui/material/Popover/Popover'; -import usePopoverAutoPosition from '$app/components/_shared/popover/Popover.hooks'; - -import { useTranslation } from 'react-i18next'; -import { EmbedLink, Unsplash, UploadTabs, TabOption, TAB_KEY, UploadImage } from '$app/components/_shared/image_upload'; -import { CustomEditor } from '$app/components/editor/command'; -import { useSlateStatic } from 'slate-react'; -import { ImageNode, ImageType } from '$app/application/document/document.types'; - -const initialOrigin: { - transformOrigin: PopoverOrigin; - anchorOrigin: PopoverOrigin; -} = { - transformOrigin: { - vertical: 'top', - horizontal: 'center', - }, - anchorOrigin: { - vertical: 'bottom', - horizontal: 'center', - }, -}; - -function UploadPopover({ - open, - anchorEl, - onClose, - node, -}: { - open: boolean; - anchorEl: HTMLDivElement | null; - onClose: () => void; - node: ImageNode; -}) { - const editor = useSlateStatic(); - - const { t } = useTranslation(); - - const { transformOrigin, anchorOrigin, isEntered, paperHeight, paperWidth } = usePopoverAutoPosition({ - initialPaperWidth: 433, - initialPaperHeight: 300, - anchorEl, - initialAnchorOrigin: initialOrigin.anchorOrigin, - initialTransformOrigin: initialOrigin.transformOrigin, - open, - }); - - const tabOptions: TabOption[] = useMemo(() => { - return [ - { - label: t('button.upload'), - key: TAB_KEY.UPLOAD, - Component: UploadImage, - onDone: (link: string) => { - CustomEditor.setImageBlockData(editor, node, { - url: link, - image_type: ImageType.Local, - }); - onClose(); - }, - }, - { - label: t('document.imageBlock.embedLink.label'), - key: TAB_KEY.EMBED_LINK, - Component: EmbedLink, - onDone: (link: string) => { - CustomEditor.setImageBlockData(editor, node, { - url: link, - image_type: ImageType.External, - }); - onClose(); - }, - }, - { - key: TAB_KEY.UNSPLASH, - label: t('document.imageBlock.unsplash.label'), - Component: Unsplash, - onDone: (link: string) => { - CustomEditor.setImageBlockData(editor, node, { - url: link, - image_type: ImageType.External, - }); - onClose(); - }, - }, - ]; - }, [editor, node, onClose, t]); - - return ( - { - e.stopPropagation(); - }, - }} - containerStyle={{ - maxWidth: paperWidth, - maxHeight: paperHeight, - overflow: 'hidden', - }} - tabOptions={tabOptions} - /> - ); -} - -export default UploadPopover; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/image/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/image/index.ts deleted file mode 100644 index 73c3003a92..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/image/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ImageBlock'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/math_equation/EditPopover.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/math_equation/EditPopover.tsx deleted file mode 100644 index f44158bdf2..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/math_equation/EditPopover.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import React, { useCallback, useEffect, useState } from 'react'; -import Popover from '@mui/material/Popover'; -import { PopoverCommonProps } from '$app/components/editor/components/tools/popover'; -import { TextareaAutosize } from '@mui/material'; -import Button from '@mui/material/Button'; -import { useTranslation } from 'react-i18next'; -import { CustomEditor } from '$app/components/editor/command'; -import { ReactEditor, useSlateStatic } from 'slate-react'; -import { MathEquationNode } from '$app/application/document/document.types'; -import katex from 'katex'; -import { PopoverOrigin } from '@mui/material/Popover/Popover'; -import usePopoverAutoPosition from '$app/components/_shared/popover/Popover.hooks'; - -const initialOrigin: { - transformOrigin: PopoverOrigin; - anchorOrigin: PopoverOrigin; -} = { - transformOrigin: { - vertical: 'top', - horizontal: 'center', - }, - anchorOrigin: { - vertical: 'bottom', - horizontal: 'center', - }, -}; - -function EditPopover({ - open, - anchorEl, - onClose, - node, -}: { - open: boolean; - node: MathEquationNode; - anchorEl: HTMLDivElement | null; - onClose: () => void; -}) { - const editor = useSlateStatic(); - - const [error, setError] = useState<{ - name: string; - message: string; - } | null>(null); - const { t } = useTranslation(); - const [value, setValue] = useState(node.data.formula || ''); - const onInput = (event: React.FormEvent) => { - setValue(event.currentTarget.value); - }; - - const handleClose = useCallback(() => { - onClose(); - if (!node) return; - ReactEditor.focus(editor); - const path = ReactEditor.findPath(editor, node); - - editor.select(path); - }, [onClose, editor, node]); - - const handleDone = () => { - if (!node || error) return; - if (value !== node.data.formula) { - CustomEditor.setMathEquationBlockFormula(editor, node, value); - } - - handleClose(); - }; - - const onKeyDown = (e: React.KeyboardEvent) => { - e.stopPropagation(); - const shift = e.shiftKey; - - // If shift is pressed, allow the user to enter a new line, otherwise close the popover - if (!shift && e.key === 'Enter') { - e.preventDefault(); - e.stopPropagation(); - handleDone(); - } - - if (e.key === 'Escape') { - e.preventDefault(); - e.stopPropagation(); - handleClose(); - } - }; - - useEffect(() => { - try { - katex.render(value, document.createElement('div')); - setError(null); - } catch (e) { - setError( - e as { - name: string; - message: string; - } - ); - } - }, [value]); - - const { transformOrigin, anchorOrigin, isEntered } = usePopoverAutoPosition({ - initialPaperWidth: 300, - initialPaperHeight: 170, - anchorEl, - initialAnchorOrigin: initialOrigin.anchorOrigin, - initialTransformOrigin: initialOrigin.transformOrigin, - open, - }); - - return ( - { - e.stopPropagation(); - }} - onKeyDown={onKeyDown} - > -
- - - {error && ( -
- {error.name}: {error.message} -
- )} - -
- - -
-
-
- ); -} - -export default EditPopover; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/math_equation/MathEquation.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/math_equation/MathEquation.tsx deleted file mode 100644 index ee441be624..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/math_equation/MathEquation.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import { forwardRef, memo, useEffect, useRef } from 'react'; -import { EditorElementProps, EditorNodeType, MathEquationNode } from '$app/application/document/document.types'; -import KatexMath from '$app/components/_shared/katex_math/KatexMath'; -import { useTranslation } from 'react-i18next'; -import { FunctionsOutlined } from '@mui/icons-material'; -import EditPopover from '$app/components/editor/components/blocks/math_equation/EditPopover'; -import { ReactEditor, useSelected, useSlateStatic } from 'slate-react'; -import { useEditorBlockDispatch, useEditorBlockState } from '$app/components/editor/stores/block'; - -export const MathEquation = memo( - forwardRef>( - ({ node, children, className, ...attributes }, ref) => { - const formula = node.data.formula; - const { t } = useTranslation(); - const containerRef = useRef(null); - const { openPopover, closePopover } = useEditorBlockDispatch(); - const state = useEditorBlockState(EditorNodeType.EquationBlock); - const open = Boolean(state?.popoverOpen && state?.blockId === node.blockId && containerRef.current); - - const selected = useSelected(); - - const editor = useSlateStatic(); - - useEffect(() => { - const slateDom = ReactEditor.toDOMNode(editor, editor); - - if (!slateDom) return; - const handleKeyDown = (e: KeyboardEvent) => { - if (e.key === 'Enter') { - e.preventDefault(); - e.stopPropagation(); - openPopover(EditorNodeType.EquationBlock, node.blockId); - } - }; - - if (selected) { - slateDom.addEventListener('keydown', handleKeyDown); - } - - return () => { - slateDom.removeEventListener('keydown', handleKeyDown); - }; - }, [editor, node.blockId, openPopover, selected]); - - return ( - <> -
{ - openPopover(EditorNodeType.EquationBlock, node.blockId); - }} - className={`${className} math-equation-block relative w-full cursor-pointer py-2`} - > -
- {formula ? ( - - ) : ( -
- - {t('document.plugins.mathEquation.addMathEquation')} -
- )} -
-
- {children} -
-
- {open && ( - { - closePopover(EditorNodeType.EquationBlock); - }} - node={node} - open={open} - anchorEl={containerRef.current} - /> - )} - - ); - } - ) -); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/math_equation/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/math_equation/index.ts deleted file mode 100644 index ae6eb70209..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/math_equation/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './MathEquation'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/numbered_list/NumberListIcon.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/numbered_list/NumberListIcon.tsx deleted file mode 100644 index 888b46c980..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/numbered_list/NumberListIcon.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import React, { useMemo } from 'react'; -import { ReactEditor, useSlate, useSlateStatic } from 'slate-react'; -import { Element, Path } from 'slate'; -import { NumberedListNode } from '$app/application/document/document.types'; -import { letterize, romanize } from '$app/utils/list'; -import { CustomEditor } from '$app/components/editor/command'; - -enum Letter { - Number = 'number', - Letter = 'letter', - Roman = 'roman', -} - -function getLetterNumber(index: number, letter: Letter) { - if (letter === Letter.Number) { - return index; - } else if (letter === Letter.Letter) { - return letterize(index); - } else { - return romanize(index); - } -} - -function NumberListIcon({ block, className }: { block: NumberedListNode; className: string }) { - const editor = useSlate(); - const staticEditor = useSlateStatic(); - - const path = ReactEditor.findPath(editor, block); - const index = useMemo(() => { - let index = 1; - - let topNode; - let prevPath = Path.previous(path); - - while (prevPath) { - const prev = editor.node(prevPath); - - const prevNode = prev[0] as Element; - - if (prevNode.type === block.type) { - index += 1; - topNode = prevNode; - } else { - break; - } - - prevPath = Path.previous(prevPath); - } - - if (!topNode) { - return Number(block.data?.number ?? 1); - } - - const startIndex = (topNode as NumberedListNode).data?.number ?? 1; - - return index + Number(startIndex) - 1; - }, [editor, block, path]); - - const letter = useMemo(() => { - const level = CustomEditor.getListLevel(staticEditor, block.type, path); - - if (level % 3 === 0) { - return Letter.Number; - } else if (level % 3 === 1) { - return Letter.Letter; - } else { - return Letter.Roman; - } - }, [block.type, staticEditor, path]); - - const dataNumber = useMemo(() => { - return getLetterNumber(index, letter); - }, [index, letter]); - - return ( - { - e.preventDefault(); - }} - contentEditable={false} - data-number={dataNumber} - className={`${className} numbered-icon flex w-[24px] min-w-[24px] justify-center pr-1 font-medium`} - /> - ); -} - -export default NumberListIcon; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/numbered_list/NumberedList.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/numbered_list/NumberedList.tsx deleted file mode 100644 index f3e34e1571..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/numbered_list/NumberedList.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React, { forwardRef, memo } from 'react'; -import { EditorElementProps, NumberedListNode } from '$app/application/document/document.types'; - -export const NumberedList = memo( - forwardRef>( - ({ node: _, children, className, ...attributes }, ref) => { - return ( -
- {children} -
- ); - } - ) -); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/numbered_list/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/numbered_list/index.ts deleted file mode 100644 index 6e985ae25b..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/numbered_list/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './NumberedList'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/page/Page.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/page/Page.tsx deleted file mode 100644 index f93cb897ba..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/page/Page.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React, { forwardRef, memo, useMemo } from 'react'; -import { EditorElementProps, PageNode } from '$app/application/document/document.types'; - -export const Page = memo( - forwardRef>(({ node: _, children, ...attributes }, ref) => { - const className = useMemo(() => { - return `${attributes.className ?? ''} document-title pb-3 text-5xl font-bold`; - }, [attributes.className]); - - return ( -
- {children} -
- ); - }) -); - -export default Page; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/page/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/page/index.ts deleted file mode 100644 index d9925d7520..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/page/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Page'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/paragraph/Paragraph.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/paragraph/Paragraph.tsx deleted file mode 100644 index 96524db239..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/paragraph/Paragraph.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React, { forwardRef, memo } from 'react'; -import { EditorElementProps, ParagraphNode } from '$app/application/document/document.types'; - -export const Paragraph = memo( - forwardRef>(({ node: _, children, ...attributes }, ref) => { - { - return ( -
- {children} -
- ); - } - }) -); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/paragraph/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/paragraph/index.ts deleted file mode 100644 index 01752c914c..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/paragraph/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Paragraph'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/quote/Quote.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/quote/Quote.tsx deleted file mode 100644 index 5afc35289b..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/quote/Quote.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React, { forwardRef, memo, useMemo } from 'react'; -import { EditorElementProps, QuoteNode } from '$app/application/document/document.types'; - -export const QuoteList = memo( - forwardRef>(({ node: _, children, ...attributes }, ref) => { - const className = useMemo(() => { - return `flex w-full flex-col ml-3 border-l-[4px] border-fill-default pl-2 ${attributes.className ?? ''}`; - }, [attributes.className]); - - return ( -
- {children} -
- ); - }) -); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/quote/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/quote/index.ts deleted file mode 100644 index c88e677a53..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/quote/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Quote'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/text/StartIcon.hooks.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/text/StartIcon.hooks.tsx deleted file mode 100644 index acf16581f4..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/text/StartIcon.hooks.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import React, { FC, useCallback, useMemo } from 'react'; -import { EditorNodeType, TextNode } from '$app/application/document/document.types'; -import { ReactEditor, useSlate } from 'slate-react'; -import { Editor, Element } from 'slate'; -import CheckboxIcon from '$app/components/editor/components/blocks/todo_list/CheckboxIcon'; -import ToggleIcon from '$app/components/editor/components/blocks/toggle_list/ToggleIcon'; -import NumberListIcon from '$app/components/editor/components/blocks/numbered_list/NumberListIcon'; -import BulletedListIcon from '$app/components/editor/components/blocks/bulleted_list/BulletedListIcon'; - -export function useStartIcon(node: TextNode) { - const editor = useSlate(); - const path = ReactEditor.findPath(editor, node); - const block = Editor.parent(editor, path)?.[0] as Element | null; - - const Component = useMemo(() => { - if (!Element.isElement(block)) { - return null; - } - - switch (block.type) { - case EditorNodeType.TodoListBlock: - return CheckboxIcon; - case EditorNodeType.ToggleListBlock: - return ToggleIcon; - case EditorNodeType.NumberedListBlock: - return NumberListIcon; - case EditorNodeType.BulletedListBlock: - return BulletedListIcon; - default: - return null; - } - }, [block]) as FC<{ block: Element; className: string }> | null; - - const renderIcon = useCallback(() => { - if (!Component || !block) { - return null; - } - - return ; - }, [Component, block]); - - return { - hasStartIcon: !!Component, - renderIcon, - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/text/Text.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/text/Text.tsx deleted file mode 100644 index 768524394e..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/text/Text.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React, { forwardRef, memo } from 'react'; -import Placeholder from '$app/components/editor/components/blocks/_shared/Placeholder'; -import { EditorElementProps, TextNode } from '$app/application/document/document.types'; -import { useSlateStatic } from 'slate-react'; -import { useStartIcon } from '$app/components/editor/components/blocks/text/StartIcon.hooks'; - -export const Text = memo( - forwardRef>(({ node, children, className, ...attributes }, ref) => { - const editor = useSlateStatic(); - const { hasStartIcon, renderIcon } = useStartIcon(node); - const isEmpty = editor.isEmpty(node); - - return ( - - {renderIcon()} - - {children} - - ); - }) -); - -export default Text; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/text/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/text/index.ts deleted file mode 100644 index b0c76af0b0..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/text/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Text'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/todo_list/CheckboxIcon.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/todo_list/CheckboxIcon.tsx deleted file mode 100644 index d98990c886..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/todo_list/CheckboxIcon.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import React, { useCallback } from 'react'; -import { TodoListNode } from '$app/application/document/document.types'; -import { CustomEditor } from '$app/components/editor/command'; -import { ReactEditor, useSlateStatic } from 'slate-react'; -import { Location } from 'slate'; -import { ReactComponent as CheckboxCheckSvg } from '$app/assets/database/checkbox-check.svg'; -import { ReactComponent as CheckboxUncheckSvg } from '$app/assets/database/checkbox-uncheck.svg'; - -function CheckboxIcon({ block, className }: { block: TodoListNode; className: string }) { - const editor = useSlateStatic(); - const { checked } = block.data; - - const toggleTodo = useCallback( - (e: React.MouseEvent) => { - const path = ReactEditor.findPath(editor, block); - const start = editor.start(path); - let at: Location = start; - - if (e.shiftKey) { - const end = editor.end(path); - - at = { - anchor: start, - focus: end, - }; - } - - CustomEditor.toggleTodo(editor, at); - }, - [editor, block] - ); - - return ( - { - e.preventDefault(); - }} - className={`${className} cursor-pointer pr-1 text-xl text-fill-default`} - > - {checked ? : } - - ); -} - -export default CheckboxIcon; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/todo_list/TodoList.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/todo_list/TodoList.tsx deleted file mode 100644 index c662c48153..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/todo_list/TodoList.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React, { forwardRef, memo, useMemo } from 'react'; -import { EditorElementProps, TodoListNode } from '$app/application/document/document.types'; - -export const TodoList = memo( - forwardRef>(({ node, children, ...attributes }, ref) => { - const { checked = false } = useMemo(() => node.data || {}, [node.data]); - const className = useMemo(() => { - return `flex w-full flex-col ${checked ? 'checked' : ''} ${attributes.className ?? ''}`; - }, [attributes.className, checked]); - - return ( - <> -
- {children} -
- - ); - }) -); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/todo_list/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/todo_list/index.ts deleted file mode 100644 index f239f43459..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/todo_list/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './TodoList'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/toggle_list/ToggleIcon.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/toggle_list/ToggleIcon.tsx deleted file mode 100644 index ad27822cb5..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/toggle_list/ToggleIcon.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React, { useCallback } from 'react'; -import { CustomEditor } from '$app/components/editor/command'; -import { useSlateStatic } from 'slate-react'; -import { ToggleListNode } from '$app/application/document/document.types'; -import { ReactComponent as RightSvg } from '$app/assets/more.svg'; - -function ToggleIcon({ block, className }: { block: ToggleListNode; className: string }) { - const editor = useSlateStatic(); - const { collapsed } = block.data; - - const toggleToggleList = useCallback(() => { - CustomEditor.toggleToggleList(editor, block); - }, [editor, block]); - - return ( - { - e.preventDefault(); - }} - className={`${className} cursor-pointer pr-1 text-xl hover:text-fill-default`} - > - {collapsed ? : } - - ); -} - -export default ToggleIcon; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/toggle_list/ToggleList.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/toggle_list/ToggleList.tsx deleted file mode 100644 index 809f3b750d..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/toggle_list/ToggleList.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React, { forwardRef, memo, useMemo } from 'react'; -import { EditorElementProps, ToggleListNode } from '$app/application/document/document.types'; - -export const ToggleList = memo( - forwardRef>(({ node, children, ...attributes }, ref) => { - const { collapsed } = useMemo(() => node.data || {}, [node.data]); - const className = `${attributes.className ?? ''} flex w-full flex-col ${collapsed ? 'collapsed' : ''}`; - - return ( - <> -
- {children} -
- - ); - }) -); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/toggle_list/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/toggle_list/index.ts deleted file mode 100644 index 833bdb5210..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/blocks/toggle_list/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ToggleList'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/editor/CollaborativeEditor.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/editor/CollaborativeEditor.tsx deleted file mode 100644 index 2526df895e..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/editor/CollaborativeEditor.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { memo, useEffect, useMemo, useState } from 'react'; - -import Editor from '$app/components/editor/components/editor/Editor'; -import { EditorProps } from '$app/application/document/document.types'; -import { Provider } from '$app/components/editor/provider'; -import { YXmlText } from 'yjs/dist/src/types/YXmlText'; -import { getInsertTarget, getYTarget } from '$app/components/editor/provider/utils/relation'; -import isEqual from 'lodash-es/isEqual'; - -export const CollaborativeEditor = memo( - ({ id, title, cover, showTitle = true, onTitleChange, onCoverChange, ...props }: EditorProps) => { - const [sharedType, setSharedType] = useState(null); - const provider = useMemo(() => { - setSharedType(null); - - return new Provider(id); - }, [id]); - - const root = useMemo(() => { - if (!showTitle || !sharedType || !sharedType.doc) return null; - - return getYTarget(sharedType?.doc, [0]); - }, [sharedType, showTitle]); - - const rootText = useMemo(() => { - if (!root) return null; - return getInsertTarget(root, [0]); - }, [root]); - - useEffect(() => { - if (!rootText || rootText.toString() === title) return; - - if (rootText.length > 0) { - rootText.delete(0, rootText.length); - } - - rootText.insert(0, title || ''); - }, [title, rootText]); - - useEffect(() => { - if (!root) return; - - const originalCover = root.getAttribute('data')?.cover; - - if (cover === undefined) return; - if (isEqual(originalCover, cover)) return; - root.setAttribute('data', { cover: cover ? cover : undefined }); - }, [cover, root]); - - useEffect(() => { - if (!root) return; - const rootId = root.getAttribute('blockId'); - - if (!rootId) return; - - const getCover = () => { - const data = root.getAttribute('data'); - - onCoverChange?.(data?.cover); - }; - - getCover(); - const onChange = () => { - onTitleChange?.(root.toString()); - getCover(); - }; - - root.observeDeep(onChange); - return () => root.unobserveDeep(onChange); - }, [onTitleChange, root, onCoverChange]); - - useEffect(() => { - provider.connect(); - - const handleConnected = () => { - setSharedType(provider.sharedType); - }; - - provider.on('ready', handleConnected); - void provider.initialDocument(showTitle); - return () => { - provider.off('ready', handleConnected); - provider.disconnect(); - }; - }, [provider, showTitle]); - - if (!sharedType || id !== provider.id) { - return null; - } - - return ; - } -); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/editor/CustomEditable.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/editor/CustomEditable.tsx deleted file mode 100644 index b0bbe0eb28..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/editor/CustomEditable.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React, { ComponentProps, useCallback } from 'react'; -import { Editable, useSlate } from 'slate-react'; -import Element from './Element'; -import { Leaf } from './Leaf'; -import { useShortcuts } from '$app/components/editor/plugins/shortcuts'; -import { useInlineKeyDown } from '$app/components/editor/components/editor/Editor.hooks'; - -type CustomEditableProps = Omit, 'renderElement' | 'renderLeaf'> & - Partial, 'renderElement' | 'renderLeaf'>> & { - disableFocus?: boolean; - }; - -export function CustomEditable({ - renderElement = Element, - disableFocus = false, - renderLeaf = Leaf, - ...props -}: CustomEditableProps) { - const editor = useSlate(); - const { onKeyDown: onShortcutsKeyDown } = useShortcuts(editor); - const withInlineKeyDown = useInlineKeyDown(editor); - const onKeyDown = useCallback( - (event: React.KeyboardEvent) => { - withInlineKeyDown(event); - onShortcutsKeyDown(event); - }, - [onShortcutsKeyDown, withInlineKeyDown] - ); - - return ( - - ); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/editor/Editor.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/editor/Editor.hooks.ts deleted file mode 100644 index f2443ba44b..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/editor/Editor.hooks.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { KeyboardEvent, useCallback, useEffect, useMemo } from 'react'; - -import { BaseRange, createEditor, Editor, Element, NodeEntry, Range, Transforms } from 'slate'; -import { ReactEditor, withReact } from 'slate-react'; -import { withBlockPlugins } from '$app/components/editor/plugins/withBlockPlugins'; -import { withInlines } from '$app/components/editor/components/inline_nodes'; -import { withYHistory, withYjs, YjsEditor } from '@slate-yjs/core'; -import * as Y from 'yjs'; -import { CustomEditor } from '$app/components/editor/command'; -import { CodeNode, EditorNodeType } from '$app/application/document/document.types'; -import { decorateCode } from '$app/components/editor/components/blocks/code/utils'; -import { withMarkdown } from '$app/components/editor/plugins/shortcuts'; -import { createHotkey, HOT_KEY_NAME } from '$app/utils/hotkeys'; - -export function useEditor(sharedType: Y.XmlText) { - const editor = useMemo(() => { - if (!sharedType) return null; - const e = withMarkdown(withBlockPlugins(withInlines(withReact(withYHistory(withYjs(createEditor(), sharedType)))))); - - // Ensure editor always has at least 1 valid child - const { normalizeNode } = e; - - e.normalizeNode = (entry) => { - const [node] = entry; - - if (!Editor.isEditor(node) || node.children.length > 0) { - return normalizeNode(entry); - } - - // Ensure editor always has at least 1 valid child - CustomEditor.insertEmptyLineAtEnd(e as ReactEditor & YjsEditor); - }; - - return e; - }, [sharedType]) as ReactEditor & YjsEditor; - - const initialValue = useMemo(() => { - return []; - }, []); - - // Connect editor in useEffect to comply with concurrent mode requirements. - useEffect(() => { - YjsEditor.connect(editor); - return () => { - YjsEditor.disconnect(editor); - }; - }, [editor]); - - const handleOnClickEnd = useCallback(() => { - const path = [editor.children.length - 1]; - const node = Editor.node(editor, path) as NodeEntry; - const latestNodeIsEmpty = CustomEditor.isEmptyText(editor, node[0]); - - if (latestNodeIsEmpty) { - ReactEditor.focus(editor); - editor.select(path); - editor.collapse({ - edge: 'end', - }); - - return; - } - - CustomEditor.insertEmptyLineAtEnd(editor); - }, [editor]); - - return { - editor, - initialValue, - handleOnClickEnd, - }; -} - -export function useDecorateCodeHighlight(editor: ReactEditor) { - return useCallback( - (entry: NodeEntry): BaseRange[] => { - const path = entry[1]; - - const blockEntry = editor.above({ - at: path, - match: (n) => !Editor.isEditor(n) && Element.isElement(n) && n.blockId !== undefined, - }); - - if (!blockEntry) return []; - - const block = blockEntry[0] as CodeNode; - - if (block.type === EditorNodeType.CodeBlock) { - const language = block.data.language; - - return decorateCode(entry, language, false); - } - - return []; - }, - [editor] - ); -} - -export function useInlineKeyDown(editor: ReactEditor) { - return useCallback( - (e: KeyboardEvent) => { - const selection = editor.selection; - - // Default left/right behavior is unit:'character'. - // This fails to distinguish between two cursor positions, such as - // foo vs foo. - // Here we modify the behavior to unit:'offset'. - // This lets the user step into and out of the inline without stepping over characters. - // You may wish to customize this further to only use unit:'offset' in specific cases. - if (selection && Range.isCollapsed(selection)) { - const { nativeEvent } = e; - - if ( - createHotkey(HOT_KEY_NAME.LEFT)(nativeEvent) && - CustomEditor.beforeIsInlineNode(editor, selection, { - unit: 'offset', - }) - ) { - e.preventDefault(); - Transforms.move(editor, { unit: 'offset', reverse: true }); - return; - } - - if ( - createHotkey(HOT_KEY_NAME.RIGHT)(nativeEvent) && - CustomEditor.afterIsInlineNode(editor, selection, { unit: 'offset' }) - ) { - e.preventDefault(); - Transforms.move(editor, { unit: 'offset' }); - return; - } - } - }, - [editor] - ); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/editor/Editor.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/editor/Editor.tsx deleted file mode 100644 index d87dbe3f35..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/editor/Editor.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import React, { useCallback } from 'react'; -import { useDecorateCodeHighlight, useEditor } from '$app/components/editor/components/editor/Editor.hooks'; -import { Slate } from 'slate-react'; -import { CustomEditable } from '$app/components/editor/components/editor/CustomEditable'; -import { SelectionToolbar } from '$app/components/editor/components/tools/selection_toolbar'; -import { BlockActionsToolbar } from '$app/components/editor/components/tools/block_actions'; - -import { CircularProgress } from '@mui/material'; -import { NodeEntry } from 'slate'; -import { - DecorateStateProvider, - EditorSelectedBlockProvider, - useInitialEditorState, - SlashStateProvider, - EditorInlineBlockStateProvider, -} from '$app/components/editor/stores'; -import CommandPanel from '../tools/command_panel/CommandPanel'; -import { EditorBlockStateProvider } from '$app/components/editor/stores/block'; -import { LocalEditorProps } from '$app/application/document/document.types'; - -function Editor({ sharedType, disableFocus, caretColor = 'var(--text-title)' }: LocalEditorProps) { - const { editor, initialValue, handleOnClickEnd, ...props } = useEditor(sharedType); - const decorateCodeHighlight = useDecorateCodeHighlight(editor); - - const { - selectedBlocks, - decorate: decorateCustomRange, - decorateState, - slashState, - inlineBlockState, - blockState, - } = useInitialEditorState(editor); - - const decorate = useCallback( - (entry: NodeEntry) => { - const codeRanges = decorateCodeHighlight(entry); - const customRanges = decorateCustomRange(entry); - - return [...codeRanges, ...customRanges]; - }, - [decorateCodeHighlight, decorateCustomRange] - ); - - if (editor.sharedRoot.length === 0) { - return ; - } - - return ( - - - - - - - - - - - -
- - - - - - - ); -} - -export default Editor; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/editor/Element.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/editor/Element.hooks.ts deleted file mode 100644 index bf7045705d..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/editor/Element.hooks.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Element } from 'slate'; -import { useContext, useEffect, useMemo } from 'react'; -import { useSnapshot } from 'valtio'; -import { useSelected } from 'slate-react'; - -import { EditorSelectedBlockContext } from '$app/components/editor/stores/selected'; - -export function useElementState(element: Element) { - const blockId = element.blockId; - const selectedBlockContext = useContext(EditorSelectedBlockContext); - const selected = useSelected(); - - useEffect(() => { - if (!blockId) return; - - if (!selected) { - selectedBlockContext.delete(blockId); - } - }, [blockId, selected, selectedBlockContext]); - - const selectedBlockIds = useSnapshot(selectedBlockContext); - const blockSelected = useMemo(() => { - if (!blockId) return false; - return selectedBlockIds.has(blockId); - }, [blockId, selectedBlockIds]); - - return { - blockSelected, - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/editor/Element.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/editor/Element.tsx deleted file mode 100644 index 1824d8a590..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/editor/Element.tsx +++ /dev/null @@ -1,132 +0,0 @@ -import React, { FC, HTMLAttributes, useMemo } from 'react'; -import { RenderElementProps, useSlateStatic } from 'slate-react'; -import { - BlockData, - EditorElementProps, - EditorInlineNodeType, - EditorNodeType, - TextNode, -} from '$app/application/document/document.types'; -import { Paragraph } from '$app/components/editor/components/blocks/paragraph'; -import { Heading } from '$app/components/editor/components/blocks/heading'; -import { TodoList } from '$app/components/editor/components/blocks/todo_list'; -import { Code } from '$app/components/editor/components/blocks/code'; -import { QuoteList } from '$app/components/editor/components/blocks/quote'; -import { NumberedList } from '$app/components/editor/components/blocks/numbered_list'; -import { BulletedList } from '$app/components/editor/components/blocks/bulleted_list'; -import { DividerNode } from '$app/components/editor/components/blocks/divider'; -import { InlineFormula } from '$app/components/editor/components/inline_nodes/inline_formula'; -import { ToggleList } from '$app/components/editor/components/blocks/toggle_list'; -import { Callout } from '$app/components/editor/components/blocks/callout'; -import { Mention } from '$app/components/editor/components/inline_nodes/mention'; -import { GridBlock } from '$app/components/editor/components/blocks/database'; -import { MathEquation } from '$app/components/editor/components/blocks/math_equation'; -import { ImageBlock } from '$app/components/editor/components/blocks/image'; - -import { Text as TextComponent } from '../blocks/text'; -import { Page } from '../blocks/page'; -import { useElementState } from '$app/components/editor/components/editor/Element.hooks'; -import UnSupportBlock from '$app/components/editor/components/blocks/_shared/unSupportBlock'; -import { renderColor } from '$app/utils/color'; - -function Element({ element, attributes, children }: RenderElementProps) { - const node = element; - - const InlineComponent = useMemo(() => { - switch (node.type) { - case EditorInlineNodeType.Formula: - return InlineFormula; - case EditorInlineNodeType.Mention: - return Mention; - default: - return null; - } - }, [node.type]) as FC; - - const Component = useMemo(() => { - switch (node.type) { - case EditorNodeType.Page: - return Page; - case EditorNodeType.HeadingBlock: - return Heading; - case EditorNodeType.TodoListBlock: - return TodoList; - case EditorNodeType.Paragraph: - return Paragraph; - case EditorNodeType.CodeBlock: - return Code; - case EditorNodeType.QuoteBlock: - return QuoteList; - case EditorNodeType.NumberedListBlock: - return NumberedList; - case EditorNodeType.BulletedListBlock: - return BulletedList; - case EditorNodeType.DividerBlock: - return DividerNode; - case EditorNodeType.ToggleListBlock: - return ToggleList; - case EditorNodeType.CalloutBlock: - return Callout; - case EditorNodeType.GridBlock: - return GridBlock; - case EditorNodeType.EquationBlock: - return MathEquation; - case EditorNodeType.ImageBlock: - return ImageBlock; - default: - return UnSupportBlock; - } - }, [node.type]) as FC>; - - const editor = useSlateStatic(); - const { blockSelected } = useElementState(node); - const isEmbed = editor.isEmbed(node); - - const className = useMemo(() => { - const align = - ( - node.data as { - align: 'left' | 'center' | 'right'; - } - )?.align || 'left'; - - return `block-element flex rounded ${align ? `block-align-${align}` : ''} ${ - blockSelected && !isEmbed ? 'bg-content-blue-100' : '' - }`; - }, [node.data, blockSelected, isEmbed]); - - const style = useMemo(() => { - const data = (node.data as BlockData) || {}; - - return { - backgroundColor: data.bg_color ? renderColor(data.bg_color) : undefined, - color: data.font_color ? renderColor(data.font_color) : undefined, - }; - }, [node.data]); - - if (InlineComponent) { - return ( - - {children} - - ); - } - - if (node.type === EditorNodeType.Text) { - return ( - - {children} - - ); - } - - return ( -
- - {children} - -
- ); -} - -export default Element; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/editor/Leaf.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/editor/Leaf.tsx deleted file mode 100644 index 188ac33361..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/editor/Leaf.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import React, { CSSProperties } from 'react'; -import { RenderLeafProps } from 'slate-react'; -import { Link } from '$app/components/editor/components/inline_nodes/link'; -import { renderColor } from '$app/utils/color'; - -export function Leaf({ attributes, children, leaf }: RenderLeafProps) { - let newChildren = children; - - const classList = [leaf.prism_token, leaf.prism_token && 'token', leaf.class_name].filter(Boolean); - - if (leaf.code) { - newChildren = ( - - {newChildren} - - ); - } - - if (leaf.underline) { - newChildren = {newChildren}; - } - - if (leaf.strikethrough) { - newChildren = {newChildren}; - } - - if (leaf.italic) { - newChildren = {newChildren}; - } - - if (leaf.bold) { - newChildren = {newChildren}; - } - - const style: CSSProperties = {}; - - if (leaf.font_color) { - style['color'] = renderColor(leaf.font_color); - } - - if (leaf.bg_color) { - style['backgroundColor'] = renderColor(leaf.bg_color); - } - - if (leaf.href) { - newChildren = {newChildren}; - } - - return ( - - {newChildren} - - ); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/editor/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/editor/index.ts deleted file mode 100644 index c0c3c728d1..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/editor/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './CollaborativeEditor'; -export * from './Editor'; -export { useDecorateCodeHighlight } from '$app/components/editor/components/editor/Editor.hooks'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/editor/utils.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/editor/utils.ts deleted file mode 100644 index cc6960cdf4..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/editor/utils.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { BasePoint, Editor, Transforms } from 'slate'; -import { ReactEditor } from 'slate-react'; - -export function getNodePath(editor: ReactEditor, target: HTMLElement) { - const slateNode = ReactEditor.toSlateNode(editor, target); - const path = ReactEditor.findPath(editor, slateNode); - - return path; -} - -export function moveCursorToNodeEnd(editor: ReactEditor, target: HTMLElement) { - const path = getNodePath(editor, target); - const afterPath = Editor.after(editor, path); - - ReactEditor.focus(editor); - - if (afterPath) { - const afterStart = Editor.start(editor, afterPath); - - moveCursorToPoint(editor, afterStart); - } else { - const beforeEnd = Editor.end(editor, path); - - moveCursorToPoint(editor, beforeEnd); - } -} - -export function moveCursorToPoint(editor: ReactEditor, point: BasePoint) { - ReactEditor.focus(editor); - Transforms.select(editor, point); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/InlineChromiumBugfix.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/InlineChromiumBugfix.tsx deleted file mode 100644 index fb32eb18a9..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/InlineChromiumBugfix.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; - -// Put this at the start and end of an inline component to work around this Chromium bug: -// https://bugs.chromium.org/p/chromium/issues/detail?id=1249405 - -export const InlineChromiumBugfix = ({ className }: { className?: string }) => ( - - {String.fromCodePoint(160) /* Non-breaking space */} - -); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/index.ts deleted file mode 100644 index 29f27984f7..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './withInline'; -export * from './inline_formula'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/inline_formula/FormulaEditPopover.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/inline_formula/FormulaEditPopover.tsx deleted file mode 100644 index c60d7af40e..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/inline_formula/FormulaEditPopover.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import React, { useState } from 'react'; - -import Popover from '@mui/material/Popover'; -import { PopoverCommonProps } from '$app/components/editor/components/tools/popover'; -import { useTranslation } from 'react-i18next'; -import TextField from '@mui/material/TextField'; -import { IconButton } from '@mui/material'; -import { ReactComponent as SelectCheck } from '$app/assets/select-check.svg'; -import { ReactComponent as Clear } from '$app/assets/delete.svg'; -import Tooltip from '@mui/material/Tooltip'; - -function FormulaEditPopover({ - defaultText, - open, - anchorEl, - onClose, - onDone, - onClear, -}: { - defaultText: string; - open: boolean; - anchorEl: HTMLElement | null; - onClose: () => void; - onClear: () => void; - onDone: (formula: string) => void; -}) { - const [text, setText] = useState(defaultText); - const { t } = useTranslation(); - - return ( - -
- setText(e.target.value)} - fullWidth={true} - onKeyDown={(e) => { - e.stopPropagation(); - if (e.key === 'Enter') { - e.preventDefault(); - onDone(text); - } - - if (e.key === 'Escape') { - e.preventDefault(); - onClose(); - } - - if (e.key === 'Tab') { - e.preventDefault(); - } - }} - /> - - onDone(text)}> - - - - - - - - -
-
- ); -} - -export default FormulaEditPopover; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/inline_formula/FormulaLeaf.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/inline_formula/FormulaLeaf.tsx deleted file mode 100644 index 324d273ab3..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/inline_formula/FormulaLeaf.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import KatexMath from '$app/components/_shared/katex_math/KatexMath'; - -function FormulaLeaf({ formula, children }: { formula: string; children: React.ReactNode }) { - return ( - - - - - - {children} - - ); -} - -export default FormulaLeaf; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/inline_formula/InlineFormula.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/inline_formula/InlineFormula.tsx deleted file mode 100644 index 204047304f..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/inline_formula/InlineFormula.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import React, { forwardRef, memo, useCallback, MouseEvent, useRef, useEffect } from 'react'; -import { ReactEditor, useSelected, useSlate } from 'slate-react'; -import { Editor, Range, Transforms } from 'slate'; -import { EditorElementProps, FormulaNode } from '$app/application/document/document.types'; -import FormulaLeaf from '$app/components/editor/components/inline_nodes/inline_formula/FormulaLeaf'; -import FormulaEditPopover from '$app/components/editor/components/inline_nodes/inline_formula/FormulaEditPopover'; -import { getNodePath, moveCursorToNodeEnd } from '$app/components/editor/components/editor/utils'; -import { CustomEditor } from '$app/components/editor/command'; -import { useEditorInlineBlockState } from '$app/components/editor/stores'; -import { InlineChromiumBugfix } from '$app/components/editor/components/inline_nodes/InlineChromiumBugfix'; - -export const InlineFormula = memo( - forwardRef>(({ node, children, ...attributes }, ref) => { - const editor = useSlate(); - const formula = node.data; - const { popoverOpen = false, setRange, openPopover, closePopover } = useEditorInlineBlockState('formula'); - const anchor = useRef(null); - const selected = useSelected(); - const open = Boolean(popoverOpen && selected); - - const isCollapsed = editor.selection && Range.isCollapsed(editor.selection); - - useEffect(() => { - if (selected && isCollapsed && !open) { - const afterPoint = editor.selection ? editor.after(editor.selection) : undefined; - - const afterStart = afterPoint ? Editor.start(editor, afterPoint) : undefined; - - if (afterStart) { - editor.select(afterStart); - } - } - }, [editor, isCollapsed, selected, open]); - - const handleClick = useCallback( - (e: MouseEvent) => { - const target = e.currentTarget; - const path = getNodePath(editor, target); - - setRange(path); - openPopover(); - }, - [editor, openPopover, setRange] - ); - - const handleEditPopoverClose = useCallback(() => { - closePopover(); - if (anchor.current === null) { - return; - } - - moveCursorToNodeEnd(editor, anchor.current); - }, [closePopover, editor]); - - const selectNode = useCallback(() => { - if (anchor.current === null) { - return; - } - - const path = getNodePath(editor, anchor.current); - - ReactEditor.focus(editor); - Transforms.select(editor, path); - }, [editor]); - - const onClear = useCallback(() => { - selectNode(); - CustomEditor.toggleFormula(editor); - closePopover(); - }, [selectNode, closePopover, editor]); - - const onDone = useCallback( - (newFormula: string) => { - selectNode(); - if (newFormula === '' && anchor.current) { - const path = getNodePath(editor, anchor.current); - const point = editor.before(path); - - CustomEditor.deleteFormula(editor); - closePopover(); - if (point) { - ReactEditor.focus(editor); - editor.select(point); - } - - return; - } else { - CustomEditor.updateFormula(editor, newFormula); - handleEditPopoverClose(); - } - }, - [closePopover, editor, handleEditPopoverClose, selectNode] - ); - - return ( - <> - { - anchor.current = el; - if (ref) { - if (typeof ref === 'function') { - ref(el); - } else { - ref.current = el; - } - } - }} - contentEditable={false} - onDoubleClick={handleClick} - onClick={handleClick} - className={`${attributes.className ?? ''} formula-inline relative cursor-pointer rounded px-1 py-0.5 ${ - selected ? 'selected' : '' - }`} - > - - {children} - - - {open && ( - - )} - - ); - }) -); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/inline_formula/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/inline_formula/index.ts deleted file mode 100644 index 5643ae8943..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/inline_formula/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './InlineFormula'; -export * from './FormulaLeaf'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/link/Link.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/link/Link.tsx deleted file mode 100644 index 09095480dc..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/link/Link.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { memo, useCallback, useRef } from 'react'; -import { ReactEditor, useSlate } from 'slate-react'; -import { getNodePath } from '$app/components/editor/components/editor/utils'; -import { Transforms, Text } from 'slate'; -import { useDecorateDispatch } from '$app/components/editor/stores'; - -export const Link = memo(({ children }: { leaf: Text; children: React.ReactNode }) => { - const { add: addDecorate } = useDecorateDispatch(); - - const editor = useSlate(); - - const ref = useRef(null); - - const handleClick = useCallback( - (e: React.MouseEvent) => { - e.stopPropagation(); - e.preventDefault(); - if (ref.current === null) { - return; - } - - const path = getNodePath(editor, ref.current); - - ReactEditor.focus(editor); - Transforms.select(editor, path); - - if (!editor.selection) return; - addDecorate({ - range: editor.selection, - class_name: 'bg-content-blue-100 rounded', - type: 'link', - }); - }, - [addDecorate, editor] - ); - - return ( - <> - - {children} - - - ); -}); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/link/LinkEditContent.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/link/LinkEditContent.tsx deleted file mode 100644 index af62a7b28f..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/link/LinkEditContent.tsx +++ /dev/null @@ -1,179 +0,0 @@ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import Typography from '@mui/material/Typography'; -import { addMark, removeMark } from 'slate'; -import { EditorMarkFormat } from '$app/application/document/document.types'; -import { notify } from 'src/appflowy_app/components/_shared/notify'; -import { CustomEditor } from '$app/components/editor/command'; -import { useTranslation } from 'react-i18next'; -import { useSlateStatic } from 'slate-react'; -import { ReactComponent as RemoveSvg } from '$app/assets/delete.svg'; -import { ReactComponent as LinkSvg } from '$app/assets/link.svg'; -import { ReactComponent as CopySvg } from '$app/assets/copy.svg'; -import KeyboardNavigation, { - KeyboardNavigationOption, -} from '$app/components/_shared/keyboard_navigation/KeyboardNavigation'; -import isHotkey from 'is-hotkey'; -import LinkEditInput from '$app/components/editor/components/inline_nodes/link/LinkEditInput'; -import { openUrl, isUrl } from '$app/utils/open_url'; - -function LinkEditContent({ onClose, defaultHref }: { onClose: () => void; defaultHref: string }) { - const editor = useSlateStatic(); - const { t } = useTranslation(); - const isActivated = CustomEditor.isMarkActive(editor, EditorMarkFormat.Href); - - const [focusMenu, setFocusMenu] = useState(false); - const scrollRef = useRef(null); - const inputRef = useRef(null); - const [link, setLink] = useState(defaultHref); - - const setNodeMark = useCallback(() => { - if (link === '') { - removeMark(editor, EditorMarkFormat.Href); - } else { - addMark(editor, EditorMarkFormat.Href, link); - } - }, [editor, link]); - - const removeNodeMark = useCallback(() => { - onClose(); - editor.removeMark(EditorMarkFormat.Href); - }, [editor, onClose]); - - useEffect(() => { - const input = inputRef.current; - - if (!input) return; - - let isComposing = false; - - const handleCompositionUpdate = () => { - isComposing = true; - }; - - const handleCompositionEnd = () => { - isComposing = false; - }; - - const handleKeyDown = (e: KeyboardEvent) => { - e.stopPropagation(); - - if (e.key === 'Enter') { - e.preventDefault(); - if (isUrl(link)) { - onClose(); - setNodeMark(); - } - - return; - } - - if (e.key === 'Escape') { - e.preventDefault(); - onClose(); - return; - } - - if (e.key === 'Tab') { - e.preventDefault(); - setFocusMenu(true); - return; - } - - if (!isComposing && (e.key === 'ArrowDown' || e.key === 'ArrowUp')) { - notify.clear(); - notify.info(`Press Tab to focus on the menu`); - return; - } - }; - - input.addEventListener('compositionstart', handleCompositionUpdate); - input.addEventListener('compositionend', handleCompositionEnd); - input.addEventListener('compositionupdate', handleCompositionUpdate); - input.addEventListener('keydown', handleKeyDown); - return () => { - input.removeEventListener('keydown', handleKeyDown); - input.removeEventListener('compositionstart', handleCompositionUpdate); - input.removeEventListener('compositionend', handleCompositionEnd); - input.removeEventListener('compositionupdate', handleCompositionUpdate); - }; - }, [link, onClose, setNodeMark]); - - const onConfirm = useCallback( - (key: string) => { - if (key === 'open') { - openUrl(link); - } else if (key === 'copy') { - void navigator.clipboard.writeText(link); - notify.success(t('message.copy.success')); - } else if (key === 'remove') { - removeNodeMark(); - } - }, - [link, removeNodeMark, t] - ); - - const renderOption = useCallback((icon: React.ReactNode, label: string) => { - return ( -
- {icon} -
{label}
-
- ); - }, []); - - const editOptions: KeyboardNavigationOption[] = useMemo(() => { - return [ - { - key: 'open', - disabled: !isUrl(link), - content: renderOption(, t('editor.openLink')), - }, - { - key: 'copy', - content: renderOption(, t('editor.copyLink')), - }, - { - key: 'remove', - content: renderOption(, t('editor.removeLink')), - }, - ]; - }, [link, renderOption, t]); - - return ( - <> - {!isActivated && ( - {t('editor.addYourLink')} - )} - - -
- {isActivated && ( - { - setFocusMenu(true); - }} - onBlur={() => { - setFocusMenu(false); - }} - disableSelect={!focusMenu} - onEscape={onClose} - onKeyDown={(e) => { - e.stopPropagation(); - if (isHotkey('Tab', e)) { - e.preventDefault(); - setFocusMenu(false); - inputRef.current?.focus(); - } - }} - /> - )} -
- - ); -} - -export default LinkEditContent; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/link/LinkEditInput.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/link/LinkEditInput.tsx deleted file mode 100644 index 6e9a0bb497..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/link/LinkEditInput.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { TextField } from '@mui/material'; -import { useTranslation } from 'react-i18next'; -import { isUrl } from '$app/utils/open_url'; - -function LinkEditInput({ - link, - setLink, - inputRef, -}: { - link: string; - setLink: (link: string) => void; - inputRef: React.RefObject; -}) { - const { t } = useTranslation(); - const [error, setError] = useState(null); - - useEffect(() => { - if (isUrl(link)) { - setError(null); - return; - } - - setError(t('editor.incorrectLink')); - }, [link, t]); - - return ( -
- setLink(e.target.value)} - spellCheck={false} - inputRef={inputRef} - className={'my-1 p-0'} - placeholder={'https://example.com'} - fullWidth={true} - /> - - ); -} - -export default LinkEditInput; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/link/LinkEditPopover.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/link/LinkEditPopover.tsx deleted file mode 100644 index 2a5e3630da..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/link/LinkEditPopover.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import React from 'react'; -import Popover from '@mui/material/Popover'; -import { PopoverCommonProps } from '$app/components/editor/components/tools/popover'; - -import usePopoverAutoPosition from '$app/components/_shared/popover/Popover.hooks'; -import { PopoverOrigin } from '@mui/material/Popover/Popover'; -import LinkEditContent from '$app/components/editor/components/inline_nodes/link/LinkEditContent'; - -const initialAnchorOrigin: PopoverOrigin = { - vertical: 'bottom', - horizontal: 'center', -}; - -const initialTransformOrigin: PopoverOrigin = { - vertical: 'top', - horizontal: 'center', -}; - -export function LinkEditPopover({ - defaultHref, - open, - onClose, - anchorPosition, - anchorReference, -}: { - defaultHref: string; - open: boolean; - onClose: () => void; - anchorPosition?: { top: number; left: number; height: number }; - anchorReference?: 'anchorPosition' | 'anchorEl'; -}) { - const { - paperHeight, - anchorPosition: newAnchorPosition, - transformOrigin, - anchorOrigin, - } = usePopoverAutoPosition({ - anchorPosition, - open, - initialAnchorOrigin, - initialTransformOrigin, - initialPaperWidth: 340, - initialPaperHeight: 200, - }); - - return ( - { - onClose(); - }} - transformOrigin={transformOrigin} - anchorOrigin={anchorOrigin} - onMouseDown={(e) => e.stopPropagation()} - > -
- -
-
- ); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/link/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/link/index.ts deleted file mode 100644 index 295683a3bc..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/link/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './Link'; - -export * from './LinkEditPopover'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/mention/Mention.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/mention/Mention.tsx deleted file mode 100644 index 7511147ad0..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/mention/Mention.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React, { forwardRef, memo } from 'react'; -import { EditorElementProps, MentionNode } from '$app/application/document/document.types'; - -import MentionLeaf from '$app/components/editor/components/inline_nodes/mention/MentionLeaf'; -import { InlineChromiumBugfix } from '$app/components/editor/components/inline_nodes/InlineChromiumBugfix'; - -export const Mention = memo( - forwardRef>(({ node, children, ...attributes }, ref) => { - return ( - - - {children} - - - - ); - }) -); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/mention/MentionLeaf.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/mention/MentionLeaf.tsx deleted file mode 100644 index 10def395c5..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/mention/MentionLeaf.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import React, { useCallback, useEffect, useState } from 'react'; -import { Mention, MentionPage } from '$app/application/document/document.types'; -import { ReactComponent as DocumentSvg } from '$app/assets/document.svg'; -import { useTranslation } from 'react-i18next'; -import { getPage } from '$app/application/folder/page.service'; -import { useSelected, useSlate } from 'slate-react'; -import { ReactComponent as EyeClose } from '$app/assets/eye_close.svg'; -import { notify } from 'src/appflowy_app/components/_shared/notify'; -import { subscribeNotifications } from '$app/application/notification'; -import { FolderNotification } from '@/services/backend'; -import { Editor, Range } from 'slate'; -import { useAppDispatch } from '$app/stores/store'; -import { openPage } from '$app_reducers/pages/async_actions'; - -export function MentionLeaf({ mention }: { mention: Mention }) { - const { t } = useTranslation(); - const [page, setPage] = useState(null); - const [error, setError] = useState(false); - const editor = useSlate(); - const selected = useSelected(); - const isCollapsed = editor.selection && Range.isCollapsed(editor.selection); - const dispatch = useAppDispatch(); - - useEffect(() => { - if (selected && isCollapsed && page) { - const afterPoint = editor.selection ? editor.after(editor.selection) : undefined; - - const afterStart = afterPoint ? Editor.start(editor, afterPoint) : undefined; - - if (afterStart) { - editor.select(afterStart); - } - } - }, [editor, isCollapsed, selected, page]); - - const loadPage = useCallback(async () => { - setError(true); - // keep old field for backward compatibility - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - const pageId = mention.page_id ?? mention.page; - - if (!pageId) return; - try { - const page = await getPage(pageId); - - setPage(page); - setError(false); - } catch { - setPage(null); - setError(true); - } - }, [mention]); - - useEffect(() => { - void loadPage(); - }, [loadPage]); - - const handleOpenPage = useCallback(() => { - if (!page) { - notify.error(t('document.mention.deletedContent')); - return; - } - - void dispatch(openPage(page.id)); - }, [page, dispatch, t]); - - useEffect(() => { - if (!page) return; - const unsubscribePromise = subscribeNotifications( - { - [FolderNotification.DidUpdateView]: (changeset) => { - setPage((prev) => { - if (!prev) { - return prev; - } - - return { - ...prev, - name: changeset.name, - }; - }); - }, - }, - { - id: page.id, - } - ); - - return () => void unsubscribePromise.then((unsubscribe) => unsubscribe()); - }, [page]); - - useEffect(() => { - const parentId = page?.parentId; - - if (!parentId) return; - - const unsubscribePromise = subscribeNotifications( - { - [FolderNotification.DidUpdateChildViews]: (changeset) => { - if (changeset.delete_child_views.includes(page.id)) { - setPage(null); - setError(true); - } - }, - }, - { - id: parentId, - } - ); - - return () => void unsubscribePromise.then((unsubscribe) => unsubscribe()); - }, [page]); - - return ( - - {error ? ( - <> - - {t('document.mention.deleted')} - - ) : ( - page && ( - <> - {page.icon?.value || } - {page.name.trim() || t('menuAppHeader.defaultNewPageName')} - - ) - )} - - ); -} - -export default MentionLeaf; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/mention/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/mention/index.ts deleted file mode 100644 index d3ee18034d..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/mention/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Mention'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/withInline.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/withInline.ts deleted file mode 100644 index 2859c1f0a8..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/inline_nodes/withInline.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { ReactEditor } from 'slate-react'; -import { EditorInlineNodeType, inlineNodeTypes } from '$app/application/document/document.types'; -import { Element } from 'slate'; - -export function withInlines(editor: ReactEditor) { - const { isInline, isElementReadOnly, isSelectable, isVoid, markableVoid } = editor; - - const matchInlineType = (element: Element) => { - return inlineNodeTypes.includes(element.type as EditorInlineNodeType); - }; - - editor.isInline = (element) => { - return matchInlineType(element) || isInline(element); - }; - - editor.isVoid = (element) => { - return matchInlineType(element) || isVoid(element); - }; - - editor.markableVoid = (element) => { - return matchInlineType(element) || markableVoid(element); - }; - - editor.isElementReadOnly = (element) => - inlineNodeTypes.includes(element.type as EditorInlineNodeType) || isElementReadOnly(element); - - editor.isSelectable = (element) => - !inlineNodeTypes.includes(element.type as EditorInlineNodeType) && isSelectable(element); - - return editor; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/_shared/ColorPicker.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/_shared/ColorPicker.tsx deleted file mode 100644 index 41dea96f1e..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/_shared/ColorPicker.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import React, { useCallback, useRef, useMemo } from 'react'; -import Typography from '@mui/material/Typography'; -import KeyboardNavigation, { - KeyboardNavigationOption, -} from '$app/components/_shared/keyboard_navigation/KeyboardNavigation'; -import { useTranslation } from 'react-i18next'; -import { TitleOutlined } from '@mui/icons-material'; -import { EditorMarkFormat } from '$app/application/document/document.types'; -import { ColorEnum, renderColor } from '$app/utils/color'; - -export interface ColorPickerProps { - onChange?: (format: EditorMarkFormat.FontColor | EditorMarkFormat.BgColor, color: string) => void; - onEscape?: () => void; - disableFocus?: boolean; -} -export function ColorPicker({ onEscape, onChange, disableFocus }: ColorPickerProps) { - const { t } = useTranslation(); - - const ref = useRef(null); - - const handleColorChange = useCallback( - (key: string) => { - const [format, , color = ''] = key.split('-'); - const formatKey = format === 'font' ? EditorMarkFormat.FontColor : EditorMarkFormat.BgColor; - - onChange?.(formatKey, color); - }, - [onChange] - ); - - const renderColorItem = useCallback( - (name: string, color: string, backgroundColor?: string) => { - return ( -
{ - handleColorChange(backgroundColor ? backgroundColor : color); - }} - className={'flex w-full cursor-pointer items-center justify-center gap-2'} - > -
- -
-
{name}
-
- ); - }, - [handleColorChange] - ); - - const colors: KeyboardNavigationOption[] = useMemo(() => { - return [ - { - key: 'font_color', - content: ( - - {t('editor.textColor')} - - ), - children: [ - { - key: 'font-default', - content: renderColorItem(t('editor.fontColorDefault'), ''), - }, - { - key: `font-gray-rgb(120, 119, 116)`, - content: renderColorItem(t('editor.fontColorGray'), 'rgb(120, 119, 116)'), - }, - { - key: 'font-brown-rgb(159, 107, 83)', - content: renderColorItem(t('editor.fontColorBrown'), 'rgb(159, 107, 83)'), - }, - { - key: 'font-orange-rgb(217, 115, 13)', - content: renderColorItem(t('editor.fontColorOrange'), 'rgb(217, 115, 13)'), - }, - { - key: 'font-yellow-rgb(203, 145, 47)', - content: renderColorItem(t('editor.fontColorYellow'), 'rgb(203, 145, 47)'), - }, - { - key: 'font-green-rgb(68, 131, 97)', - content: renderColorItem(t('editor.fontColorGreen'), 'rgb(68, 131, 97)'), - }, - { - key: 'font-blue-rgb(51, 126, 169)', - content: renderColorItem(t('editor.fontColorBlue'), 'rgb(51, 126, 169)'), - }, - { - key: 'font-purple-rgb(144, 101, 176)', - content: renderColorItem(t('editor.fontColorPurple'), 'rgb(144, 101, 176)'), - }, - { - key: 'font-pink-rgb(193, 76, 138)', - content: renderColorItem(t('editor.fontColorPink'), 'rgb(193, 76, 138)'), - }, - { - key: 'font-red-rgb(212, 76, 71)', - content: renderColorItem(t('editor.fontColorRed'), 'rgb(212, 76, 71)'), - }, - ], - }, - { - key: 'bg_color', - content: ( - - {t('editor.backgroundColor')} - - ), - children: [ - { - key: 'bg-default', - content: renderColorItem(t('editor.backgroundColorDefault'), '', ''), - }, - { - key: `bg-lime-${ColorEnum.Lime}`, - content: renderColorItem(t('editor.backgroundColorLime'), '', ColorEnum.Lime), - }, - { - key: `bg-aqua-${ColorEnum.Aqua}`, - content: renderColorItem(t('editor.backgroundColorAqua'), '', ColorEnum.Aqua), - }, - { - key: `bg-orange-${ColorEnum.Orange}`, - content: renderColorItem(t('editor.backgroundColorOrange'), '', ColorEnum.Orange), - }, - { - key: `bg-yellow-${ColorEnum.Yellow}`, - content: renderColorItem(t('editor.backgroundColorYellow'), '', ColorEnum.Yellow), - }, - { - key: `bg-green-${ColorEnum.Green}`, - content: renderColorItem(t('editor.backgroundColorGreen'), '', ColorEnum.Green), - }, - { - key: `bg-blue-${ColorEnum.Blue}`, - content: renderColorItem(t('editor.backgroundColorBlue'), '', ColorEnum.Blue), - }, - { - key: `bg-purple-${ColorEnum.Purple}`, - content: renderColorItem(t('editor.backgroundColorPurple'), '', ColorEnum.Purple), - }, - { - key: `bg-pink-${ColorEnum.Pink}`, - content: renderColorItem(t('editor.backgroundColorPink'), '', ColorEnum.Pink), - }, - { - key: `bg-red-${ColorEnum.LightPink}`, - content: renderColorItem(t('editor.backgroundColorRed'), '', ColorEnum.LightPink), - }, - ], - }, - ]; - }, [renderColorItem, t]); - - return ( -
- -
- ); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/_shared/CustomColorPicker.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/_shared/CustomColorPicker.tsx deleted file mode 100644 index ae463a2ff3..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/_shared/CustomColorPicker.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React, { useState } from 'react'; -import Popover, { PopoverProps } from '@mui/material/Popover'; -import { Color, SketchPicker } from 'react-color'; - -import { Divider } from '@mui/material'; - -export function CustomColorPicker({ - onColorChange, - ...props -}: { - onColorChange?: (color: string) => void; -} & PopoverProps) { - const [color, setColor] = useState(); - - return ( - - { - setColor(color.rgb); - onColorChange?.(color.hex); - }} - color={color} - /> - - - ); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/_shared/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/_shared/index.ts deleted file mode 100644 index 00e212aa7f..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/_shared/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './CustomColorPicker'; -export * from './ColorPicker'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/block_actions/AddBlockBelow.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/block_actions/AddBlockBelow.tsx deleted file mode 100644 index eb2675bc71..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/block_actions/AddBlockBelow.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import React from 'react'; -import { ReactEditor, useSlate } from 'slate-react'; -import { IconButton, Tooltip } from '@mui/material'; -import { ReactComponent as AddSvg } from '$app/assets/add.svg'; -import { useTranslation } from 'react-i18next'; -import { Element, Path } from 'slate'; -import { CustomEditor } from '$app/components/editor/command'; -import { EditorNodeType } from '$app/application/document/document.types'; -import { YjsEditor } from '@slate-yjs/core'; -import { useSlashState } from '$app/components/editor/stores'; - -function AddBlockBelow({ node }: { node?: Element }) { - const { t } = useTranslation(); - const editor = useSlate(); - const { setOpen: setSlashOpen } = useSlashState(); - - const handleAddBelow = () => { - if (!node) return; - ReactEditor.focus(editor); - - const nodePath = ReactEditor.findPath(editor, node); - const nextPath = Path.next(nodePath); - - editor.select(nodePath); - - if (editor.isSelectable(node)) { - editor.collapse({ - edge: 'start', - }); - } - - const isEmptyNode = CustomEditor.isEmptyText(editor, node); - - // if the node is not a paragraph, or it is not empty, insert a new empty line - if (node.type !== EditorNodeType.Paragraph || !isEmptyNode) { - CustomEditor.insertEmptyLine(editor as ReactEditor & YjsEditor, nextPath); - editor.select(nextPath); - } - - requestAnimationFrame(() => { - setSlashOpen(true); - }); - }; - - return ( - <> - - - - - - - ); -} - -export default AddBlockBelow; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/block_actions/BlockActions.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/block_actions/BlockActions.tsx deleted file mode 100644 index f5833e538b..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/block_actions/BlockActions.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; - -import { Element } from 'slate'; -import AddBlockBelow from '$app/components/editor/components/tools/block_actions/AddBlockBelow'; -import { ReactComponent as DragSvg } from '$app/assets/drag.svg'; -import { IconButton, Tooltip } from '@mui/material'; -import { useTranslation } from 'react-i18next'; - -export function BlockActions({ - node, - onClickDrag, -}: { - node?: Element; - onClickDrag: (e: React.MouseEvent) => void; -}) { - const { t } = useTranslation(); - - return ( - <> - - - - - - - - ); -} - -export default BlockActions; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/block_actions/BlockActionsToolbar.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/block_actions/BlockActionsToolbar.hooks.ts deleted file mode 100644 index bc1086dde9..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/block_actions/BlockActionsToolbar.hooks.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { RefObject, useCallback, useEffect, useState } from 'react'; -import { ReactEditor, useSlate } from 'slate-react'; -import { findEventNode, getBlockActionsPosition } from '$app/components/editor/components/tools/block_actions/utils'; -import { Element, Editor, Range } from 'slate'; -import { EditorNodeType } from '$app/application/document/document.types'; -import { Log } from '$app/utils/log'; - -export function useBlockActionsToolbar(ref: RefObject, contextMenuVisible: boolean) { - const editor = useSlate(); - const [node, setNode] = useState(null); - - const recalculatePosition = useCallback( - (blockElement: HTMLElement) => { - const { top, left } = getBlockActionsPosition(editor, blockElement); - - const slateEditorDom = ReactEditor.toDOMNode(editor, editor); - - if (!ref.current) return; - - ref.current.style.top = `${top + slateEditorDom.offsetTop}px`; - ref.current.style.left = `${left + slateEditorDom.offsetLeft - 64}px`; - }, - [editor, ref] - ); - - const close = useCallback(() => { - const el = ref.current; - - if (!el) return; - - el.style.opacity = '0'; - el.style.pointerEvents = 'none'; - setNode(null); - }, [ref]); - - useEffect(() => { - const handleMouseMove = (e: MouseEvent) => { - const el = ref.current; - - if (!el) return; - - const target = e.target as HTMLElement; - - if (target.closest(`[contenteditable="false"]`)) { - return; - } - - let range: Range | null = null; - let node; - - try { - range = ReactEditor.findEventRange(editor, e); - } catch { - const editorDom = ReactEditor.toDOMNode(editor, editor); - const rect = editorDom.getBoundingClientRect(); - const isOverLeftBoundary = e.clientX < rect.left + 64; - const isOverRightBoundary = e.clientX > rect.right - 64; - let newX = e.clientX; - - if (isOverLeftBoundary) { - newX = rect.left + 64; - } - - if (isOverRightBoundary) { - newX = rect.right - 64; - } - - node = findEventNode(editor, { - x: newX, - y: e.clientY, - }); - } - - if (!range && !node) { - Log.warn('No range and node found'); - return; - } else if (range) { - const match = editor.above({ - match: (n) => { - return !Editor.isEditor(n) && Element.isElement(n) && n.blockId !== undefined; - }, - at: range, - }); - - if (!match) { - close(); - return; - } - - node = match[0] as Element; - } - - if (!node) { - close(); - return; - } - - if (node.type === EditorNodeType.Page) return; - const blockElement = ReactEditor.toDOMNode(editor, node); - - if (!blockElement) return; - recalculatePosition(blockElement); - el.style.opacity = '1'; - el.style.pointerEvents = 'auto'; - const slateNode = ReactEditor.toSlateNode(editor, blockElement) as Element; - - setNode(slateNode); - }; - - const dom = ReactEditor.toDOMNode(editor, editor); - - if (!contextMenuVisible) { - dom.addEventListener('mousemove', handleMouseMove); - dom.parentElement?.addEventListener('mouseleave', close); - } - - return () => { - dom.removeEventListener('mousemove', handleMouseMove); - dom.parentElement?.removeEventListener('mouseleave', close); - }; - }, [close, editor, contextMenuVisible, ref, recalculatePosition]); - - useEffect(() => { - let observer: MutationObserver | null = null; - - if (node) { - const dom = ReactEditor.toDOMNode(editor, node); - - if (dom.parentElement) { - observer = new MutationObserver(close); - - observer.observe(dom.parentElement, { - childList: true, - }); - } - } - - return () => { - observer?.disconnect(); - }; - }, [close, editor, node]); - - return { - node: node?.type === EditorNodeType.Page ? null : node, - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/block_actions/BlockActionsToolbar.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/block_actions/BlockActionsToolbar.tsx deleted file mode 100644 index 729b4df144..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/block_actions/BlockActionsToolbar.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; -import { useBlockActionsToolbar } from './BlockActionsToolbar.hooks'; -import BlockActions from '$app/components/editor/components/tools/block_actions/BlockActions'; - -import { getBlockCssProperty } from '$app/components/editor/components/tools/block_actions/utils'; -import BlockOperationMenu from '$app/components/editor/components/tools/block_actions/BlockOperationMenu'; -import { ReactEditor, useSlateStatic } from 'slate-react'; -import { PopoverProps } from '@mui/material/Popover'; - -import { EditorSelectedBlockContext } from '$app/components/editor/stores/selected'; -import withErrorBoundary from '$app/components/_shared/error_boundary/withError'; -import { CustomEditor } from '$app/components/editor/command'; -import isEqual from 'lodash-es/isEqual'; -import { Range } from 'slate'; - -const Toolbar = () => { - const ref = useRef(null); - const [openContextMenu, setOpenContextMenu] = useState(false); - const { node } = useBlockActionsToolbar(ref, openContextMenu); - const cssProperty = node && getBlockCssProperty(node); - const selectedBlockContext = useContext(EditorSelectedBlockContext); - const popoverPropsRef = useRef | undefined>(undefined); - const editor = useSlateStatic(); - - const handleOpen = useCallback(() => { - if (!node || !node.blockId) return; - setOpenContextMenu(true); - const path = ReactEditor.findPath(editor, node); - - editor.select(path); - selectedBlockContext.clear(); - selectedBlockContext.add(node.blockId); - }, [editor, node, selectedBlockContext]); - - const handleClose = useCallback(() => { - setOpenContextMenu(false); - selectedBlockContext.clear(); - }, [selectedBlockContext]); - - useEffect(() => { - if (!node) return; - const nodeDom = ReactEditor.toDOMNode(editor, node); - const onContextMenu = (e: MouseEvent) => { - const { clientX, clientY } = e; - - e.stopPropagation(); - - const { selection } = editor; - - const editorRange = ReactEditor.findEventRange(editor, e); - - if (!editorRange || !selection) return; - - const rangeBlock = CustomEditor.getBlock(editor, editorRange); - const selectedBlock = CustomEditor.getBlock(editor, selection); - - if ( - Range.intersection(selection, editorRange) || - (rangeBlock && selectedBlock && isEqual(rangeBlock[1], selectedBlock[1])) - ) { - const windowSelection = window.getSelection(); - const range = windowSelection?.rangeCount ? windowSelection?.getRangeAt(0) : null; - const isCollapsed = windowSelection?.isCollapsed; - - if (windowSelection && !isCollapsed) { - if (range && range.endOffset === 0 && range.startContainer !== range.endContainer) { - const newRange = range.cloneRange(); - - newRange.setEnd(range.startContainer, range.startOffset); - windowSelection.removeAllRanges(); - windowSelection.addRange(newRange); - } - } - - return; - } - - e.preventDefault(); - - popoverPropsRef.current = { - transformOrigin: { - vertical: 'top', - horizontal: 'left', - }, - anchorReference: 'anchorPosition', - anchorPosition: { - top: clientY, - left: clientX, - }, - }; - - handleOpen(); - }; - - nodeDom.addEventListener('contextmenu', onContextMenu); - - return () => { - nodeDom.removeEventListener('contextmenu', onContextMenu); - }; - }, [editor, handleOpen, node]); - return ( - <> -
- {/* Ensure the toolbar in middle */} -
$
- { - ) => { - const target = e.currentTarget; - const rect = target.getBoundingClientRect(); - - popoverPropsRef.current = { - transformOrigin: { - vertical: 'center', - horizontal: 'right', - }, - anchorReference: 'anchorPosition', - anchorPosition: { - top: rect.top + rect.height / 2, - left: rect.left, - }, - }; - - handleOpen(); - }} - /> - } -
- {node && openContextMenu && ( - - )} - - ); -}; - -export const BlockActionsToolbar = withErrorBoundary(Toolbar); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/block_actions/BlockOperationMenu.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/block_actions/BlockOperationMenu.tsx deleted file mode 100644 index ade9817503..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/block_actions/BlockOperationMenu.tsx +++ /dev/null @@ -1,209 +0,0 @@ -import React, { useCallback, useMemo } from 'react'; -import Popover, { PopoverProps } from '@mui/material/Popover'; -import { ReactComponent as DeleteSvg } from '$app/assets/delete.svg'; -import { ReactComponent as CopySvg } from '$app/assets/copy.svg'; -import { useTranslation } from 'react-i18next'; -import { Divider } from '@mui/material'; -import { PopoverCommonProps } from '$app/components/editor/components/tools/popover'; -import { Element, Path } from 'slate'; -import { ReactEditor, useSlateStatic } from 'slate-react'; -import { CustomEditor } from '$app/components/editor/command'; -import KeyboardNavigation, { - KeyboardNavigationOption, -} from '$app/components/_shared/keyboard_navigation/KeyboardNavigation'; -import { Color } from '$app/components/editor/components/tools/block_actions/color'; -import { getModifier } from '$app/utils/hotkeys'; - -import isHotkey from 'is-hotkey'; -import { EditorNodeType } from '$app/application/document/document.types'; -import { EditorSelectedBlockContext } from '$app/components/editor/stores/selected'; - -export const canSetColorBlocks: EditorNodeType[] = [ - EditorNodeType.Paragraph, - EditorNodeType.HeadingBlock, - EditorNodeType.TodoListBlock, - EditorNodeType.BulletedListBlock, - EditorNodeType.NumberedListBlock, - EditorNodeType.ToggleListBlock, - EditorNodeType.QuoteBlock, - EditorNodeType.CalloutBlock, -]; - -export function BlockOperationMenu({ - node, - ...props -}: { - node: Element; -} & PopoverProps) { - const editor = useSlateStatic(); - const { t } = useTranslation(); - - const canSetColor = useMemo(() => { - return canSetColorBlocks.includes(node.type as EditorNodeType); - }, [node]); - const selectedBlockContext = React.useContext(EditorSelectedBlockContext); - const [openColorMenu, setOpenColorMenu] = React.useState(false); - const ref = React.useRef(null); - const handleClose = useCallback(() => { - props.onClose?.({}, 'backdropClick'); - ReactEditor.focus(editor); - try { - const path = ReactEditor.findPath(editor, node); - - editor.select(path); - } catch (e) { - // do nothing - } - - editor.collapse({ - edge: 'start', - }); - }, [editor, node, props]); - - const onConfirm = useCallback( - (optionKey: string) => { - switch (optionKey) { - case 'delete': { - CustomEditor.deleteNode(editor, node); - break; - } - - case 'duplicate': { - const path = ReactEditor.findPath(editor, node); - const newNode = CustomEditor.duplicateNode(editor, node); - - handleClose(); - - const newBlockId = newNode.blockId; - - if (!newBlockId) return; - requestAnimationFrame(() => { - selectedBlockContext.clear(); - selectedBlockContext.add(newBlockId); - const nextPath = Path.next(path); - - editor.select(nextPath); - editor.collapse({ - edge: 'start', - }); - }); - return; - } - - case 'color': { - setOpenColorMenu(true); - return; - } - } - - handleClose(); - }, - [editor, handleClose, node, selectedBlockContext] - ); - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const options: KeyboardNavigationOption[] = useMemo( - () => - [ - { - key: 'block-operation', - children: [ - { - key: 'delete', - content: ( -
- -
{t('button.delete')}
-
{'Del'}
-
- ), - }, - { - key: 'duplicate', - content: ( -
- -
{t('button.duplicate')}
-
{`${getModifier()} + D`}
-
- ), - }, - ], - }, - canSetColor && { - key: 'color', - content: , - children: [ - { - key: 'color', - content: ( - { - setOpenColorMenu(false); - }} - openPicker={openColorMenu} - onOpenPicker={() => setOpenColorMenu(true)} - /> - ), - }, - ], - }, - ].filter(Boolean), - [node, canSetColor, openColorMenu, t] - ); - - const handleKeyDown = useCallback( - (e: KeyboardEvent) => { - e.stopPropagation(); - if (isHotkey('mod+d', e)) { - e.preventDefault(); - onConfirm('duplicate'); - } - - if (isHotkey('del', e) || isHotkey('backspace', e)) { - e.preventDefault(); - onConfirm('delete'); - } - }, - [onConfirm] - ); - - return ( - e.stopPropagation()} - {...props} - onClose={handleClose} - > -
- { - if (key === 'color') { - onConfirm(key); - } else { - handleClose(); - } - }} - options={options} - scrollRef={ref} - onEscape={handleClose} - onConfirm={onConfirm} - /> -
-
- ); -} - -export default BlockOperationMenu; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/block_actions/color/Color.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/block_actions/color/Color.tsx deleted file mode 100644 index 499ab95c76..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/block_actions/color/Color.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import React, { useCallback, useRef } from 'react'; -import { Element } from 'slate'; -import { Popover } from '@mui/material'; -import ColorLensOutlinedIcon from '@mui/icons-material/ColorLensOutlined'; -import { useTranslation } from 'react-i18next'; -import { ColorPicker } from '$app/components/editor/components/tools/_shared'; -import { CustomEditor } from '$app/components/editor/command'; -import { ReactComponent as MoreSvg } from '$app/assets/more.svg'; -import { useSlateStatic } from 'slate-react'; -import { PopoverOrigin } from '@mui/material/Popover/Popover'; - -const initialOrigin: { - transformOrigin?: PopoverOrigin; - anchorOrigin?: PopoverOrigin; -} = { - anchorOrigin: { - vertical: 'center', - horizontal: 'right', - }, - transformOrigin: { - vertical: 'center', - horizontal: 'left', - }, -}; - -export function Color({ - node, - openPicker, - onOpenPicker, - onClosePicker, -}: { - node: Element & { - data?: { - font_color?: string; - bg_color?: string; - }; - }; - openPicker?: boolean; - onOpenPicker?: () => void; - onClosePicker?: () => void; -}) { - const { t } = useTranslation(); - - const editor = useSlateStatic(); - - const ref = useRef(null); - - const onColorChange = useCallback( - (format: 'font_color' | 'bg_color', color: string) => { - CustomEditor.setBlockColor(editor, node, { - [format]: color, - }); - onClosePicker?.(); - }, - [editor, node, onClosePicker] - ); - - return ( - <> -
- -
{t('editor.color')}
- -
- {openPicker && ( - { - e.stopPropagation(); - e.nativeEvent.stopImmediatePropagation(); - if (e.key === 'Escape' || e.key === 'ArrowLeft') { - e.preventDefault(); - onClosePicker?.(); - } - }} - onClick={(e) => e.stopPropagation()} - anchorEl={ref.current} - onClose={onClosePicker} - > -
- -
-
- )} - - ); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/block_actions/color/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/block_actions/color/index.ts deleted file mode 100644 index 0fd619bc86..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/block_actions/color/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Color'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/block_actions/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/block_actions/index.ts deleted file mode 100644 index e8b87721c2..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/block_actions/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './BlockActions'; -export * from './BlockActionsToolbar'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/block_actions/utils.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/block_actions/utils.ts deleted file mode 100644 index b63afe9dc1..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/block_actions/utils.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { ReactEditor } from 'slate-react'; -import { getEditorDomNode, getHeadingCssProperty } from '$app/components/editor/plugins/utils'; -import { Element } from 'slate'; -import { EditorNodeType, HeadingNode } from '$app/application/document/document.types'; - -export function getBlockActionsPosition(editor: ReactEditor, blockElement: HTMLElement) { - const editorDom = getEditorDomNode(editor); - const editorDomRect = editorDom.getBoundingClientRect(); - const blockDomRect = blockElement.getBoundingClientRect(); - - const relativeTop = blockDomRect.top - editorDomRect.top; - const relativeLeft = blockDomRect.left - editorDomRect.left; - - return { - top: relativeTop, - left: relativeLeft, - }; -} - -export function getBlockCssProperty(node: Element) { - switch (node.type) { - case EditorNodeType.HeadingBlock: - return `${getHeadingCssProperty((node as HeadingNode).data.level)} mt-1`; - case EditorNodeType.CodeBlock: - case EditorNodeType.CalloutBlock: - case EditorNodeType.EquationBlock: - case EditorNodeType.GridBlock: - return 'my-3'; - case EditorNodeType.DividerBlock: - return 'my-0'; - default: - return 'mt-1'; - } -} - -/** - * @param editor - * @param e - */ -export function findEventNode( - editor: ReactEditor, - { - x, - y, - }: { - x: number; - y: number; - } -) { - const element = document.elementFromPoint(x, y); - const nodeDom = element?.closest('[data-block-type]'); - - if (nodeDom) { - return ReactEditor.toSlateNode(editor, nodeDom) as Element; - } - - return null; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/Command.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/Command.hooks.ts deleted file mode 100644 index 633d09349d..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/Command.hooks.ts +++ /dev/null @@ -1,259 +0,0 @@ -import { useEffect, useState, useCallback, useRef } from 'react'; -import { getPanelPosition } from '$app/components/editor/components/tools/command_panel/utils'; -import { useSlate } from 'slate-react'; -import { PopoverPreventBlurProps } from '$app/components/editor/components/tools/popover'; -import { PopoverProps } from '@mui/material/Popover'; - -import { Editor, Point, Range, Transforms } from 'slate'; -import { CustomEditor } from '$app/components/editor/command'; -import { PopoverOrigin } from '@mui/material/Popover/Popover'; -import { useSlashState } from '$app/components/editor/stores'; - -export enum EditorCommand { - Mention = '@', - SlashCommand = '/', -} - -export const PanelPopoverProps: Partial = { - ...PopoverPreventBlurProps, - anchorReference: 'anchorPosition', -}; - -const commands = Object.values(EditorCommand); - -export interface PanelProps { - anchorPosition?: { left: number; top: number; height: number }; - closePanel: (deleteText?: boolean) => void; - searchText: string; - openPanel: () => void; -} - -export function useCommandPanel() { - const editor = useSlate(); - const { open: slashOpen, setOpen: setSlashOpen } = useSlashState(); - const [command, setCommand] = useState(undefined); - const [anchorPosition, setAnchorPosition] = useState< - | { - top: number; - left: number; - height: number; - } - | undefined - >(undefined); - const startPoint = useRef(); - const endPoint = useRef(); - const open = Boolean(anchorPosition); - const [searchText, setSearchText] = useState(''); - - const closePanel = useCallback( - (deleteText?: boolean) => { - if (deleteText && startPoint.current && endPoint.current) { - const anchor = { - path: startPoint.current.path, - offset: startPoint.current.offset > 0 ? startPoint.current.offset - 1 : 0, - }; - const focus = { - path: endPoint.current.path, - offset: endPoint.current.offset, - }; - - if (!Point.equals(anchor, focus)) { - Transforms.delete(editor, { - at: { - anchor, - focus, - }, - }); - } - } - - setSlashOpen(false); - setCommand(undefined); - setAnchorPosition(undefined); - setSearchText(''); - }, - [editor, setSlashOpen] - ); - - const setPosition = useCallback( - (position?: { left: number; top: number; height: number }) => { - if (!position) { - closePanel(false); - return; - } - - const nodeEntry = CustomEditor.getBlock(editor); - - if (!nodeEntry) return; - - setAnchorPosition(position); - }, - [closePanel, editor] - ); - - const openPanel = useCallback(() => { - const position = getPanelPosition(editor); - - if (position && editor.selection) { - startPoint.current = Editor.start(editor, editor.selection); - endPoint.current = Editor.end(editor, editor.selection); - setPosition(position); - } else { - setPosition(undefined); - } - }, [editor, setPosition]); - - useEffect(() => { - if (!slashOpen && command === EditorCommand.SlashCommand) { - closePanel(); - return; - } - - if (slashOpen && !open) { - setCommand(EditorCommand.SlashCommand); - openPanel(); - return; - } - }, [slashOpen, closePanel, command, open, openPanel]); - /** - * listen to editor insertText and deleteBackward event - */ - useEffect(() => { - const { insertText } = editor; - - /** - * insertText: when insert char at after space or at start of element, show the panel - * open condition: - * 1. open is false - * 2. current block is not code block - * 3. current selection is not include root - * 4. current selection is collapsed - * 5. insert char is command char - * 6. before text is empty or end with space - * --------- start ----------------- - * | - selection point - * @ - panel char - * _ - space - * - - other text - * -------- open panel ---------------- - * ---_@|--- => insert text is panel char and before text is end with space, open the panel - * @|--- => insert text is panel char and before text is empty, open the panel - */ - editor.insertText = (text, opts) => { - if (open || CustomEditor.isCodeBlock(editor) || CustomEditor.selectionIncludeRoot(editor)) { - insertText(text, opts); - return; - } - - const { selection } = editor; - - const command = commands.find((c) => text.endsWith(c)); - const endOfPanelChar = !!command; - - if (command === EditorCommand.SlashCommand) { - setSlashOpen(true); - } - - setCommand(command); - if (!selection || !endOfPanelChar || !Range.isCollapsed(selection)) { - insertText(text, opts); - return; - } - - const block = CustomEditor.getBlock(editor); - const path = block ? block[1] : []; - const { anchor } = selection; - const beforeText = Editor.string(editor, { anchor, focus: Editor.start(editor, path) }) + text.slice(0, -1); - // show the panel when insert char at after space or at start of element - const showPanel = !beforeText || beforeText.endsWith(' '); - - insertText(text, opts); - - if (!showPanel) return; - openPanel(); - }; - - return () => { - editor.insertText = insertText; - }; - }, [open, editor, openPanel, setSlashOpen]); - - /** - * listen to editor onChange event - */ - useEffect(() => { - const { onChange } = editor; - - if (!open) return; - - /** - * onChange: when selection change, update the search text or close the panel - * --------- start ----------------- - * | - selection point - * @ - panel char - * __ - search text - * - - other text - * -------- close panel ---------------- - * --|@--- => selection is backward to start point, close the panel - * ---@__-|--- => selection is forward to end point, close the panel - * -------- update search text ---------------- - * ---@__|--- - * ---@_|_--- => selection is forward to start point and backward to end point, update the search text - * ---@|__--- - * --------- end ----------------- - */ - editor.onChange = (...args) => { - if (!editor.selection || !startPoint.current || !endPoint.current) return; - onChange(...args); - const isSelectionChange = editor.operations.every((op) => op.type === 'set_selection'); - const currentPoint = Editor.end(editor, editor.selection); - const isBackward = currentPoint.offset < startPoint.current.offset; - - if (isBackward) { - closePanel(false); - return; - } - - if (!isSelectionChange) { - if (currentPoint.offset > endPoint.current?.offset) { - endPoint.current = currentPoint; - } - - const text = Editor.string(editor, { - anchor: startPoint.current, - focus: endPoint.current, - }); - - setSearchText(text); - } else { - const isForward = currentPoint.offset > endPoint.current.offset; - - if (isForward) { - closePanel(false); - } - } - }; - - return () => { - editor.onChange = onChange; - }; - }, [open, editor, closePanel]); - - return { - anchorPosition, - closePanel, - searchText, - openPanel, - command, - }; -} - -export const initialTransformOrigin: PopoverOrigin = { - vertical: 'top', - horizontal: 'left', -}; - -export const initialAnchorOrigin: PopoverOrigin = { - vertical: 'bottom', - horizontal: 'right', -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/CommandPanel.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/CommandPanel.tsx deleted file mode 100644 index db58e2deca..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/CommandPanel.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -import { SlashCommandPanel } from '$app/components/editor/components/tools/command_panel/slash_command_panel'; -import { MentionPanel } from '$app/components/editor/components/tools/command_panel/mention_panel'; -import { EditorCommand, useCommandPanel } from '$app/components/editor/components/tools/command_panel/Command.hooks'; -import withErrorBoundary from '$app/components/_shared/error_boundary/withError'; - -function CommandPanel() { - const { anchorPosition, searchText, openPanel, closePanel, command } = useCommandPanel(); - - const Component = command === EditorCommand.SlashCommand ? SlashCommandPanel : MentionPanel; - - return ( - - ); -} - -export default withErrorBoundary(CommandPanel); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/index.ts deleted file mode 100644 index cf07c7d996..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './mention_panel'; -export * from './slash_command_panel'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/mention_panel/MentionPanel.hooks.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/mention_panel/MentionPanel.hooks.tsx deleted file mode 100644 index 5d83870719..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/mention_panel/MentionPanel.hooks.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import React, { useCallback, useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useSlate } from 'slate-react'; -import { MentionPage, MentionType } from '$app/application/document/document.types'; -import { CustomEditor } from '$app/components/editor/command'; -import { KeyboardNavigationOption } from '$app/components/_shared/keyboard_navigation/KeyboardNavigation'; -import { ReactComponent as DocumentSvg } from '$app/assets/document.svg'; -// import dayjs from 'dayjs'; - -// enum DateKey { -// Today = 'today', -// Tomorrow = 'tomorrow', -// } -export function useMentionPanel({ - closePanel, - pages, -}: { - pages: MentionPage[]; - closePanel: (deleteText?: boolean) => void; -}) { - const { t } = useTranslation(); - const editor = useSlate(); - - const onConfirm = useCallback( - (key: string) => { - const [, id] = key.split(','); - - closePanel(true); - CustomEditor.insertMention(editor, { - page_id: id, - type: MentionType.PageRef, - }); - }, - [closePanel, editor] - ); - - const renderPage = useCallback( - (page: MentionPage) => { - return { - key: `${MentionType.PageRef},${page.id}`, - content: ( -
-
{page.icon?.value || }
- -
{page.name.trim() || t('menuAppHeader.defaultNewPageName')}
-
- ), - }; - }, - [t] - ); - - // const renderDate = useCallback(() => { - // return [ - // { - // key: DateKey.Today, - // content: ( - //
- // {t('relativeDates.today')} -{' '} - // {dayjs().format('MMM D, YYYY')} - //
- // ), - // - // children: [], - // }, - // { - // key: DateKey.Tomorrow, - // content: ( - //
- // {t('relativeDates.tomorrow')} - //
- // ), - // children: [], - // }, - // ]; - // }, [t]); - - const options: KeyboardNavigationOption[] = useMemo(() => { - return [ - // { - // key: MentionType.Date, - // content:
{t('editor.date')}
, - // children: renderDate(), - // }, - { - key: 'divider', - content:
, - children: [], - }, - - { - key: MentionType.PageRef, - content:
{t('document.mention.page.label')}
, - children: - pages.length > 0 - ? pages.map(renderPage) - : [ - { - key: 'noPage', - content: ( -
{t('findAndReplace.noResult')}
- ), - children: [], - }, - ], - }, - ]; - }, [pages, renderPage, t]); - - return { - options, - onConfirm, - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/mention_panel/MentionPanel.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/mention_panel/MentionPanel.tsx deleted file mode 100644 index 6ca0225579..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/mention_panel/MentionPanel.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { - initialAnchorOrigin, - initialTransformOrigin, - PanelPopoverProps, - PanelProps, -} from '$app/components/editor/components/tools/command_panel/Command.hooks'; -import Popover from '@mui/material/Popover'; - -import MentionPanelContent from '$app/components/editor/components/tools/command_panel/mention_panel/MentionPanelContent'; -import usePopoverAutoPosition from '$app/components/_shared/popover/Popover.hooks'; -import { useAppSelector } from '$app/stores/store'; -import { MentionPage } from '$app/application/document/document.types'; - -export function MentionPanel({ anchorPosition, closePanel, searchText }: PanelProps) { - const ref = useRef(null); - const pagesMap = useAppSelector((state) => state.pages.pageMap); - - const pagesRef = useRef([]); - const [recentPages, setPages] = useState([]); - - const loadPages = useCallback(async () => { - const pages = Object.values(pagesMap); - - pagesRef.current = pages; - setPages(pages); - }, [pagesMap]); - - useEffect(() => { - void loadPages(); - }, [loadPages]); - - useEffect(() => { - if (!searchText) { - setPages(pagesRef.current); - return; - } - - const filteredPages = pagesRef.current.filter((page) => { - return page.name.toLowerCase().includes(searchText.toLowerCase()); - }); - - setPages(filteredPages); - }, [searchText]); - const open = Boolean(anchorPosition); - - const { - paperHeight, - anchorPosition: newAnchorPosition, - paperWidth, - transformOrigin, - anchorOrigin, - isEntered, - } = usePopoverAutoPosition({ - initialPaperWidth: 300, - initialPaperHeight: 360, - anchorPosition, - initialTransformOrigin, - initialAnchorOrigin, - open, - }); - - return ( -
- {open && ( - closePanel(false)} - > - - - )} -
- ); -} - -export default MentionPanel; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/mention_panel/MentionPanelContent.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/mention_panel/MentionPanelContent.tsx deleted file mode 100644 index 36b00ca2b6..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/mention_panel/MentionPanelContent.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import React, { useRef } from 'react'; -import { useMentionPanel } from '$app/components/editor/components/tools/command_panel/mention_panel/MentionPanel.hooks'; - -import KeyboardNavigation from '$app/components/_shared/keyboard_navigation/KeyboardNavigation'; -import { MentionPage } from '$app/application/document/document.types'; - -function MentionPanelContent({ - closePanel, - pages, - maxHeight, - width, -}: { - closePanel: (deleteText?: boolean) => void; - pages: MentionPage[]; - maxHeight: number; - width: number; -}) { - const scrollRef = useRef(null); - - const { options, onConfirm } = useMentionPanel({ - closePanel, - pages, - }); - - return ( -
- -
- ); -} - -export default MentionPanelContent; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/mention_panel/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/mention_panel/index.ts deleted file mode 100644 index bfca34ef9a..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/mention_panel/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './MentionPanel'; -export * from './MentionPanelContent'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/slash_command_panel/SlashCommandPanel.hooks.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/slash_command_panel/SlashCommandPanel.hooks.tsx deleted file mode 100644 index c2d9445b56..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/slash_command_panel/SlashCommandPanel.hooks.tsx +++ /dev/null @@ -1,245 +0,0 @@ -import { EditorNodeType } from '$app/application/document/document.types'; -import { useCallback, useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; -import { ReactEditor, useSlate } from 'slate-react'; -import { Path } from 'slate'; -import { getBlock } from '$app/components/editor/plugins/utils'; -import { ReactComponent as TextIcon } from '$app/assets/text.svg'; -import { ReactComponent as TodoListIcon } from '$app/assets/todo-list.svg'; -import { ReactComponent as Heading1Icon } from '$app/assets/h1.svg'; -import { ReactComponent as Heading2Icon } from '$app/assets/h2.svg'; -import { ReactComponent as Heading3Icon } from '$app/assets/h3.svg'; -import { ReactComponent as BulletedListIcon } from '$app/assets/list.svg'; -import { ReactComponent as NumberedListIcon } from '$app/assets/numbers.svg'; -import { ReactComponent as QuoteIcon } from '$app/assets/quote.svg'; -import { ReactComponent as ToggleListIcon } from '$app/assets/show-menu.svg'; -import { ReactComponent as GridIcon } from '$app/assets/grid.svg'; -import { ReactComponent as ImageIcon } from '$app/assets/image.svg'; -import { DataObjectOutlined, FunctionsOutlined, HorizontalRuleOutlined, MenuBookOutlined } from '@mui/icons-material'; -import { CustomEditor } from '$app/components/editor/command'; -import { KeyboardNavigationOption } from '$app/components/_shared/keyboard_navigation/KeyboardNavigation'; -import { YjsEditor } from '@slate-yjs/core'; -import { useEditorBlockDispatch } from '$app/components/editor/stores/block'; -import { - headingTypes, - headingTypeToLevelMap, - reorderSlashOptions, - SlashAliases, - SlashCommandPanelTab, - slashOptionGroup, - slashOptionMapToEditorNodeType, - SlashOptionType, -} from '$app/components/editor/components/tools/command_panel/slash_command_panel/const'; - -export function useSlashCommandPanel({ - searchText, - closePanel, -}: { - searchText: string; - closePanel: (deleteText?: boolean) => void; -}) { - const { openPopover } = useEditorBlockDispatch(); - const { t } = useTranslation(); - const editor = useSlate(); - const onConfirm = useCallback( - (type: SlashOptionType) => { - const node = getBlock(editor); - - if (!node) return; - - const nodeType = slashOptionMapToEditorNodeType[type]; - - if (!nodeType) return; - - const data = {}; - - if (headingTypes.includes(type)) { - Object.assign(data, { - level: headingTypeToLevelMap[type], - }); - } - - if (nodeType === EditorNodeType.CalloutBlock) { - Object.assign(data, { - icon: '📌', - }); - } - - if (nodeType === EditorNodeType.CodeBlock) { - Object.assign(data, { - language: 'json', - }); - } - - if (nodeType === EditorNodeType.ImageBlock) { - Object.assign(data, { - url: '', - }); - } - - closePanel(true); - - const newNode = getBlock(editor); - const block = CustomEditor.getBlock(editor); - - const path = block ? block[1] : null; - - if (!newNode || !path) return; - - const isEmpty = CustomEditor.isEmptyText(editor, newNode); - - if (!isEmpty) { - const nextPath = Path.next(path); - - CustomEditor.insertEmptyLine(editor as ReactEditor & YjsEditor, nextPath); - editor.select(nextPath); - } - - const turnIntoBlock = CustomEditor.turnToBlock(editor, { - type: nodeType, - data, - }); - - setTimeout(() => { - if (turnIntoBlock && turnIntoBlock.blockId) { - if (turnIntoBlock.type === EditorNodeType.ImageBlock || turnIntoBlock.type === EditorNodeType.EquationBlock) { - openPopover(turnIntoBlock.type, turnIntoBlock.blockId); - } - } - }, 0); - }, - [editor, closePanel, openPopover] - ); - - const typeToLabelIconMap = useMemo(() => { - return { - [SlashOptionType.Paragraph]: { - label: t('editor.text'), - Icon: TextIcon, - }, - [SlashOptionType.TodoList]: { - label: t('editor.checkbox'), - Icon: TodoListIcon, - }, - [SlashOptionType.Heading1]: { - label: t('editor.heading1'), - Icon: Heading1Icon, - }, - [SlashOptionType.Heading2]: { - label: t('editor.heading2'), - Icon: Heading2Icon, - }, - [SlashOptionType.Heading3]: { - label: t('editor.heading3'), - Icon: Heading3Icon, - }, - [SlashOptionType.BulletedList]: { - label: t('editor.bulletedList'), - Icon: BulletedListIcon, - }, - [SlashOptionType.NumberedList]: { - label: t('editor.numberedList'), - Icon: NumberedListIcon, - }, - [SlashOptionType.Quote]: { - label: t('editor.quote'), - Icon: QuoteIcon, - }, - [SlashOptionType.ToggleList]: { - label: t('document.plugins.toggleList'), - Icon: ToggleListIcon, - }, - [SlashOptionType.Divider]: { - label: t('editor.divider'), - Icon: HorizontalRuleOutlined, - }, - [SlashOptionType.Callout]: { - label: t('document.plugins.callout'), - Icon: MenuBookOutlined, - }, - [SlashOptionType.Code]: { - label: t('document.selectionMenu.codeBlock'), - Icon: DataObjectOutlined, - }, - [SlashOptionType.Grid]: { - label: t('grid.menuName'), - Icon: GridIcon, - }, - - [SlashOptionType.MathEquation]: { - label: t('document.plugins.mathEquation.name'), - Icon: FunctionsOutlined, - }, - [SlashOptionType.Image]: { - label: t('editor.image'), - Icon: ImageIcon, - }, - }; - }, [t]); - - const groupTypeToLabelMap = useMemo(() => { - return { - [SlashCommandPanelTab.BASIC]: 'Basic', - [SlashCommandPanelTab.ADVANCED]: 'Advanced', - [SlashCommandPanelTab.MEDIA]: 'Media', - [SlashCommandPanelTab.DATABASE]: 'Database', - }; - }, []); - - const renderOptionContent = useCallback( - (type: SlashOptionType) => { - const Icon = typeToLabelIconMap[type].Icon; - - return ( -
-
- -
- -
{typeToLabelIconMap[type].label}
-
- ); - }, - [typeToLabelIconMap] - ); - - const options: KeyboardNavigationOption[] = useMemo(() => { - return slashOptionGroup - .map((group) => { - return { - key: group.key, - content:
{groupTypeToLabelMap[group.key]}
, - children: group.options - - .map((type) => { - return { - key: type, - content: renderOptionContent(type), - }; - }) - .filter((option) => { - if (!searchText) return true; - const label = typeToLabelIconMap[option.key].label; - - let newSearchText = searchText; - - if (searchText.startsWith('/')) { - newSearchText = searchText.slice(1); - } - - return ( - label.toLowerCase().includes(newSearchText.toLowerCase()) || - SlashAliases[option.key].some((alias) => alias.startsWith(newSearchText.toLowerCase())) - ); - }) - .sort(reorderSlashOptions(searchText)), - }; - }) - .filter((group) => group.children.length > 0); - }, [searchText, groupTypeToLabelMap, typeToLabelIconMap, renderOptionContent]); - - return { - options, - onConfirm, - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/slash_command_panel/SlashCommandPanel.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/slash_command_panel/SlashCommandPanel.tsx deleted file mode 100644 index b09af97b39..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/slash_command_panel/SlashCommandPanel.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import React, { useCallback, useRef } from 'react'; -import { - initialAnchorOrigin, - initialTransformOrigin, - PanelPopoverProps, - PanelProps, -} from '$app/components/editor/components/tools/command_panel/Command.hooks'; -import Popover from '@mui/material/Popover'; -import SlashCommandPanelContent from '$app/components/editor/components/tools/command_panel/slash_command_panel/SlashCommandPanelContent'; -import { useSlate } from 'slate-react'; -import usePopoverAutoPosition from '$app/components/_shared/popover/Popover.hooks'; - -export function SlashCommandPanel({ anchorPosition, closePanel, searchText }: PanelProps) { - const ref = useRef(null); - const editor = useSlate(); - - const open = Boolean(anchorPosition); - - const handleClose = useCallback( - (deleteText?: boolean) => { - closePanel(deleteText); - }, - [closePanel] - ); - - const { - paperHeight, - paperWidth, - anchorPosition: newAnchorPosition, - transformOrigin, - anchorOrigin, - isEntered, - } = usePopoverAutoPosition({ - initialPaperWidth: 220, - initialPaperHeight: 360, - anchorPosition, - initialTransformOrigin, - initialAnchorOrigin, - open, - }); - - return ( -
- {open && ( - { - const selection = editor.selection; - - handleClose(false); - - if (selection) { - editor.select(selection); - } - }} - > - - - )} -
- ); -} - -export default SlashCommandPanel; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/slash_command_panel/SlashCommandPanelContent.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/slash_command_panel/SlashCommandPanelContent.tsx deleted file mode 100644 index 256e82f811..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/slash_command_panel/SlashCommandPanelContent.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import React, { useEffect, useRef } from 'react'; -import KeyboardNavigation from '$app/components/_shared/keyboard_navigation/KeyboardNavigation'; -import { useSlashCommandPanel } from '$app/components/editor/components/tools/command_panel/slash_command_panel/SlashCommandPanel.hooks'; -import { useSlateStatic } from 'slate-react'; -import { SlashOptionType } from '$app/components/editor/components/tools/command_panel/slash_command_panel/const'; - -const noResultBuffer = 2; - -function SlashCommandPanelContent({ - closePanel, - searchText, - maxHeight, - width, -}: { - closePanel: (deleteText?: boolean) => void; - searchText: string; - maxHeight: number; - width: number; -}) { - const scrollRef = useRef(null); - - const { options, onConfirm } = useSlashCommandPanel({ - closePanel, - searchText, - }); - - // Used to keep track of how many times the user has typed and not found any result - const noResultCount = useRef(0); - - const editor = useSlateStatic(); - - useEffect(() => { - const { insertText, deleteBackward } = editor; - - editor.insertText = (text, opts) => { - // close panel if track of no result is greater than buffer - if (noResultCount.current >= noResultBuffer) { - closePanel(false); - } - - if (options.length === 0) { - noResultCount.current += 1; - } - - insertText(text, opts); - }; - - editor.deleteBackward = (unit) => { - // reset no result count - if (noResultCount.current > 0) { - noResultCount.current -= 1; - } - - // close panel if no text - if (!searchText) { - closePanel(true); - return; - } - - deleteBackward(unit); - }; - - return () => { - editor.insertText = insertText; - editor.deleteBackward = deleteBackward; - }; - }, [closePanel, editor, searchText, options.length]); - - return ( -
- onConfirm(key as SlashOptionType)} - options={options} - disableFocus={true} - /> -
- ); -} - -export default SlashCommandPanelContent; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/slash_command_panel/const.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/slash_command_panel/const.ts deleted file mode 100644 index 7dfaa2b4a0..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/slash_command_panel/const.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { EditorNodeType } from '$app/application/document/document.types'; - -export enum SlashCommandPanelTab { - BASIC = 'basic', - MEDIA = 'media', - DATABASE = 'database', - ADVANCED = 'advanced', -} - -export enum SlashOptionType { - Paragraph, - TodoList, - Heading1, - Heading2, - Heading3, - BulletedList, - NumberedList, - Quote, - ToggleList, - Divider, - Callout, - Code, - Grid, - MathEquation, - Image, -} - -export const slashOptionGroup = [ - { - key: SlashCommandPanelTab.BASIC, - options: [ - SlashOptionType.Paragraph, - SlashOptionType.TodoList, - SlashOptionType.Heading1, - SlashOptionType.Heading2, - SlashOptionType.Heading3, - SlashOptionType.BulletedList, - SlashOptionType.NumberedList, - SlashOptionType.Quote, - SlashOptionType.ToggleList, - SlashOptionType.Divider, - SlashOptionType.Callout, - ], - }, - { - key: SlashCommandPanelTab.MEDIA, - options: [SlashOptionType.Code, SlashOptionType.Image], - }, - { - key: SlashCommandPanelTab.DATABASE, - options: [SlashOptionType.Grid], - }, - { - key: SlashCommandPanelTab.ADVANCED, - options: [SlashOptionType.MathEquation], - }, -]; -export const slashOptionMapToEditorNodeType = { - [SlashOptionType.Paragraph]: EditorNodeType.Paragraph, - [SlashOptionType.TodoList]: EditorNodeType.TodoListBlock, - [SlashOptionType.Heading1]: EditorNodeType.HeadingBlock, - [SlashOptionType.Heading2]: EditorNodeType.HeadingBlock, - [SlashOptionType.Heading3]: EditorNodeType.HeadingBlock, - [SlashOptionType.BulletedList]: EditorNodeType.BulletedListBlock, - [SlashOptionType.NumberedList]: EditorNodeType.NumberedListBlock, - [SlashOptionType.Quote]: EditorNodeType.QuoteBlock, - [SlashOptionType.ToggleList]: EditorNodeType.ToggleListBlock, - [SlashOptionType.Divider]: EditorNodeType.DividerBlock, - [SlashOptionType.Callout]: EditorNodeType.CalloutBlock, - [SlashOptionType.Code]: EditorNodeType.CodeBlock, - [SlashOptionType.Grid]: EditorNodeType.GridBlock, - [SlashOptionType.MathEquation]: EditorNodeType.EquationBlock, - [SlashOptionType.Image]: EditorNodeType.ImageBlock, -}; -export const headingTypeToLevelMap: Record = { - [SlashOptionType.Heading1]: 1, - [SlashOptionType.Heading2]: 2, - [SlashOptionType.Heading3]: 3, -}; -export const headingTypes = [SlashOptionType.Heading1, SlashOptionType.Heading2, SlashOptionType.Heading3]; - -export const SlashAliases = { - [SlashOptionType.Paragraph]: ['paragraph', 'text', 'block', 'textblock'], - [SlashOptionType.TodoList]: [ - 'list', - 'todo', - 'todolist', - 'checkbox', - 'block', - 'todoblock', - 'checkboxblock', - 'todolistblock', - ], - [SlashOptionType.Heading1]: ['h1', 'heading1', 'block', 'headingblock', 'h1block'], - [SlashOptionType.Heading2]: ['h2', 'heading2', 'block', 'headingblock', 'h2block'], - [SlashOptionType.Heading3]: ['h3', 'heading3', 'block', 'headingblock', 'h3block'], - [SlashOptionType.BulletedList]: [ - 'list', - 'bulleted', - 'block', - 'bulletedlist', - 'bulletedblock', - 'listblock', - 'bulletedlistblock', - 'bulletelist', - ], - [SlashOptionType.NumberedList]: [ - 'list', - 'numbered', - 'block', - 'numberedlist', - 'numberedblock', - 'listblock', - 'numberedlistblock', - 'numberlist', - ], - [SlashOptionType.Quote]: ['quote', 'block', 'quoteblock'], - [SlashOptionType.ToggleList]: ['list', 'toggle', 'block', 'togglelist', 'toggleblock', 'listblock', 'togglelistblock'], - [SlashOptionType.Divider]: ['divider', 'hr', 'block', 'dividerblock', 'line', 'lineblock'], - [SlashOptionType.Callout]: ['callout', 'info', 'block', 'calloutblock'], - [SlashOptionType.Code]: ['code', 'code', 'block', 'codeblock', 'media'], - [SlashOptionType.Grid]: ['grid', 'table', 'block', 'gridblock', 'database'], - [SlashOptionType.MathEquation]: [ - 'math', - 'equation', - 'block', - 'mathblock', - 'mathequation', - 'mathequationblock', - 'advanced', - ], - [SlashOptionType.Image]: ['img', 'image', 'block', 'imageblock', 'media'], -}; - -export const reorderSlashOptions = (searchText: string) => { - return ( - a: { - key: SlashOptionType; - }, - b: { - key: SlashOptionType; - } - ) => { - const compareIndex = (option: SlashOptionType) => { - const aliases = SlashAliases[option]; - - if (aliases) { - for (const alias of aliases) { - if (alias.startsWith(searchText)) { - return -1; - } - } - } - - return 0; - }; - - const compareLength = (option: SlashOptionType) => { - const aliases = SlashAliases[option]; - - if (aliases) { - for (const alias of aliases) { - if (alias.length < searchText.length) { - return -1; - } - } - } - - return 0; - }; - - return compareIndex(a.key) - compareIndex(b.key) || compareLength(a.key) - compareLength(b.key); - }; -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/slash_command_panel/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/slash_command_panel/index.ts deleted file mode 100644 index 688a6ffb7d..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/slash_command_panel/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './SlashCommandPanel'; -export * from './SlashCommandPanelContent'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/utils.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/utils.ts deleted file mode 100644 index 65a095dc58..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/command_panel/utils.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { ReactEditor } from 'slate-react'; - -export function getPanelPosition(editor: ReactEditor) { - const { selection } = editor; - - const isFocused = ReactEditor.isFocused(editor); - - if (!selection || !isFocused) { - return null; - } - - const domSelection = window.getSelection(); - const rangeCount = domSelection?.rangeCount; - - if (!rangeCount) return null; - - const domRange = rangeCount > 0 ? domSelection.getRangeAt(0) : undefined; - - const rect = domRange?.getBoundingClientRect(); - - if (!rect) return null; - const nodeDom = domSelection.anchorNode?.parentElement?.closest('.text-element'); - const height = (nodeDom?.getBoundingClientRect().height ?? 0) + 8; - - return { - ...rect, - height, - top: rect.top, - left: rect.left, - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/popover.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/popover.ts deleted file mode 100644 index 2b6a715baa..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/popover.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { PopoverProps } from '@mui/material/Popover'; - -export const PopoverCommonProps: Partial = { - keepMounted: false, - disableAutoFocus: true, - disableEnforceFocus: true, - disableRestoreFocus: true, -}; - -export const PopoverPreventBlurProps: Partial = { - ...PopoverCommonProps, - - onMouseDown: (e) => { - // prevent editor blur - e.preventDefault(); - e.stopPropagation(); - }, -}; - -export const PopoverNoBackdropProps: Partial = { - ...PopoverCommonProps, - sx: { - pointerEvents: 'none', - }, - PaperProps: { - style: { - pointerEvents: 'auto', - }, - }, - onMouseDown: (e) => { - // prevent editor blur - e.stopPropagation(); - }, -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/SelectionActions.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/SelectionActions.tsx deleted file mode 100644 index 9d7c19b999..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/SelectionActions.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import React from 'react'; - -import { Paragraph } from '$app/components/editor/components/tools/selection_toolbar/actions/paragraph'; -import { Heading } from '$app/components/editor/components/tools/selection_toolbar/actions/heading'; -import { Divider } from '@mui/material'; -import { Bold } from '$app/components/editor/components/tools/selection_toolbar/actions/bold'; -import { Italic } from '$app/components/editor/components/tools/selection_toolbar/actions/italic'; -import { Underline } from '$app/components/editor/components/tools/selection_toolbar/actions/underline'; -import { StrikeThrough } from '$app/components/editor/components/tools/selection_toolbar/actions/strikethrough'; -import { InlineCode } from '$app/components/editor/components/tools/selection_toolbar/actions/inline_code'; -import { Formula } from '$app/components/editor/components/tools/selection_toolbar/actions/formula'; -import { TodoList } from '$app/components/editor/components/tools/selection_toolbar/actions/todo_list'; -import { Quote } from '$app/components/editor/components/tools/selection_toolbar/actions/quote'; -import { ToggleList } from '$app/components/editor/components/tools/selection_toolbar/actions/toggle_list'; -import { BulletedList } from '$app/components/editor/components/tools/selection_toolbar/actions/bulleted_list'; -import { NumberedList } from '$app/components/editor/components/tools/selection_toolbar/actions/numbered_list'; -import { Href, LinkActions } from '$app/components/editor/components/tools/selection_toolbar/actions/href'; -import { Align } from '$app/components/editor/components/tools/selection_toolbar/actions/align'; -import { Color } from '$app/components/editor/components/tools/selection_toolbar/actions/color'; - -function SelectionActions({ - isAcrossBlocks, - storeSelection, - restoreSelection, - isIncludeRoot, -}: { - storeSelection: () => void; - restoreSelection: () => void; - isAcrossBlocks: boolean; - visible: boolean; - isIncludeRoot: boolean; -}) { - if (isIncludeRoot) return null; - return ( -
- {!isAcrossBlocks && ( - <> - - - - - )} - - - - - - {!isAcrossBlocks && ( - <> - - - - )} - - {!isAcrossBlocks && ( - <> - - - - - - - - )} - {!isAcrossBlocks && } - - - -
- ); -} - -export default SelectionActions; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/SelectionToolbar.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/SelectionToolbar.hooks.ts deleted file mode 100644 index 58834db6d5..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/SelectionToolbar.hooks.ts +++ /dev/null @@ -1,239 +0,0 @@ -import { ReactEditor, useFocused, useSlate } from 'slate-react'; -import { MutableRefObject, useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { getSelectionPosition } from '$app/components/editor/components/tools/selection_toolbar/utils'; -import debounce from 'lodash-es/debounce'; -import { CustomEditor } from '$app/components/editor/command'; -import { BaseRange, Range as SlateRange } from 'slate'; -import { useDecorateDispatch } from '$app/components/editor/stores/decorate'; - -const DELAY = 300; - -export function useSelectionToolbar(ref: MutableRefObject) { - const editor = useSlate() as ReactEditor; - const isDraggingRef = useRef(false); - const [isAcrossBlocks, setIsAcrossBlocks] = useState(false); - const [visible, setVisible] = useState(false); - const isFocusedEditor = useFocused(); - const isIncludeRoot = CustomEditor.selectionIncludeRoot(editor); - - // paint the selection when the editor is blurred - const { add: addDecorate, clear: clearDecorate, getStaticState } = useDecorateDispatch(); - - // Restore selection after the editor is focused - const restoreSelection = useCallback(() => { - const decorateState = getStaticState(); - - if (!decorateState) return; - - editor.select({ - ...decorateState.range, - }); - - clearDecorate(); - ReactEditor.focus(editor); - }, [getStaticState, clearDecorate, editor]); - - // Store selection when the editor is blurred - const storeSelection = useCallback(() => { - addDecorate({ - range: editor.selection as BaseRange, - class_name: 'bg-content-blue-100', - }); - }, [addDecorate, editor]); - - const closeToolbar = useCallback(() => { - const el = ref.current; - - if (!el) { - return; - } - - restoreSelection(); - - setVisible(false); - el.style.opacity = '0'; - el.style.pointerEvents = 'none'; - }, [ref, restoreSelection]); - - const recalculatePosition = useCallback(() => { - const el = ref.current; - - if (!el) { - return; - } - - const position = getSelectionPosition(editor); - - if (!position) { - closeToolbar(); - return; - } - - const slateEditorDom = ReactEditor.toDOMNode(editor, editor); - - setVisible(true); - el.style.opacity = '1'; - - // if dragging, disable pointer events - if (isDraggingRef.current) { - el.style.pointerEvents = 'none'; - } else { - el.style.pointerEvents = 'auto'; - } - - // If toolbar is out of editor, move it to the top - el.style.top = `${position.top + slateEditorDom.offsetTop - el.offsetHeight}px`; - - const left = position.left + slateEditorDom.offsetLeft; - - // If toolbar is out of editor, move it to the left edge of the editor - if (left < 0) { - el.style.left = '0'; - return; - } - - const right = left + el.offsetWidth; - - // If toolbar is out of editor, move the right edge to the right edge of the editor - if (right > slateEditorDom.offsetWidth) { - el.style.left = `${slateEditorDom.offsetWidth - el.offsetWidth}px`; - return; - } - - el.style.left = `${left}px`; - }, [closeToolbar, editor, ref]); - - const debounceRecalculatePosition = useMemo(() => debounce(recalculatePosition, DELAY), [recalculatePosition]); - - // eslint-disable-next-line react-hooks/exhaustive-deps - useEffect(() => { - const decorateState = getStaticState(); - - if (decorateState) { - setIsAcrossBlocks(false); - return; - } - - const { selection } = editor; - - const close = () => { - debounceRecalculatePosition.cancel(); - closeToolbar(); - }; - - if (isIncludeRoot || !isFocusedEditor || !selection || SlateRange.isCollapsed(selection)) { - close(); - return; - } - - // There has a bug which the text of selection is empty when the selection include inline blocks - const isEmptyText = !CustomEditor.includeInlineBlocks(editor) && editor.string(selection) === ''; - - if (isEmptyText) { - close(); - return; - } - - setIsAcrossBlocks(CustomEditor.isMultipleBlockSelected(editor, true)); - debounceRecalculatePosition(); - }); - - // Update drag status - useEffect(() => { - const el = ReactEditor.toDOMNode(editor, editor); - - const toolbar = ref.current; - - if (!el || !toolbar) { - return; - } - - const onMouseDown = () => { - isDraggingRef.current = true; - }; - - const onMouseUp = () => { - if (visible) { - toolbar.style.pointerEvents = 'auto'; - } - - isDraggingRef.current = false; - }; - - el.addEventListener('mousedown', onMouseDown); - document.addEventListener('mouseup', onMouseUp); - - return () => { - el.removeEventListener('mousedown', onMouseDown); - document.removeEventListener('mouseup', onMouseUp); - }; - }, [visible, editor, ref]); - - // Close toolbar when press ESC - useEffect(() => { - const slateEditorDom = ReactEditor.toDOMNode(editor, editor); - const onKeyDown = (e: KeyboardEvent) => { - // Close toolbar when press ESC - if (e.key === 'Escape') { - e.preventDefault(); - e.stopPropagation(); - editor.collapse({ - edge: 'end', - }); - debounceRecalculatePosition.cancel(); - closeToolbar(); - } - }; - - if (visible) { - slateEditorDom.addEventListener('keydown', onKeyDown); - } else { - slateEditorDom.removeEventListener('keydown', onKeyDown); - } - - return () => { - slateEditorDom.removeEventListener('keydown', onKeyDown); - }; - }, [closeToolbar, debounceRecalculatePosition, editor, visible]); - - // Recalculate position when the scroll container is scrolled - useEffect(() => { - const slateEditorDom = ReactEditor.toDOMNode(editor, editor); - const scrollContainer = slateEditorDom.closest('.appflowy-scroll-container'); - - if (!visible) return; - if (!scrollContainer) return; - const handleScroll = () => { - if (isDraggingRef.current) return; - - const domSelection = window.getSelection(); - const rangeCount = domSelection?.rangeCount; - - if (!rangeCount) return null; - - const domRange = rangeCount > 0 ? domSelection.getRangeAt(0) : undefined; - - const rangeRect = domRange?.getBoundingClientRect(); - - // Stop calculating when the range is out of the window - if (!rangeRect?.bottom || rangeRect.bottom < 0) { - return; - } - - recalculatePosition(); - }; - - scrollContainer.addEventListener('scroll', handleScroll); - return () => { - scrollContainer.removeEventListener('scroll', handleScroll); - }; - }, [visible, editor, recalculatePosition]); - - return { - visible, - restoreSelection, - storeSelection, - isAcrossBlocks, - isIncludeRoot, - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/SelectionToolbar.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/SelectionToolbar.tsx deleted file mode 100644 index d4ca9c9de0..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/SelectionToolbar.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React, { memo, useRef } from 'react'; -import { useSelectionToolbar } from '$app/components/editor/components/tools/selection_toolbar/SelectionToolbar.hooks'; -import SelectionActions from '$app/components/editor/components/tools/selection_toolbar/SelectionActions'; -import withErrorBoundary from '$app/components/_shared/error_boundary/withError'; - -const Toolbar = memo(() => { - const ref = useRef(null); - - const { visible, restoreSelection, storeSelection, isAcrossBlocks, isIncludeRoot } = useSelectionToolbar(ref); - - return ( -
{ - // prevent toolbar from taking focus away from editor - e.preventDefault(); - }} - > - -
- ); -}); - -export const SelectionToolbar = withErrorBoundary(Toolbar); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/_shared/ActionButton.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/_shared/ActionButton.tsx deleted file mode 100644 index 3f86d1eab9..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/_shared/ActionButton.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import React, { forwardRef } from 'react'; -import IconButton, { IconButtonProps } from '@mui/material/IconButton'; -import { Tooltip } from '@mui/material'; - -const ActionButton = forwardRef< - HTMLButtonElement, - { - tooltip: string | React.ReactNode; - onClick?: (e: React.MouseEvent) => void; - children: React.ReactNode; - active?: boolean; - } & IconButtonProps ->(({ tooltip, onClick, disabled, children, active, className, ...props }, ref) => { - return ( - - - {children} - - - ); -}); - -export default ActionButton; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/align/Align.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/align/Align.tsx deleted file mode 100644 index 23917e146b..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/align/Align.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import React, { useCallback, useMemo, useState } from 'react'; -import Tooltip from '@mui/material/Tooltip'; -import { ReactComponent as AlignLeftSvg } from '$app/assets/align-left.svg'; -import { ReactComponent as AlignCenterSvg } from '$app/assets/align-center.svg'; -import { ReactComponent as AlignRightSvg } from '$app/assets/align-right.svg'; -import ActionButton from '$app/components/editor/components/tools/selection_toolbar/actions/_shared/ActionButton'; -import { useTranslation } from 'react-i18next'; -import { CustomEditor } from '$app/components/editor/command'; -import { useSlateStatic } from 'slate-react'; -import { IconButton } from '@mui/material'; -import { ReactComponent as MoreSvg } from '$app/assets/more.svg'; - -export function Align() { - const { t } = useTranslation(); - const editor = useSlateStatic(); - const align = CustomEditor.getAlign(editor); - const [open, setOpen] = useState(false); - - const handleClose = useCallback(() => { - setOpen(false); - }, []); - - const handleOpen = useCallback(() => { - setOpen(true); - }, []); - - const Icon = useMemo(() => { - switch (align) { - case 'left': - return AlignLeftSvg; - case 'center': - return AlignCenterSvg; - case 'right': - return AlignRightSvg; - default: - return AlignLeftSvg; - } - }, [align]); - - const toggleAlign = useCallback( - (align: string) => { - return () => { - CustomEditor.toggleAlign(editor, align); - handleClose(); - }; - }, - [editor, handleClose] - ); - - const getAlignIcon = useCallback((key: string) => { - switch (key) { - case 'left': - return ; - case 'center': - return ; - case 'right': - return ; - default: - return ; - } - }, []); - - return ( - - {['left', 'center', 'right'].map((key) => { - return ( - - {getAlignIcon(key)} - - ); - })} -
- } - > - -
- - -
-
- - ); -} - -export default Align; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/align/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/align/index.ts deleted file mode 100644 index 6cba19d7bd..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/align/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Align'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/bold/Bold.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/bold/Bold.tsx deleted file mode 100644 index 22be9f970a..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/bold/Bold.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React, { useCallback, useMemo } from 'react'; -import ActionButton from '$app/components/editor/components/tools/selection_toolbar/actions/_shared/ActionButton'; -import { useTranslation } from 'react-i18next'; -import { useSlateStatic } from 'slate-react'; -import { CustomEditor } from '$app/components/editor/command'; -import { ReactComponent as BoldSvg } from '$app/assets/bold.svg'; -import { EditorMarkFormat } from '$app/application/document/document.types'; -import { createHotKeyLabel, HOT_KEY_NAME } from '$app/utils/hotkeys'; - -export function Bold() { - const { t } = useTranslation(); - const editor = useSlateStatic(); - const isActivated = CustomEditor.isMarkActive(editor, EditorMarkFormat.Bold); - - const modifier = useMemo(() => createHotKeyLabel(HOT_KEY_NAME.BOLD), []); - const onClick = useCallback(() => { - CustomEditor.toggleMark(editor, { - key: EditorMarkFormat.Bold, - value: true, - }); - }, [editor]); - - return ( - -
{t('toolbar.bold')}
-
{modifier}
- - } - > - -
- ); -} - -export default Bold; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/bold/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/bold/index.ts deleted file mode 100644 index 6ef457faaa..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/bold/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Bold'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/bulleted_list/BulletedList.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/bulleted_list/BulletedList.tsx deleted file mode 100644 index f35f2aeeea..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/bulleted_list/BulletedList.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React, { useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useSlateStatic } from 'slate-react'; -import { EditorNodeType } from '$app/application/document/document.types'; -import { CustomEditor } from '$app/components/editor/command'; -import ActionButton from '$app/components/editor/components/tools/selection_toolbar/actions/_shared/ActionButton'; -import { ReactComponent as BulletedListSvg } from '$app/assets/list.svg'; - -export function BulletedList() { - const { t } = useTranslation(); - const editor = useSlateStatic(); - const isActivated = CustomEditor.isBlockActive(editor, EditorNodeType.BulletedListBlock); - - const onClick = useCallback(() => { - CustomEditor.turnToBlock(editor, { - type: EditorNodeType.BulletedListBlock, - }); - }, [editor]); - - return ( - - - - ); -} - -export default BulletedList; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/bulleted_list/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/bulleted_list/index.ts deleted file mode 100644 index 2095dff308..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/bulleted_list/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './BulletedList'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/color/Color.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/color/Color.tsx deleted file mode 100644 index 60c44423bd..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/color/Color.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React, { useCallback, useMemo, useRef, useState } from 'react'; -import ActionButton from '$app/components/editor/components/tools/selection_toolbar/actions/_shared/ActionButton'; -import { CustomEditor } from '$app/components/editor/command'; -import { useTranslation } from 'react-i18next'; -import { useSlateStatic } from 'slate-react'; -import ColorLensOutlinedIcon from '@mui/icons-material/ColorLensOutlined'; -import { ReactComponent as MoreSvg } from '$app/assets/more.svg'; -import { EditorMarkFormat } from '$app/application/document/document.types'; -import debounce from 'lodash-es/debounce'; -import ColorPopover from './ColorPopover'; - -export function Color(_: { onOpen?: () => void; onClose?: () => void }) { - const { t } = useTranslation(); - const editor = useSlateStatic(); - const [open, setOpen] = useState(false); - const ref = useRef(null); - - const isActivated = - CustomEditor.isMarkActive(editor, EditorMarkFormat.FontColor) || - CustomEditor.isMarkActive(editor, EditorMarkFormat.BgColor); - const debouncedClose = useMemo( - () => - debounce(() => { - setOpen(false); - }, 200), - [] - ); - - const handleOpen = useCallback(() => { - debouncedClose.cancel(); - setOpen(true); - }, [debouncedClose]); - - return ( - <> - -
- - -
-
- {open && ref.current && ( - - )} - - ); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/color/ColorPopover.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/color/ColorPopover.tsx deleted file mode 100644 index 9f007dc7b5..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/color/ColorPopover.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import React, { useCallback } from 'react'; -import { PopoverNoBackdropProps } from '$app/components/editor/components/tools/popover'; -import { ColorPicker } from '$app/components/editor/components/tools/_shared'; -import { Popover } from '@mui/material'; -import { PopoverOrigin } from '@mui/material/Popover/Popover'; -import { EditorMarkFormat } from '$app/application/document/document.types'; -import { addMark, removeMark } from 'slate'; -import { useSlateStatic } from 'slate-react'; -import { DebouncedFunc } from 'lodash-es/debounce'; -import usePopoverAutoPosition from '$app/components/_shared/popover/Popover.hooks'; - -const initialOrigin: { - transformOrigin?: PopoverOrigin; - anchorOrigin?: PopoverOrigin; -} = { - anchorOrigin: { - vertical: 'bottom', - horizontal: 'center', - }, - transformOrigin: { - vertical: 'top', - horizontal: 'center', - }, -}; - -function ColorPopover({ - open, - anchorEl, - debounceClose, -}: { - open: boolean; - onOpen: () => void; - anchorEl: HTMLButtonElement | null; - debounceClose: DebouncedFunc<() => void>; -}) { - const editor = useSlateStatic(); - const handleChange = useCallback( - (format: EditorMarkFormat.FontColor | EditorMarkFormat.BgColor, color: string) => { - if (color) { - addMark(editor, format, color); - } else { - removeMark(editor, format); - } - }, - [editor] - ); - - const { paperHeight, transformOrigin, anchorOrigin, isEntered } = usePopoverAutoPosition({ - initialPaperWidth: 200, - initialPaperHeight: 420, - anchorEl, - initialAnchorOrigin: initialOrigin.anchorOrigin, - initialTransformOrigin: initialOrigin.transformOrigin, - open, - }); - - return ( - { - e.stopPropagation(); - if (e.key === 'Escape') { - debounceClose(); - } - }} - onMouseDown={(e) => { - e.preventDefault(); - }} - onMouseEnter={() => { - debounceClose.cancel(); - }} - onMouseLeave={debounceClose} - > - - - ); -} - -export default ColorPopover; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/color/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/color/index.ts deleted file mode 100644 index 0fd619bc86..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/color/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Color'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/formula/Formula.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/formula/Formula.tsx deleted file mode 100644 index c7bfc11352..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/formula/Formula.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React, { useCallback } from 'react'; -import ActionButton from '$app/components/editor/components/tools/selection_toolbar/actions/_shared/ActionButton'; -import { useTranslation } from 'react-i18next'; -import { useSlateStatic } from 'slate-react'; -import { CustomEditor } from '$app/components/editor/command'; -import Functions from '@mui/icons-material/Functions'; -import { useEditorInlineBlockState } from '$app/components/editor/stores'; - -export function Formula() { - const { t } = useTranslation(); - const editor = useSlateStatic(); - const isActivatedMention = CustomEditor.isMentionActive(editor); - - const formulaMatch = CustomEditor.formulaActiveNode(editor); - const isActivated = !isActivatedMention && CustomEditor.isFormulaActive(editor); - - const { setRange, openPopover } = useEditorInlineBlockState('formula'); - const onClick = useCallback(() => { - let selection = editor.selection; - - if (!selection) return; - if (formulaMatch) { - selection = editor.range(formulaMatch[1]); - editor.select(selection); - } else { - CustomEditor.toggleFormula(editor); - } - - requestAnimationFrame(() => { - const selection = editor.selection; - - if (!selection) return; - - setRange(selection); - openPopover(); - }); - }, [editor, formulaMatch, setRange, openPopover]); - - return ( - - - - ); -} - -export default Formula; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/formula/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/formula/index.ts deleted file mode 100644 index dc4ad2cd03..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/formula/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Formula'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/heading/Heading.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/heading/Heading.tsx deleted file mode 100644 index 1fc639c41f..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/heading/Heading.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import React, { useCallback } from 'react'; -import ActionButton from '$app/components/editor/components/tools/selection_toolbar/actions/_shared/ActionButton'; -import { ReactComponent as Heading1Svg } from '$app/assets/h1.svg'; -import { ReactComponent as Heading2Svg } from '$app/assets/h2.svg'; -import { ReactComponent as Heading3Svg } from '$app/assets/h3.svg'; -import { useTranslation } from 'react-i18next'; -import { CustomEditor } from '$app/components/editor/command'; -import { EditorNodeType, HeadingNode } from '$app/application/document/document.types'; -import { useSlateStatic } from 'slate-react'; -import { getBlock } from '$app/components/editor/plugins/utils'; - -export function Heading() { - const { t } = useTranslation(); - const editor = useSlateStatic(); - const toHeading = useCallback( - (level: number) => { - return () => { - CustomEditor.turnToBlock(editor, { - type: EditorNodeType.HeadingBlock, - data: { - level, - }, - }); - }; - }, - [editor] - ); - - const isActivated = useCallback( - (level: number) => { - const node = getBlock(editor) as HeadingNode; - - if (!node) return false; - const isBlock = CustomEditor.isBlockActive(editor, EditorNodeType.HeadingBlock); - - return isBlock && node.data.level === level; - }, - [editor] - ); - - return ( -
- - - - - - - - - -
- ); -} - -export default Heading; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/heading/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/heading/index.ts deleted file mode 100644 index 6406e7b07f..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/heading/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Heading'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/href/Href.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/href/Href.tsx deleted file mode 100644 index e7412a909e..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/href/Href.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React, { useCallback, useMemo } from 'react'; -import ActionButton from '$app/components/editor/components/tools/selection_toolbar/actions/_shared/ActionButton'; -import { useTranslation } from 'react-i18next'; -import { useSlateStatic } from 'slate-react'; -import { CustomEditor } from '$app/components/editor/command'; -import { ReactComponent as LinkSvg } from '$app/assets/link.svg'; -import { EditorMarkFormat } from '$app/application/document/document.types'; -import { useDecorateDispatch } from '$app/components/editor/stores'; -import { getModifier } from '$app/utils/hotkeys'; - -export function Href() { - const { t } = useTranslation(); - const editor = useSlateStatic(); - const isActivatedInline = CustomEditor.isInlineActive(editor); - const isActivated = !isActivatedInline && CustomEditor.isMarkActive(editor, EditorMarkFormat.Href); - - const { add: addDecorate } = useDecorateDispatch(); - const onClick = useCallback(() => { - if (!editor.selection) return; - addDecorate({ - range: editor.selection, - class_name: 'bg-content-blue-100 rounded', - type: 'link', - }); - }, [addDecorate, editor]); - - const tooltip = useMemo(() => { - const modifier = getModifier(); - - return ( - <> -
{t('editor.link')}
-
{`${modifier} + K`}
- - ); - }, [t]); - - return ( - <> - - - - - ); -} - -export default Href; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/href/LinkActions.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/href/LinkActions.tsx deleted file mode 100644 index b77a249051..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/href/LinkActions.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import React, { useCallback, useMemo } from 'react'; -import { useDecorateDispatch, useDecorateState } from '$app/components/editor/stores'; -import { ReactEditor, useSlateStatic } from 'slate-react'; -import { Editor } from 'slate'; -import { LinkEditPopover } from '$app/components/editor/components/inline_nodes/link'; - -export function LinkActions() { - const editor = useSlateStatic(); - const decorateState = useDecorateState('link'); - const openEditPopover = !!decorateState; - const { clear: clearDecorate } = useDecorateDispatch(); - - const anchorPosition = useMemo(() => { - const range = decorateState?.range; - - if (!range) return; - - const domRange = ReactEditor.toDOMRange(editor, range); - - const rect = domRange.getBoundingClientRect(); - - return { - top: rect.top, - left: rect.left, - height: rect.height, - }; - }, [decorateState?.range, editor]); - - const defaultHref = useMemo(() => { - const range = decorateState?.range; - - if (!range) return ''; - - const marks = Editor.marks(editor); - - return marks?.href || Editor.string(editor, range); - }, [decorateState?.range, editor]); - - const handleEditPopoverClose = useCallback(() => { - const range = decorateState?.range; - - clearDecorate(); - if (range) { - ReactEditor.focus(editor); - editor.select(range); - } - }, [clearDecorate, decorateState?.range, editor]); - - if (!openEditPopover) return null; - return ( - - ); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/href/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/href/index.ts deleted file mode 100644 index 9a7210c140..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/href/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './Href'; -export * from './LinkActions'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/inline_code/InlineCode.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/inline_code/InlineCode.tsx deleted file mode 100644 index 3cf9c7ed85..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/inline_code/InlineCode.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React, { useCallback, useMemo } from 'react'; -import ActionButton from '$app/components/editor/components/tools/selection_toolbar/actions/_shared/ActionButton'; -import { useTranslation } from 'react-i18next'; -import { useSlateStatic } from 'slate-react'; -import { CustomEditor } from '$app/components/editor/command'; -import { ReactComponent as CodeSvg } from '$app/assets/inline-code.svg'; -import { EditorMarkFormat } from '$app/application/document/document.types'; -import { createHotKeyLabel, HOT_KEY_NAME } from '$app/utils/hotkeys'; - -export function InlineCode() { - const { t } = useTranslation(); - const editor = useSlateStatic(); - const isActivated = CustomEditor.isMarkActive(editor, EditorMarkFormat.Code); - const modifier = useMemo(() => createHotKeyLabel(HOT_KEY_NAME.CODE), []); - - const onClick = useCallback(() => { - CustomEditor.toggleMark(editor, { - key: EditorMarkFormat.Code, - value: true, - }); - }, [editor]); - - return ( - -
{t('editor.embedCode')}
-
{modifier}
- - } - > - -
- ); -} - -export default InlineCode; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/inline_code/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/inline_code/index.ts deleted file mode 100644 index 9a4c4930c7..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/inline_code/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './InlineCode'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/italic/Italic.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/italic/Italic.tsx deleted file mode 100644 index 89fff40e6f..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/italic/Italic.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React, { useCallback, useMemo } from 'react'; -import ActionButton from '$app/components/editor/components/tools/selection_toolbar/actions/_shared/ActionButton'; -import { useTranslation } from 'react-i18next'; -import { useSlateStatic } from 'slate-react'; -import { CustomEditor } from '$app/components/editor/command'; -import { ReactComponent as ItalicSvg } from '$app/assets/italic.svg'; -import { EditorMarkFormat } from '$app/application/document/document.types'; -import { createHotKeyLabel, HOT_KEY_NAME } from '$app/utils/hotkeys'; - -export function Italic() { - const { t } = useTranslation(); - const editor = useSlateStatic(); - const isActivated = CustomEditor.isMarkActive(editor, EditorMarkFormat.Italic); - const modifier = useMemo(() => createHotKeyLabel(HOT_KEY_NAME.ITALIC), []); - - const onClick = useCallback(() => { - CustomEditor.toggleMark(editor, { - key: EditorMarkFormat.Italic, - value: true, - }); - }, [editor]); - - return ( - -
{t('toolbar.italic')}
-
{modifier}
- - } - > - -
- ); -} - -export default Italic; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/italic/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/italic/index.ts deleted file mode 100644 index 70bb069b60..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/italic/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Italic'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/numbered_list/NumberedList.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/numbered_list/NumberedList.tsx deleted file mode 100644 index 006247ca8b..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/numbered_list/NumberedList.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React, { useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useSlateStatic } from 'slate-react'; -import { EditorNodeType } from '$app/application/document/document.types'; -import { CustomEditor } from '$app/components/editor/command'; -import ActionButton from '$app/components/editor/components/tools/selection_toolbar/actions/_shared/ActionButton'; -import { ReactComponent as NumberedListSvg } from '$app/assets/numbers.svg'; - -export function NumberedList() { - const { t } = useTranslation(); - const editor = useSlateStatic(); - const isActivated = CustomEditor.isBlockActive(editor, EditorNodeType.NumberedListBlock); - - const onClick = useCallback(() => { - let type = EditorNodeType.NumberedListBlock; - - if (isActivated) { - type = EditorNodeType.Paragraph; - } - - CustomEditor.turnToBlock(editor, { - type, - }); - }, [editor, isActivated]); - - return ( - - - - ); -} - -export default NumberedList; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/numbered_list/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/numbered_list/index.ts deleted file mode 100644 index 6e985ae25b..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/numbered_list/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './NumberedList'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/paragraph/Paragraph.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/paragraph/Paragraph.tsx deleted file mode 100644 index 1ac5610787..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/paragraph/Paragraph.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React, { useCallback } from 'react'; -import ActionButton from '$app/components/editor/components/tools/selection_toolbar/actions/_shared/ActionButton'; -import { useTranslation } from 'react-i18next'; -import { getBlock } from '$app/components/editor/plugins/utils'; -import { CustomEditor } from '$app/components/editor/command'; -import { EditorNodeType } from '$app/application/document/document.types'; -import { useSlateStatic } from 'slate-react'; -import { ReactComponent as ParagraphSvg } from '$app/assets/text.svg'; - -export function Paragraph() { - const { t } = useTranslation(); - const editor = useSlateStatic(); - - const onClick = useCallback(() => { - const node = getBlock(editor); - - if (!node) return; - - CustomEditor.turnToBlock(editor, { - type: EditorNodeType.Paragraph, - }); - }, [editor]); - - const isActive = CustomEditor.isBlockActive(editor, EditorNodeType.Paragraph); - - return ( - - - - ); -} - -export default Paragraph; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/paragraph/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/paragraph/index.ts deleted file mode 100644 index 01752c914c..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/paragraph/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Paragraph'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/quote/Quote.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/quote/Quote.tsx deleted file mode 100644 index 29ad0de104..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/quote/Quote.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import React, { useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useSlateStatic } from 'slate-react'; -import { EditorNodeType } from '$app/application/document/document.types'; -import { CustomEditor } from '$app/components/editor/command'; -import ActionButton from '$app/components/editor/components/tools/selection_toolbar/actions/_shared/ActionButton'; -import { ReactComponent as QuoteSvg } from '$app/assets/quote.svg'; - -export function Quote() { - const { t } = useTranslation(); - const editor = useSlateStatic(); - const isActivated = CustomEditor.isBlockActive(editor, EditorNodeType.QuoteBlock); - - const onClick = useCallback(() => { - let type = EditorNodeType.QuoteBlock; - - if (isActivated) { - type = EditorNodeType.Paragraph; - } - - CustomEditor.turnToBlock(editor, { - type, - }); - }, [editor, isActivated]); - - return ( - - - - ); -} - -export default Quote; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/quote/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/quote/index.ts deleted file mode 100644 index c88e677a53..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/quote/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Quote'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/strikethrough/StrikeThrough.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/strikethrough/StrikeThrough.tsx deleted file mode 100644 index 325f6ac55a..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/strikethrough/StrikeThrough.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React, { useCallback, useMemo } from 'react'; -import ActionButton from '$app/components/editor/components/tools/selection_toolbar/actions/_shared/ActionButton'; -import { useTranslation } from 'react-i18next'; -import { useSlateStatic } from 'slate-react'; -import { CustomEditor } from '$app/components/editor/command'; -import { ReactComponent as StrikeThroughSvg } from '$app/assets/strikethrough.svg'; -import { EditorMarkFormat } from '$app/application/document/document.types'; -import { createHotKeyLabel, HOT_KEY_NAME } from '$app/utils/hotkeys'; - -export function StrikeThrough() { - const { t } = useTranslation(); - const editor = useSlateStatic(); - const isActivated = CustomEditor.isMarkActive(editor, EditorMarkFormat.StrikeThrough); - const modifier = useMemo(() => createHotKeyLabel(HOT_KEY_NAME.STRIKETHROUGH), []); - - const onClick = useCallback(() => { - CustomEditor.toggleMark(editor, { - key: EditorMarkFormat.StrikeThrough, - value: true, - }); - }, [editor]); - - return ( - -
{t('editor.strikethrough')}
-
{modifier}
- - } - > - -
- ); -} - -export default StrikeThrough; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/strikethrough/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/strikethrough/index.ts deleted file mode 100644 index f8314d16e3..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/strikethrough/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './StrikeThrough'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/todo_list/TodoList.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/todo_list/TodoList.tsx deleted file mode 100644 index cd576edafa..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/todo_list/TodoList.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React, { useCallback } from 'react'; -import { useSlateStatic } from 'slate-react'; -import { CustomEditor } from '$app/components/editor/command'; -import { EditorNodeType } from '$app/application/document/document.types'; -import { ReactComponent as TodoListSvg } from '$app/assets/todo-list.svg'; -import ActionButton from '$app/components/editor/components/tools/selection_toolbar/actions/_shared/ActionButton'; -import { useTranslation } from 'react-i18next'; - -export function TodoList() { - const { t } = useTranslation(); - const editor = useSlateStatic(); - - const isActivated = CustomEditor.isBlockActive(editor, EditorNodeType.TodoListBlock); - - const onClick = useCallback(() => { - let type = EditorNodeType.TodoListBlock; - - if (isActivated) { - type = EditorNodeType.Paragraph; - } - - CustomEditor.turnToBlock(editor, { - type, - data: { - checked: false, - }, - }); - }, [editor, isActivated]); - - return ( - - - - ); -} - -export default TodoList; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/todo_list/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/todo_list/index.ts deleted file mode 100644 index f239f43459..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/todo_list/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './TodoList'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/toggle_list/ToggleList.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/toggle_list/ToggleList.tsx deleted file mode 100644 index 4d82652988..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/toggle_list/ToggleList.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React, { useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useSlateStatic } from 'slate-react'; -import { CustomEditor } from '$app/components/editor/command'; -import { EditorNodeType } from '$app/application/document/document.types'; -import ActionButton from '$app/components/editor/components/tools/selection_toolbar/actions/_shared/ActionButton'; -import { ReactComponent as ToggleListSvg } from '$app/assets/show-menu.svg'; - -export function ToggleList() { - const { t } = useTranslation(); - const editor = useSlateStatic(); - const isActivated = CustomEditor.isBlockActive(editor, EditorNodeType.ToggleListBlock); - - const onClick = useCallback(() => { - let type = EditorNodeType.ToggleListBlock; - - if (isActivated) { - type = EditorNodeType.Paragraph; - } - - CustomEditor.turnToBlock(editor, { - type, - data: { - collapsed: false, - }, - }); - }, [editor, isActivated]); - - return ( - - - - ); -} - -export default ToggleList; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/toggle_list/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/toggle_list/index.ts deleted file mode 100644 index 833bdb5210..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/toggle_list/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ToggleList'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/underline/Underline.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/underline/Underline.tsx deleted file mode 100644 index b0df70e30e..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/underline/Underline.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import React, { useCallback, useMemo } from 'react'; -import ActionButton from '$app/components/editor/components/tools/selection_toolbar/actions/_shared/ActionButton'; -import { useTranslation } from 'react-i18next'; -import { useSlateStatic } from 'slate-react'; -import { CustomEditor } from '$app/components/editor/command'; -import { ReactComponent as UnderlineSvg } from '$app/assets/underline.svg'; -import { EditorMarkFormat } from '$app/application/document/document.types'; -import { createHotKeyLabel, HOT_KEY_NAME } from '$app/utils/hotkeys'; - -export function Underline() { - const { t } = useTranslation(); - const editor = useSlateStatic(); - const isActivated = CustomEditor.isMarkActive(editor, EditorMarkFormat.Underline); - const modifier = useMemo(() => createHotKeyLabel(HOT_KEY_NAME.UNDERLINE), []); - - const onClick = useCallback(() => { - CustomEditor.toggleMark(editor, { - key: EditorMarkFormat.Underline, - value: true, - }); - }, [editor]); - - return ( - -
{t('editor.underline')}
-
{modifier}
- - } - > - -
- ); -} - -export default Underline; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/underline/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/underline/index.ts deleted file mode 100644 index a1d53a4384..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/actions/underline/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Underline'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/index.ts deleted file mode 100644 index a6ced3f248..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './SelectionToolbar'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/utils.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/utils.ts deleted file mode 100644 index 178da73df4..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/components/tools/selection_toolbar/utils.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { ReactEditor } from 'slate-react'; -import { getEditorDomNode } from '$app/components/editor/plugins/utils'; - -export function getSelectionPosition(editor: ReactEditor) { - const domSelection = window.getSelection(); - const rangeCount = domSelection?.rangeCount; - - if (!rangeCount) return null; - - const domRange = rangeCount > 0 ? domSelection.getRangeAt(0) : undefined; - - const rect = domRange?.getBoundingClientRect(); - - let newRect; - - const domNode = getEditorDomNode(editor); - const domNodeRect = domNode.getBoundingClientRect(); - - // the default height of the toolbar is 30px - const gap = 106; - - if (rect) { - let relativeDomTop = rect.top - domNodeRect.top; - const relativeDomLeft = rect.left - domNodeRect.left; - - // if the range is above the window, move the toolbar to the bottom of range - if (rect.top < gap) { - relativeDomTop = -domNodeRect.top + gap; - } - - newRect = { - top: relativeDomTop, - left: relativeDomLeft, - width: rect.width, - height: rect.height, - }; - } - - return newRect; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/editor.scss b/frontend/appflowy_tauri/src/appflowy_app/components/editor/editor.scss deleted file mode 100644 index 271dd36cda..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/editor.scss +++ /dev/null @@ -1,231 +0,0 @@ - -.block-element { - @apply my-[4px]; - -} - -.block-element .block-element { - @apply mb-0; - margin-left: 24px; -} - -.block-element.block-align-left { - > div > .text-element { - text-align: left; - justify-content: flex-start; - } -} -.block-element.block-align-right { - > div > .text-element { - text-align: right; - justify-content: flex-end; - } -} -.block-element.block-align-center { - > div > .text-element { - text-align: center; - justify-content: center; - } - -} - - -.block-element[data-block-type="todo_list"] .checked > .text-element { - text-decoration: line-through; - color: var(--text-caption); -} - -.block-element .collapsed .block-element { - display: none !important; -} - -[role=textbox] { - .text-element { - &::selection { - @apply bg-transparent; - } - } -} - - - -span[data-slate-placeholder="true"]:not(.inline-block-content) { - @apply text-text-placeholder; - opacity: 1 !important; -} - - -[role="textbox"] { - ::selection { - @apply bg-content-blue-100; - } - .text-content { - &::selection { - @apply bg-transparent; - } - &.selected { - @apply bg-content-blue-100; - } - span { - &::selection { - @apply bg-content-blue-100; - } - } - } -} - - -[data-dark-mode="true"] [role="textbox"]{ - ::selection { - background-color: #1e79a2; - } - - .text-content { - &::selection { - @apply bg-transparent; - } - &.selected { - background-color: #1e79a2; - } - span { - &::selection { - background-color: #1e79a2; - } - } - } -} - - -.text-content, [data-dark-mode="true"] .text-content { - @apply min-w-[1px]; - &.empty-text { - span { - &::selection { - @apply bg-transparent; - } - } - } -} - -.text-element:has(.text-placeholder), .divider-node, [data-dark-mode="true"] .text-element:has(.text-placeholder), [data-dark-mode="true"] .divider-node { - ::selection { - @apply bg-transparent; - } -} - -.text-placeholder { - @apply absolute left-[5px] transform -translate-y-1/2 pointer-events-none select-none whitespace-nowrap; - &:after { - @apply text-text-placeholder absolute top-0; - content: (attr(placeholder)); - } -} - -.block-align-center { - .text-placeholder { - @apply left-[calc(50%+1px)]; - &:after { - @apply left-0; - } - } - .has-start-icon .text-placeholder { - @apply left-[calc(50%+13px)]; - &:after { - @apply left-0; - } - } - -} - -.block-align-left { - .text-placeholder { - &:after { - @apply left-0; - } - } - .has-start-icon .text-placeholder { - &:after { - @apply left-[24px]; - } - } -} - -.block-align-right { - - .text-placeholder { - - @apply relative w-fit h-0 order-2; - &:after { - @apply relative w-fit top-1/2 left-[-6px]; - } - } - .text-content { - @apply order-1; - } - - .has-start-icon .text-placeholder { - &:after { - @apply left-[-6px]; - } - } -} - - -.formula-inline { - &.selected { - @apply rounded bg-content-blue-100; - } -} - -.bulleted-icon { - &:after { - content: attr(data-letter); - } -} - -.numbered-icon { - &:after { - content: attr(data-number) "."; - } -} - - -.grid-block .grid-scroll-container::-webkit-scrollbar { - width: 0; - height: 0; -} - -.image-render { - .image-resizer { - @apply absolute w-[10px] top-0 z-10 flex h-full cursor-col-resize items-center justify-end; - .resize-handle { - @apply h-1/4 w-1/2 transform transition-all duration-500 select-none rounded-full border border-white opacity-0; - background: var(--fill-toolbar); - } - } - &:hover { - .image-resizer{ - .resize-handle { - @apply opacity-90; - } - } - } -} - - -.image-block, .math-equation-block, [data-dark-mode="true"] .image-block, [data-dark-mode="true"] .math-equation-block { - ::selection { - @apply bg-transparent; - } - &:hover { - .container-bg { - background: var(--content-blue-100) !important; - } - } -} - -.mention-inline { - &:hover { - @apply bg-fill-list-active rounded; - } -} \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/index.ts deleted file mode 100644 index 8b7c4c267a..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Editor'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/constants.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/constants.ts deleted file mode 100644 index 03e441b1c3..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/constants.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { EditorNodeType } from '$app/application/document/document.types'; - -export const SOFT_BREAK_TYPES = [EditorNodeType.CalloutBlock, EditorNodeType.CodeBlock]; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/copyPasted/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/copyPasted/index.ts deleted file mode 100644 index bf2b09a1c3..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/copyPasted/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './withCopy'; -export * from './withPasted'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/copyPasted/utils.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/copyPasted/utils.ts deleted file mode 100644 index cb377fece4..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/copyPasted/utils.ts +++ /dev/null @@ -1,311 +0,0 @@ -import { ReactEditor } from 'slate-react'; -import { Editor, Node, Location, Range, Path, Element, Text, Transforms, NodeEntry } from 'slate'; -import { EditorNodeType } from '$app/application/document/document.types'; -import { CustomEditor } from '$app/components/editor/command'; -import { LIST_TYPES } from '$app/components/editor/command/tab'; - -/** - * Rewrite the insertFragment function to avoid the empty node(doesn't have text node) in the fragment - - * @param editor - * @param fragment - * @param options - */ -export function insertFragment( - editor: ReactEditor, - fragment: (Text | Element)[], - options: { - at?: Location; - hanging?: boolean; - voids?: boolean; - } = {} -) { - Editor.withoutNormalizing(editor, () => { - const { hanging = false, voids = false } = options; - let { at = getDefaultInsertLocation(editor) } = options; - - if (!fragment.length) { - return; - } - - if (Range.isRange(at)) { - if (!hanging) { - at = Editor.unhangRange(editor, at, { voids }); - } - - if (Range.isCollapsed(at)) { - at = at.anchor; - } else { - const [, end] = Range.edges(at); - - if (!voids && Editor.void(editor, { at: end })) { - return; - } - - const pointRef = Editor.pointRef(editor, end); - - Transforms.delete(editor, { at }); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - at = pointRef.unref()!; - } - } else if (Path.isPath(at)) { - at = Editor.start(editor, at); - } - - if (!voids && Editor.void(editor, { at })) { - return; - } - - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const blockMatch = Editor.above(editor, { - match: (n) => Element.isElement(n) && Editor.isBlock(editor, n) && n.blockId !== undefined, - at, - voids, - })!; - const [block, blockPath] = blockMatch as NodeEntry; - - const isEmbedBlock = Element.isElement(block) && editor.isEmbed(block); - const isPageBlock = Element.isElement(block) && block.type === EditorNodeType.Page; - const isBlockStart = Editor.isStart(editor, at, blockPath); - const isBlockEnd = Editor.isEnd(editor, at, blockPath); - const isBlockEmpty = isBlockStart && isBlockEnd; - - if (isEmbedBlock) { - insertOnEmbedBlock(editor, fragment, blockPath); - return; - } - - if (isBlockEmpty && !isPageBlock) { - const node = fragment[0] as Element; - - if (block.type !== EditorNodeType.Paragraph) { - node.type = block.type; - node.data = { - ...(node.data || {}), - ...(block.data || {}), - }; - } - - insertOnEmptyBlock(editor, fragment, blockPath); - return; - } - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const fragmentRoot: Node = { - children: fragment, - }; - const [, firstPath] = Node.first(fragmentRoot, []); - const [, lastPath] = Node.last(fragmentRoot, []); - const sameBlock = Path.equals(firstPath.slice(0, -1), lastPath.slice(0, -1)); - - if (sameBlock) { - insertTexts( - editor, - isPageBlock - ? ({ - children: [ - { - text: CustomEditor.getNodeTextContent(fragmentRoot), - }, - ], - } as Node) - : fragmentRoot, - at - ); - return; - } - - const isListTypeBlock = LIST_TYPES.includes(block.type as EditorNodeType); - const [, ...blockChildren] = block.children; - - const blockEnd = editor.end([...blockPath, 0]); - const afterRange: Range = { anchor: at, focus: blockEnd }; - - const afterTexts = getTexts(editor, { - children: editor.fragment(afterRange), - } as Node) as (Text | Element)[]; - - Transforms.delete(editor, { at: afterRange }); - - const { startTexts, startChildren, middles } = getFragmentGroup(editor, fragment); - - insertNodes( - editor, - isPageBlock - ? [ - { - text: CustomEditor.getNodeTextContent({ - children: startTexts, - } as Node), - }, - ] - : startTexts, - { - at, - } - ); - - if (isPageBlock) { - insertNodes(editor, [...startChildren, ...middles], { - at: Path.next(blockPath), - select: true, - }); - } else { - if (blockChildren.length > 0) { - const path = [...blockPath, 1]; - - insertNodes(editor, [...startChildren, ...middles], { - at: path, - select: true, - }); - } else { - const newMiddle = [...middles]; - - if (isListTypeBlock) { - const path = [...blockPath, 1]; - - insertNodes(editor, startChildren, { - at: path, - select: newMiddle.length === 0, - }); - } else { - newMiddle.unshift(...startChildren); - } - - insertNodes(editor, newMiddle, { - at: Path.next(blockPath), - select: true, - }); - } - } - - const { selection } = editor; - - if (!selection) return; - - insertNodes(editor, afterTexts, { - at: selection, - }); - }); -} - -function getFragmentGroup(editor: ReactEditor, fragment: Node[]) { - const startTexts = []; - const startChildren = []; - const middles = []; - - const [firstNode, ...otherNodes] = fragment; - const [firstNodeText, ...firstNodeChildren] = (firstNode as Element).children as Element[]; - - startTexts.push(...firstNodeText.children); - startChildren.push(...firstNodeChildren); - - for (const node of otherNodes) { - if (Element.isElement(node) && node.blockId !== undefined) { - middles.push(node); - } - } - - return { - startTexts, - startChildren, - middles, - }; -} - -function getTexts(editor: ReactEditor, fragment: Node) { - const matches = []; - const matcher = ([n]: NodeEntry) => Text.isText(n) || (Element.isElement(n) && editor.isInline(n)); - - for (const entry of Node.nodes(fragment, { pass: matcher })) { - if (matcher(entry)) { - matches.push(entry[0]); - } - } - - return matches; -} - -function insertTexts(editor: ReactEditor, fragmentRoot: Node, at: Location) { - const matches = getTexts(editor, fragmentRoot); - - insertNodes(editor, matches, { - at, - select: true, - }); -} - -function insertOnEmptyBlock(editor: ReactEditor, fragment: Node[], blockPath: Path) { - editor.removeNodes({ - at: blockPath, - }); - - insertNodes(editor, fragment, { - at: blockPath, - select: true, - }); -} - -function insertOnEmbedBlock(editor: ReactEditor, fragment: Node[], blockPath: Path) { - insertNodes(editor, fragment, { - at: Path.next(blockPath), - select: true, - }); -} - -function insertNodes(editor: ReactEditor, nodes: Node[], options: { at?: Location; select?: boolean } = {}) { - try { - Transforms.insertNodes(editor, nodes, options); - } catch (e) { - try { - editor.move({ - distance: 1, - unit: 'line', - }); - } catch (e) { - // do nothing - } - } -} - -/** - * Copy Code from slate/src/utils/get-default-insert-location.ts - * Get the default location to insert content into the editor. - * By default, use the selection as the target location. But if there is - * no selection, insert at the end of the document since that is such a - * common use case when inserting from a non-selected state. - */ -export const getDefaultInsertLocation = (editor: Editor): Location => { - if (editor.selection) { - return editor.selection; - } else if (editor.children.length > 0) { - return Editor.end(editor, []); - } else { - return [0]; - } -}; - -export function transFragment(editor: ReactEditor, fragment: Node[]) { - // flatten the fragment to avoid the empty node(doesn't have text node) in the fragment - const flatMap = (node: Node): Node[] => { - const isInputElement = - !Editor.isEditor(node) && Element.isElement(node) && node.blockId !== undefined && !editor.isEmbed(node); - - if ( - isInputElement && - node.children?.length > 0 && - Element.isElement(node.children[0]) && - node.children[0].type !== EditorNodeType.Text - ) { - return node.children.flatMap((child) => flatMap(child)); - } - - return [node]; - }; - - const fragmentFlatMap = fragment?.flatMap(flatMap); - - // clone the node to avoid the duplicated block id - return fragmentFlatMap.map((item) => CustomEditor.cloneBlock(editor, item as Element)); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/copyPasted/withCopy.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/copyPasted/withCopy.ts deleted file mode 100644 index c0daab0a8f..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/copyPasted/withCopy.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { ReactEditor } from 'slate-react'; -import { Editor, Element, Range } from 'slate'; - -export function withCopy(editor: ReactEditor) { - const { setFragmentData } = editor; - - editor.setFragmentData = (...args) => { - if (!editor.selection) { - setFragmentData(...args); - return; - } - - // selection is collapsed and the node is an embed, we need to set the data manually - if (Range.isCollapsed(editor.selection)) { - const match = Editor.above(editor, { - match: (n) => !Editor.isEditor(n) && Element.isElement(n) && n.blockId !== undefined, - }); - const node = match ? (match[0] as Element) : undefined; - - if (node && editor.isEmbed(node)) { - const fragment = editor.getFragment(); - - if (fragment.length > 0) { - const data = args[0]; - const string = JSON.stringify(fragment); - const encoded = window.btoa(encodeURIComponent(string)); - - const dom = ReactEditor.toDOMNode(editor, node); - - data.setData(`application/x-slate-fragment`, encoded); - data.setData(`text/html`, dom.innerHTML); - } - } - } - - setFragmentData(...args); - }; - - return editor; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/copyPasted/withPasted.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/copyPasted/withPasted.ts deleted file mode 100644 index 2266ff41c7..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/copyPasted/withPasted.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { ReactEditor } from 'slate-react'; -import { insertFragment, transFragment } from './utils'; -import { convertBlockToJson } from '$app/application/document/document.service'; -import { InputType } from '@/services/backend'; -import { CustomEditor } from '$app/components/editor/command'; -import { Log } from '$app/utils/log'; - -export function withPasted(editor: ReactEditor) { - const { insertData } = editor; - - editor.insertData = (data) => { - const fragment = data.getData('application/x-slate-fragment'); - - if (fragment) { - insertData(data); - return; - } - - const html = data.getData('text/html'); - const text = data.getData('text/plain'); - - if (!html && !text) { - insertData(data); - return; - } - - void (async () => { - try { - const nodes = await convertBlockToJson(html, InputType.Html); - - const htmlTransNoText = nodes.every((node) => { - return CustomEditor.getNodeTextContent(node).length === 0; - }); - - if (!htmlTransNoText) { - return editor.insertFragment(nodes); - } - } catch (e) { - Log.warn('pasted html error', e); - // ignore - } - - if (text) { - const nodes = await convertBlockToJson(text, InputType.PlainText); - - editor.insertFragment(nodes); - return; - } - })(); - }; - - editor.insertFragment = (fragment, options = {}) => { - const clonedFragment = transFragment(editor, fragment); - - insertFragment(editor, clonedFragment, options); - }; - - return editor; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/shortcuts/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/shortcuts/index.ts deleted file mode 100644 index 0292784ba5..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/shortcuts/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './shortcuts.hooks'; -export * from './withMarkdown'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/shortcuts/markdown.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/shortcuts/markdown.ts deleted file mode 100644 index 59ff0a8593..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/shortcuts/markdown.ts +++ /dev/null @@ -1,172 +0,0 @@ -export type MarkdownRegex = { - [key in MarkdownShortcuts]: { - pattern: RegExp; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - data?: Record; - }[]; -}; - -export type TriggerHotKey = { - [key in MarkdownShortcuts]: string[]; -}; - -export enum MarkdownShortcuts { - Bold, - Italic, - StrikeThrough, - Code, - Equation, - /** block */ - Heading, - BlockQuote, - CodeBlock, - Divider, - /** list */ - BulletedList, - NumberedList, - TodoList, - ToggleList, -} - -const defaultMarkdownRegex: MarkdownRegex = { - [MarkdownShortcuts.Heading]: [ - { - pattern: /^#{1,6}$/, - }, - ], - [MarkdownShortcuts.Bold]: [ - { - pattern: /(\*\*|__)(.*?)(\*\*|__)$/, - }, - ], - [MarkdownShortcuts.Italic]: [ - { - pattern: /([*_])(.*?)([*_])$/, - }, - ], - [MarkdownShortcuts.StrikeThrough]: [ - { - pattern: /(~~)(.*?)(~~)$/, - }, - { - pattern: /(~)(.*?)(~)$/, - }, - ], - [MarkdownShortcuts.Code]: [ - { - pattern: /(`)(.*?)(`)$/, - }, - ], - [MarkdownShortcuts.Equation]: [ - { - pattern: /(\$)(.*?)(\$)$/, - data: { - formula: '', - }, - }, - ], - [MarkdownShortcuts.BlockQuote]: [ - { - pattern: /^([”“"])$/, - }, - ], - [MarkdownShortcuts.CodeBlock]: [ - { - pattern: /^(`{2,})$/, - data: { - language: 'json', - }, - }, - ], - [MarkdownShortcuts.Divider]: [ - { - pattern: /^(([-*]){2,})$/, - }, - ], - - [MarkdownShortcuts.BulletedList]: [ - { - pattern: /^([*\-+])$/, - }, - ], - [MarkdownShortcuts.NumberedList]: [ - { - pattern: /^(\d+)\.$/, - }, - ], - [MarkdownShortcuts.TodoList]: [ - { - pattern: /^(-)?\[ ]$/, - data: { - checked: false, - }, - }, - { - pattern: /^(-)?\[x]$/, - data: { - checked: true, - }, - }, - { - pattern: /^(-)?\[]$/, - data: { - checked: false, - }, - }, - ], - [MarkdownShortcuts.ToggleList]: [ - { - pattern: /^>$/, - data: { - collapsed: false, - }, - }, - ], -}; - -export const defaultTriggerChar: TriggerHotKey = { - [MarkdownShortcuts.Heading]: [' '], - [MarkdownShortcuts.Bold]: ['*', '_'], - [MarkdownShortcuts.Italic]: ['*', '_'], - [MarkdownShortcuts.StrikeThrough]: ['~'], - [MarkdownShortcuts.Code]: ['`'], - [MarkdownShortcuts.BlockQuote]: [' '], - [MarkdownShortcuts.CodeBlock]: ['`'], - [MarkdownShortcuts.Divider]: ['-', '*'], - [MarkdownShortcuts.Equation]: ['$'], - [MarkdownShortcuts.BulletedList]: [' '], - [MarkdownShortcuts.NumberedList]: [' '], - [MarkdownShortcuts.TodoList]: [' '], - [MarkdownShortcuts.ToggleList]: [' '], -}; - -export function isTriggerChar(char: string) { - return Object.values(defaultTriggerChar).some((trigger) => trigger.includes(char)); -} - -export function whatShortcutTrigger(char: string): MarkdownShortcuts[] | null { - const isTrigger = isTriggerChar(char); - - if (!isTrigger) { - return null; - } - - const shortcuts = Object.keys(defaultTriggerChar).map((key) => Number(key) as MarkdownShortcuts); - - return shortcuts.filter((shortcut) => defaultTriggerChar[shortcut].includes(char)); -} - -export function getRegex(shortcut: MarkdownShortcuts) { - return defaultMarkdownRegex[shortcut]; -} - -export function whatShortcutsMatch(text: string) { - const shortcuts = Object.keys(defaultMarkdownRegex).map((key) => Number(key) as MarkdownShortcuts); - - return shortcuts.filter((shortcut) => { - const regexes = defaultMarkdownRegex[shortcut]; - - return regexes.some((regex) => regex.pattern.test(text)); - }); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/shortcuts/shortcuts.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/shortcuts/shortcuts.hooks.ts deleted file mode 100644 index 45d61f847c..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/shortcuts/shortcuts.hooks.ts +++ /dev/null @@ -1,349 +0,0 @@ -import { ReactEditor } from 'slate-react'; -import { useCallback, KeyboardEvent } from 'react'; -import { EditorMarkFormat, EditorNodeType, ToggleListNode } from '$app/application/document/document.types'; -import { getBlock } from '$app/components/editor/plugins/utils'; -import { SOFT_BREAK_TYPES } from '$app/components/editor/plugins/constants'; -import { CustomEditor } from '$app/components/editor/command'; -import { createHotkey, HOT_KEY_NAME } from '$app/utils/hotkeys'; -import { openUrl } from '$app/utils/open_url'; -import { Range } from 'slate'; -import { readText } from '@tauri-apps/api/clipboard'; -import { useDecorateDispatch } from '$app/components/editor/stores'; - -function getScrollContainer(editor: ReactEditor) { - const editorDom = ReactEditor.toDOMNode(editor, editor); - - return editorDom.closest('.appflowy-scroll-container') as HTMLDivElement; -} - -export function useShortcuts(editor: ReactEditor) { - const { add: addDecorate } = useDecorateDispatch(); - - const formatLink = useCallback(() => { - const { selection } = editor; - - if (!selection || Range.isCollapsed(selection)) return; - - const isIncludeRoot = CustomEditor.selectionIncludeRoot(editor); - - if (isIncludeRoot) return; - - const isActivatedInline = CustomEditor.isInlineActive(editor); - - if (isActivatedInline) return; - - addDecorate({ - range: selection, - class_name: 'bg-content-blue-100 rounded', - type: 'link', - }); - }, [addDecorate, editor]); - - const onKeyDown = useCallback( - (e: KeyboardEvent) => { - const event = e.nativeEvent; - const hasEditableTarget = ReactEditor.hasEditableTarget(editor, event.target); - - if (!hasEditableTarget) return; - - const node = getBlock(editor); - - const { selection } = editor; - const isExpanded = selection && Range.isExpanded(selection); - - switch (true) { - /** - * Select all: Mod+A - * Default behavior: Select all text in the editor - * Special case for select all in code block: Only select all text in code block - */ - case createHotkey(HOT_KEY_NAME.SELECT_ALL)(event): - if (node && node.type === EditorNodeType.CodeBlock) { - e.preventDefault(); - const path = ReactEditor.findPath(editor, node); - - editor.select(path); - } - - break; - /** - * Escape: Esc - * Default behavior: Deselect editor - */ - case createHotkey(HOT_KEY_NAME.ESCAPE)(event): - editor.deselect(); - break; - /** - * Indent block: Tab - * Default behavior: Indent block - */ - case createHotkey(HOT_KEY_NAME.INDENT_BLOCK)(event): - e.preventDefault(); - if (SOFT_BREAK_TYPES.includes(node?.type as EditorNodeType)) { - editor.insertText('\t'); - break; - } - - CustomEditor.tabForward(editor); - break; - /** - * Outdent block: Shift+Tab - * Default behavior: Outdent block - */ - case createHotkey(HOT_KEY_NAME.OUTDENT_BLOCK)(event): - e.preventDefault(); - CustomEditor.tabBackward(editor); - break; - /** - * Split block: Enter - * Default behavior: Split block - * Special case for soft break types: Insert \n - */ - case createHotkey(HOT_KEY_NAME.SPLIT_BLOCK)(event): - if (SOFT_BREAK_TYPES.includes(node?.type as EditorNodeType)) { - e.preventDefault(); - editor.insertText('\n'); - } - - break; - /** - * Insert soft break: Shift+Enter - * Default behavior: Insert \n - * Special case for soft break types: Split block - */ - case createHotkey(HOT_KEY_NAME.INSERT_SOFT_BREAK)(event): - e.preventDefault(); - if (node && SOFT_BREAK_TYPES.includes(node.type as EditorNodeType)) { - editor.splitNodes({ - always: true, - }); - } else { - editor.insertText('\n'); - } - - break; - /** - * Toggle todo: Shift+Enter - * Default behavior: Toggle todo - * Special case for toggle list block: Toggle collapse - */ - case createHotkey(HOT_KEY_NAME.TOGGLE_TODO)(event): - case createHotkey(HOT_KEY_NAME.TOGGLE_COLLAPSE)(event): - e.preventDefault(); - if (node && node.type === EditorNodeType.ToggleListBlock) { - CustomEditor.toggleToggleList(editor, node as ToggleListNode); - } else { - CustomEditor.toggleTodo(editor); - } - - break; - /** - * Backspace: Backspace / Shift+Backspace - * Default behavior: Delete backward - */ - case createHotkey(HOT_KEY_NAME.BACKSPACE)(event): - e.stopPropagation(); - break; - /** - * Open link: Alt + enter - * Default behavior: Open one link in selection - */ - case createHotkey(HOT_KEY_NAME.OPEN_LINK)(event): { - if (!isExpanded) break; - e.preventDefault(); - const links = CustomEditor.getLinks(editor); - - if (links.length === 0) break; - openUrl(links[0]); - break; - } - - /** - * Open links: Alt + Shift + enter - * Default behavior: Open all links in selection - */ - case createHotkey(HOT_KEY_NAME.OPEN_LINKS)(event): { - if (!isExpanded) break; - e.preventDefault(); - const links = CustomEditor.getLinks(editor); - - if (links.length === 0) break; - links.forEach((link) => openUrl(link)); - break; - } - - /** - * Extend line backward: Opt + Shift + right - * Default behavior: Extend line backward - */ - case createHotkey(HOT_KEY_NAME.EXTEND_LINE_BACKWARD)(event): - e.preventDefault(); - CustomEditor.extendLineBackward(editor); - break; - /** - * Extend line forward: Opt + Shift + left - */ - case createHotkey(HOT_KEY_NAME.EXTEND_LINE_FORWARD)(event): - e.preventDefault(); - CustomEditor.extendLineForward(editor); - break; - - /** - * Paste: Mod + Shift + V - * Default behavior: Paste plain text - */ - case createHotkey(HOT_KEY_NAME.PASTE_PLAIN_TEXT)(event): - e.preventDefault(); - void (async () => { - const text = await readText(); - - if (!text) return; - CustomEditor.insertPlainText(editor, text); - })(); - - break; - /** - * Highlight: Mod + Shift + H - * Default behavior: Highlight selected text - */ - case createHotkey(HOT_KEY_NAME.HIGH_LIGHT)(event): - e.preventDefault(); - CustomEditor.highlight(editor); - break; - /** - * Extend document backward: Mod + Shift + Up - * Don't prevent default behavior - * Default behavior: Extend document backward - */ - case createHotkey(HOT_KEY_NAME.EXTEND_DOCUMENT_BACKWARD)(event): - editor.collapse({ edge: 'start' }); - break; - /** - * Extend document forward: Mod + Shift + Down - * Don't prevent default behavior - * Default behavior: Extend document forward - */ - case createHotkey(HOT_KEY_NAME.EXTEND_DOCUMENT_FORWARD)(event): - editor.collapse({ edge: 'end' }); - break; - - /** - * Scroll to top: Home - * Default behavior: Scroll to top - */ - case createHotkey(HOT_KEY_NAME.SCROLL_TO_TOP)(event): { - const scrollContainer = getScrollContainer(editor); - - scrollContainer.scrollTo({ - top: 0, - }); - break; - } - - /** - * Scroll to bottom: End - * Default behavior: Scroll to bottom - */ - case createHotkey(HOT_KEY_NAME.SCROLL_TO_BOTTOM)(event): { - const scrollContainer = getScrollContainer(editor); - - scrollContainer.scrollTo({ - top: scrollContainer.scrollHeight, - }); - break; - } - - /** - * Align left: Control + Shift + L - * Default behavior: Align left - */ - case createHotkey(HOT_KEY_NAME.ALIGN_LEFT)(event): - e.preventDefault(); - CustomEditor.toggleAlign(editor, 'left'); - break; - /** - * Align center: Control + Shift + E - */ - case createHotkey(HOT_KEY_NAME.ALIGN_CENTER)(event): - e.preventDefault(); - CustomEditor.toggleAlign(editor, 'center'); - break; - /** - * Align right: Control + Shift + R - */ - case createHotkey(HOT_KEY_NAME.ALIGN_RIGHT)(event): - e.preventDefault(); - CustomEditor.toggleAlign(editor, 'right'); - break; - /** - * Bold: Mod + B - */ - case createHotkey(HOT_KEY_NAME.BOLD)(event): - e.preventDefault(); - CustomEditor.toggleMark(editor, { - key: EditorMarkFormat.Bold, - value: true, - }); - break; - /** - * Italic: Mod + I - */ - case createHotkey(HOT_KEY_NAME.ITALIC)(event): - e.preventDefault(); - CustomEditor.toggleMark(editor, { - key: EditorMarkFormat.Italic, - value: true, - }); - break; - /** - * Underline: Mod + U - */ - case createHotkey(HOT_KEY_NAME.UNDERLINE)(event): - e.preventDefault(); - CustomEditor.toggleMark(editor, { - key: EditorMarkFormat.Underline, - value: true, - }); - break; - /** - * Strikethrough: Mod + Shift + S / Mod + Shift + X - */ - case createHotkey(HOT_KEY_NAME.STRIKETHROUGH)(event): - e.preventDefault(); - CustomEditor.toggleMark(editor, { - key: EditorMarkFormat.StrikeThrough, - value: true, - }); - break; - /** - * Code: Mod + E - */ - case createHotkey(HOT_KEY_NAME.CODE)(event): - e.preventDefault(); - CustomEditor.toggleMark(editor, { - key: EditorMarkFormat.Code, - value: true, - }); - break; - /** - * Format link: Mod + K - */ - case createHotkey(HOT_KEY_NAME.FORMAT_LINK)(event): - formatLink(); - break; - - case createHotkey(HOT_KEY_NAME.FIND_REPLACE)(event): - console.log('find replace'); - break; - - default: - break; - } - }, - [formatLink, editor] - ); - - return { - onKeyDown, - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/shortcuts/withMarkdown.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/shortcuts/withMarkdown.ts deleted file mode 100644 index fd7801204c..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/shortcuts/withMarkdown.ts +++ /dev/null @@ -1,239 +0,0 @@ -import { Range, Element, Editor, NodeEntry, Path } from 'slate'; -import { ReactEditor } from 'slate-react'; -import { - defaultTriggerChar, - getRegex, - MarkdownShortcuts, - whatShortcutsMatch, - whatShortcutTrigger, -} from '$app/components/editor/plugins/shortcuts/markdown'; -import { CustomEditor } from '$app/components/editor/command'; -import { EditorMarkFormat, EditorNodeType } from '$app/application/document/document.types'; -import isEqual from 'lodash-es/isEqual'; - -export const withMarkdown = (editor: ReactEditor) => { - const { insertText } = editor; - - editor.insertText = (char) => { - const { selection } = editor; - - insertText(char); - if (!selection || !Range.isCollapsed(selection)) { - return; - } - - const triggerShortcuts = whatShortcutTrigger(char); - - if (!triggerShortcuts) { - return; - } - - const match = CustomEditor.getBlock(editor); - const [node, path] = match as NodeEntry; - - let prevIsNumberedList = false; - - try { - const prevPath = Path.previous(path); - const prev = editor.node(prevPath) as NodeEntry; - - prevIsNumberedList = prev && prev[0].type === EditorNodeType.NumberedListBlock; - } catch (e) { - // do nothing - } - - const start = Editor.start(editor, path); - const beforeRange = { anchor: start, focus: selection.anchor }; - const beforeText = Editor.string(editor, beforeRange); - - const removeBeforeText = (beforeRange: Range) => { - editor.deleteBackward('character'); - editor.delete({ - at: beforeRange, - }); - }; - - const matchBlockShortcuts = whatShortcutsMatch(beforeText); - - for (const shortcut of matchBlockShortcuts) { - const block = whichBlock(shortcut, beforeText); - - // if the block shortcut is matched, remove the before text and turn to the block - // then return - if (block && defaultTriggerChar[shortcut].includes(char)) { - // Don't turn to the block condition - // 1. Heading should be able to co-exist with number list - if (block.type === EditorNodeType.NumberedListBlock && node.type === EditorNodeType.HeadingBlock) { - return; - } - - // 2. If the block is the same type, and data is the same - if (block.type === node.type && isEqual(block.data || {}, node.data || {})) { - return; - } - - // 3. If the block is number list, and the previous block is also number list - if (block.type === EditorNodeType.NumberedListBlock && prevIsNumberedList) { - return; - } - - removeBeforeText(beforeRange); - CustomEditor.turnToBlock(editor, block); - - return; - } - } - - // get the range that matches the mark shortcuts - const markRange = { - anchor: Editor.start(editor, selection.anchor.path), - focus: selection.focus, - }; - const rangeText = Editor.string(editor, markRange) + char; - - if (!rangeText) return; - - // inputting a character that is start of a mark - const isStartTyping = rangeText.indexOf(char) === rangeText.lastIndexOf(char); - - if (isStartTyping) return; - - // if the range text includes a double character mark, and the last one is not finished - const doubleCharNotFinish = - ['*', '_', '~'].includes(char) && - rangeText.indexOf(`${char}${char}`) > -1 && - rangeText.indexOf(`${char}${char}`) === rangeText.lastIndexOf(`${char}${char}`); - - if (doubleCharNotFinish) return; - - const matchMarkShortcuts = whatShortcutsMatch(rangeText); - - for (const shortcut of matchMarkShortcuts) { - const item = getRegex(shortcut).find((p) => p.pattern.test(rangeText)); - const execArr = item?.pattern?.exec(rangeText); - - const removeText = execArr ? execArr[0] : ''; - - const text = execArr ? execArr[2]?.replaceAll(char, '') : ''; - - if (text) { - const index = rangeText.indexOf(removeText); - const removeRange = { - anchor: { - path: markRange.anchor.path, - offset: markRange.anchor.offset + index, - }, - focus: { - path: markRange.anchor.path, - offset: markRange.anchor.offset + index + removeText.length, - }, - }; - - removeBeforeText(removeRange); - insertMark(editor, shortcut, text); - return; - } - } - }; - - return editor; -}; - -function whichBlock(shortcut: MarkdownShortcuts, beforeText: string) { - switch (shortcut) { - case MarkdownShortcuts.Heading: - return { - type: EditorNodeType.HeadingBlock, - data: { - level: beforeText.length, - }, - }; - case MarkdownShortcuts.CodeBlock: - return { - type: EditorNodeType.CodeBlock, - data: { - language: 'json', - }, - }; - case MarkdownShortcuts.BulletedList: - return { - type: EditorNodeType.BulletedListBlock, - data: {}, - }; - case MarkdownShortcuts.NumberedList: - return { - type: EditorNodeType.NumberedListBlock, - data: { - number: Number(beforeText.split('.')[0]) ?? 1, - }, - }; - case MarkdownShortcuts.TodoList: - return { - type: EditorNodeType.TodoListBlock, - data: { - checked: beforeText.includes('[x]'), - }, - }; - case MarkdownShortcuts.BlockQuote: - return { - type: EditorNodeType.QuoteBlock, - data: {}, - }; - case MarkdownShortcuts.Divider: - return { - type: EditorNodeType.DividerBlock, - data: {}, - }; - - case MarkdownShortcuts.ToggleList: - return { - type: EditorNodeType.ToggleListBlock, - data: { - collapsed: false, - }, - }; - - default: - return null; - } -} - -function insertMark(editor: ReactEditor, shortcut: MarkdownShortcuts, text: string) { - switch (shortcut) { - case MarkdownShortcuts.Bold: - case MarkdownShortcuts.Italic: - case MarkdownShortcuts.StrikeThrough: - case MarkdownShortcuts.Code: { - const textNode = { - text, - }; - const attributes = { - [MarkdownShortcuts.Bold]: { - [EditorMarkFormat.Bold]: true, - }, - [MarkdownShortcuts.Italic]: { - [EditorMarkFormat.Italic]: true, - }, - [MarkdownShortcuts.StrikeThrough]: { - [EditorMarkFormat.StrikeThrough]: true, - }, - [MarkdownShortcuts.Code]: { - [EditorMarkFormat.Code]: true, - }, - }; - - Object.assign(textNode, attributes[shortcut]); - - editor.insertNodes(textNode); - return; - } - - case MarkdownShortcuts.Equation: { - CustomEditor.insertFormula(editor, text); - return; - } - - default: - return null; - } -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/utils.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/utils.ts deleted file mode 100644 index 62e3ad945a..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/utils.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Element, NodeEntry } from 'slate'; -import { ReactEditor } from 'slate-react'; -import { CustomEditor } from '$app/components/editor/command'; - -export function getHeadingCssProperty(level: number) { - switch (level) { - case 1: - return 'text-3xl pt-[10px] pb-[8px] font-bold'; - case 2: - return 'text-2xl pt-[8px] pb-[6px] font-bold'; - case 3: - return 'text-xl pt-[4px] font-bold'; - case 4: - return 'text-lg pt-[4px] font-bold'; - case 5: - return 'text-base pt-[4px] font-bold'; - case 6: - return 'text-sm pt-[4px] font-bold'; - default: - return ''; - } -} - -export function getBlock(editor: ReactEditor) { - const match = CustomEditor.getBlock(editor); - - if (match) { - const [node] = match as NodeEntry; - - return node; - } - - return; -} - -export function getEditorDomNode(editor: ReactEditor) { - return ReactEditor.toDOMNode(editor, editor); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/withBlockDelete.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/withBlockDelete.ts deleted file mode 100644 index 0bcd0965a9..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/withBlockDelete.ts +++ /dev/null @@ -1,228 +0,0 @@ -import { ReactEditor } from 'slate-react'; -import { Editor, Element, NodeEntry, Path, Range } from 'slate'; -import { EditorNodeType } from '$app/application/document/document.types'; -import { CustomEditor } from '$app/components/editor/command'; - -/** - * Delete backward. - * | -> cursor - * - * ------------------- delete backward to its previous sibling lift all children - * 1 1|2 - * |2 3 - * 3 => delete backward => 4 - * 4 5 - * 5 - * ------------------- delete backward to its parent and lift all children - * 1 1|2 - * |2 3 - * 3 => delete backward => 4 - * 4 5 - * 5 - * ------------------- outdent the node if the node has no children - * 1 1 - * 2 2 - * |3 |3 - * 4 => delete backward => 4 - * @param editor - */ -export function withBlockDelete(editor: ReactEditor) { - const { deleteBackward, deleteFragment, mergeNodes } = editor; - - editor.deleteBackward = (unit) => { - const match = CustomEditor.getBlock(editor); - - if (!match || !CustomEditor.focusAtStartOfBlock(editor)) { - deleteBackward(unit); - return; - } - - const [node, path] = match; - - const isEmbed = editor.isEmbed(node); - - if (isEmbed) { - CustomEditor.deleteNode(editor, node); - return; - } - - const previous = editor.previous({ - at: path, - match: (n) => !Editor.isEditor(n) && Element.isElement(n) && n.blockId !== undefined, - }); - const [previousNode] = previous || [undefined, undefined]; - - const previousIsPage = previousNode && Element.isElement(previousNode) && previousNode.type === EditorNodeType.Page; - - // merge to document title - if (previousIsPage) { - const textNodePath = [...path, 0]; - const [textNode] = editor.node(textNodePath); - const text = CustomEditor.getNodeTextContent(textNode); - - // clear all attributes - editor.select(textNodePath); - CustomEditor.removeMarks(editor); - editor.insertText(text); - - editor.move({ - distance: text.length, - reverse: true, - }); - } - - // if the current node is not a paragraph, convert it to a paragraph(except code block and callout block) - if ( - ![EditorNodeType.Paragraph, EditorNodeType.CalloutBlock, EditorNodeType.CodeBlock].includes( - node.type as EditorNodeType - ) && - node.type !== EditorNodeType.Page - ) { - CustomEditor.turnToBlock(editor, { type: EditorNodeType.Paragraph }); - return; - } - - const next = editor.next({ - at: path, - }); - - if (!next && path.length > 1) { - CustomEditor.tabBackward(editor); - return; - } - - const length = node.children.length; - - for (let i = length - 1; i > 0; i--) { - editor.liftNodes({ - at: [...path, i], - }); - } - - // if previous node is an embed, merge the current node to another node which is not an embed - if (Element.isElement(previousNode) && editor.isEmbed(previousNode)) { - const previousTextMatch = editor.previous({ - at: path, - match: (n) => !Editor.isEditor(n) && Element.isElement(n) && n.textId !== undefined, - }); - - if (!previousTextMatch) { - deleteBackward(unit); - return; - } - - const previousTextPath = previousTextMatch[1]; - const textNode = node.children[0] as Element; - - const at = Editor.end(editor, previousTextPath); - - editor.select(at); - editor.insertNodes(textNode.children, { - at, - }); - - editor.removeNodes({ - at: path, - }); - return; - } - - deleteBackward(unit); - }; - - editor.deleteFragment = (...args) => { - beforeDeleteToDocumentTitle(editor); - - deleteFragment(...args); - }; - - editor.mergeNodes = (options) => { - mergeNodes(options); - if (!editor.selection || !options?.at) return; - const nextPath = findNextPath(editor, editor.selection.anchor.path); - - const [nextNode] = editor.node(nextPath); - - if (Element.isElement(nextNode) && nextNode.blockId !== undefined && nextNode.children.length === 0) { - editor.removeNodes({ - at: nextPath, - }); - } - - return; - }; - - return editor; -} - -function beforeDeleteToDocumentTitle(editor: ReactEditor) { - if (!editor.selection) return; - if (Range.isCollapsed(editor.selection)) return; - const start = Range.start(editor.selection); - const end = Range.end(editor.selection); - const startNode = editor.above({ - at: start, - match: (n) => !Editor.isEditor(n) && Element.isElement(n) && n.type === EditorNodeType.Page, - }); - - const endNode = editor.above({ - at: end, - match: (n) => !Editor.isEditor(n) && Element.isElement(n) && n.blockId !== undefined, - }); - - const startNodeIsPage = !!startNode; - - if (!startNodeIsPage || !endNode) return; - const [node, path] = endNode as NodeEntry; - const selectedText = editor.string({ - anchor: { - path, - offset: 0, - }, - focus: end, - }); - - const nodeChildren = node.children; - const nodeChildrenLength = nodeChildren.length; - - for (let i = nodeChildrenLength - 1; i > 0; i--) { - editor.liftNodes({ - at: [...path, i], - }); - } - - const textNodePath = [...path, 0]; - const [textNode] = editor.node(textNodePath); - const text = CustomEditor.getNodeTextContent(textNode); - - // clear all attributes - editor.select([...path, 0]); - CustomEditor.removeMarks(editor); - editor.insertText(text); - editor.move({ - distance: text.length - selectedText.length, - reverse: true, - }); - editor.select({ - anchor: start, - focus: editor.selection.focus, - }); -} - -function findNextPath(editor: ReactEditor, path: Path): Path { - if (path.length === 0) return path; - const parentPath = Path.parent(path); - - try { - const nextPath = Path.next(path); - const [nextNode] = Editor.node(editor, nextPath); - - if (Element.isElement(nextNode) && nextNode.blockId !== undefined) { - return nextPath; - } - } catch (e) { - // ignore - } - - return findNextPath(editor, parentPath); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/withBlockInsertBreak.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/withBlockInsertBreak.ts deleted file mode 100644 index b6f8da0e56..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/withBlockInsertBreak.ts +++ /dev/null @@ -1,94 +0,0 @@ -import { ReactEditor } from 'slate-react'; -import { EditorNodeType } from '$app/application/document/document.types'; -import { CustomEditor } from '$app/components/editor/command'; -import { Path, Transforms } from 'slate'; -import { YjsEditor } from '@slate-yjs/core'; -import { generateId } from '$app/components/editor/provider/utils/convert'; - -export function withBlockInsertBreak(editor: ReactEditor) { - const { insertBreak } = editor; - - editor.insertBreak = (...args) => { - const block = CustomEditor.getBlock(editor); - - if (!block) return insertBreak(...args); - - const [node, path] = block; - - const isEmbed = editor.isEmbed(node); - - const nextPath = Path.next(path); - - if (isEmbed) { - CustomEditor.insertEmptyLine(editor as ReactEditor & YjsEditor, nextPath); - editor.select(nextPath); - return; - } - - const type = node.type as EditorNodeType; - - const isBeginning = CustomEditor.focusAtStartOfBlock(editor); - - const isEmpty = CustomEditor.isEmptyText(editor, node); - - if (isEmpty) { - const depth = path.length; - let hasNextNode = false; - - try { - hasNextNode = Boolean(editor.node(nextPath)); - } catch (e) { - // do nothing - } - - // if the node is empty and the depth is greater than 1, tab backward - if (depth > 1 && !hasNextNode) { - CustomEditor.tabBackward(editor); - return; - } - - // if the node is empty, convert it to a paragraph - if (type !== EditorNodeType.Paragraph && type !== EditorNodeType.Page) { - CustomEditor.turnToBlock(editor, { type: EditorNodeType.Paragraph }); - return; - } - } else if (isBeginning) { - // insert line below the current block - const newNodeType = [ - EditorNodeType.TodoListBlock, - EditorNodeType.BulletedListBlock, - EditorNodeType.NumberedListBlock, - ].includes(type) - ? type - : EditorNodeType.Paragraph; - - Transforms.insertNodes( - editor, - { - type: newNodeType, - data: node.data ?? {}, - blockId: generateId(), - children: [ - { - type: EditorNodeType.Text, - textId: generateId(), - children: [ - { - text: '', - }, - ], - }, - ], - }, - { - at: path, - } - ); - return; - } - - insertBreak(...args); - }; - - return editor; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/withBlockMove.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/withBlockMove.ts deleted file mode 100644 index 814c6e7333..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/withBlockMove.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { ReactEditor } from 'slate-react'; -import { generateId } from '$app/components/editor/provider/utils/convert'; -import { Editor, Element, Location, NodeEntry, Path, Node, Transforms } from 'slate'; -import { EditorNodeType } from '$app/application/document/document.types'; - -const matchPath = (editor: Editor, path: Path): ((node: Node) => boolean) => { - const [node] = Editor.node(editor, path); - - return (n) => { - return n === node; - }; -}; - -export function withBlockMove(editor: ReactEditor) { - const { moveNodes } = editor; - - editor.moveNodes = (args) => { - const { to } = args; - - moveNodes(args); - - replaceId(editor, to); - }; - - editor.liftNodes = (args = {}) => { - Editor.withoutNormalizing(editor, () => { - const { at = editor.selection, mode = 'lowest', voids = false } = args; - let { match } = args; - - if (!match) { - match = Path.isPath(at) - ? matchPath(editor, at) - : (n) => Element.isElement(n) && Editor.isBlock(editor, n) && n.blockId !== undefined; - } - - if (!at) { - return; - } - - const matches = Editor.nodes(editor, { at, match, mode, voids }); - - const pathRefs = Array.from(matches, ([, p]) => { - return Editor.pathRef(editor, p); - }); - - for (const pathRef of pathRefs) { - const path = pathRef.unref(); - - if (!path) return; - if (path.length < 2) { - throw new Error(`Cannot lift node at a path [${path}] because it has a depth of less than \`2\`.`); - } - - const parentNodeEntry = Editor.node(editor, Path.parent(path)); - const [parent, parentPath] = parentNodeEntry as NodeEntry; - const index = path[path.length - 1]; - const { length } = parent.children; - - if (length === 1) { - const toPath = Path.next(parentPath); - - Transforms.moveNodes(editor, { at: path, to: toPath, voids }); - Transforms.removeNodes(editor, { at: parentPath, voids }); - } else if (index === 0) { - Transforms.moveNodes(editor, { at: path, to: parentPath, voids }); - } else if (index === length - 1) { - const toPath = Path.next(parentPath); - - Transforms.moveNodes(editor, { at: path, to: toPath, voids }); - } else { - const toPath = Path.next(parentPath); - - const node = parent.children[index] as Element; - const nodeChildrenLength = node.children.length; - - for (let i = length - 1; i > index; i--) { - Transforms.moveNodes(editor, { - at: [...parentPath, i], - to: [...path, nodeChildrenLength], - mode: 'all', - }); - } - - Transforms.moveNodes(editor, { at: path, to: toPath, voids }); - } - } - }); - }; - - return editor; -} - -function replaceId(editor: Editor, at?: Location) { - const newBlockId = generateId(); - const newTextId = generateId(); - - const selection = editor.selection; - - const location = at || selection; - - if (!location) return; - - const [node, path] = editor.node(location) as NodeEntry; - - if (node.blockId === undefined) { - return; - } - - const [textNode, ...children] = node.children as Element[]; - - editor.setNodes( - { - blockId: newBlockId, - }, - { - at, - } - ); - - if (textNode && textNode.type === EditorNodeType.Text) { - editor.setNodes( - { - textId: newTextId, - }, - { - at: [...path, 0], - } - ); - } - - children.forEach((_, index) => { - replaceId(editor, [...path, index + 1]); - }); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/withBlockPlugins.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/withBlockPlugins.ts deleted file mode 100644 index 1e9fc7f105..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/withBlockPlugins.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { ReactEditor } from 'slate-react'; - -import { withBlockDelete } from '$app/components/editor/plugins/withBlockDelete'; -import { withBlockInsertBreak } from '$app/components/editor/plugins/withBlockInsertBreak'; -import { withSplitNodes } from '$app/components/editor/plugins/withSplitNodes'; -import { withPasted, withCopy } from '$app/components/editor/plugins/copyPasted'; -import { withBlockMove } from '$app/components/editor/plugins/withBlockMove'; -import { CustomEditor } from '$app/components/editor/command'; - -export function withBlockPlugins(editor: ReactEditor) { - const { isElementReadOnly, isEmpty, isSelectable } = editor; - - editor.isElementReadOnly = (element) => { - return CustomEditor.isEmbedNode(element) || isElementReadOnly(element); - }; - - editor.isEmbed = (element) => { - return CustomEditor.isEmbedNode(element); - }; - - editor.isSelectable = (element) => { - return !CustomEditor.isEmbedNode(element) && isSelectable(element); - }; - - editor.isEmpty = (element) => { - return !CustomEditor.isEmbedNode(element) && isEmpty(element); - }; - - return withPasted(withBlockMove(withSplitNodes(withBlockInsertBreak(withBlockDelete(withCopy(editor)))))); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/withSplitNodes.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/withSplitNodes.ts deleted file mode 100644 index eee7dd92d0..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/plugins/withSplitNodes.ts +++ /dev/null @@ -1,139 +0,0 @@ -import { ReactEditor } from 'slate-react'; -import { Transforms, Editor, Element, NodeEntry, Path, Range } from 'slate'; -import { EditorNodeType, ToggleListNode } from '$app/application/document/document.types'; -import { CustomEditor } from '$app/components/editor/command'; -import { generateId } from '$app/components/editor/provider/utils/convert'; -import cloneDeep from 'lodash-es/cloneDeep'; -import { SOFT_BREAK_TYPES } from '$app/components/editor/plugins/constants'; - -/** - * Split nodes. - * split text node into two text nodes, and wrap the second text node with a new block node. - * - * Split to the first child condition: - * 1. block type is toggle list block, and the block is not collapsed. - * - * Split to the next sibling condition: - * 1. block type is toggle list block, and the block is collapsed. - * 2. block type is other block type. - * - * Split to a paragraph node: (otherwise split to the same block type) - * 1. block type is heading block. - * 2. block type is quote block. - * 3. block type is page. - * 4. block type is code block and callout block. - * 5. block type is paragraph. - * - * @param editor - */ -export function withSplitNodes(editor: ReactEditor) { - const { splitNodes } = editor; - - editor.splitNodes = (...args) => { - const isInsertBreak = args.length === 1 && JSON.stringify(args[0]) === JSON.stringify({ always: true }); - - if (!isInsertBreak) { - splitNodes(...args); - return; - } - - const selection = editor.selection; - - const isCollapsed = selection && Range.isCollapsed(selection); - - if (!isCollapsed) { - editor.deleteFragment({ direction: 'backward' }); - } - - const match = CustomEditor.getBlock(editor); - - if (!match) { - splitNodes(...args); - return; - } - - const [node, path] = match; - const nodeType = node.type as EditorNodeType; - - const newBlockId = generateId(); - const newTextId = generateId(); - - splitNodes(...args); - - const matchTextNode = editor.above({ - match: (n) => !Editor.isEditor(n) && Element.isElement(n) && n.type === EditorNodeType.Text, - }); - - if (!matchTextNode) return; - const [textNode, textNodePath] = matchTextNode as NodeEntry; - - editor.removeNodes({ - at: textNodePath, - }); - - const newNodeType = [ - EditorNodeType.HeadingBlock, - EditorNodeType.QuoteBlock, - EditorNodeType.Page, - ...SOFT_BREAK_TYPES, - ].includes(node.type as EditorNodeType) - ? EditorNodeType.Paragraph - : node.type; - - const newNode: Element = { - type: newNodeType, - data: {}, - blockId: newBlockId, - children: [ - { - ...cloneDeep(textNode), - textId: newTextId, - }, - ], - }; - let newNodePath; - - if (nodeType === EditorNodeType.ToggleListBlock) { - const collapsed = (node as ToggleListNode).data.collapsed; - - if (!collapsed) { - newNode.type = EditorNodeType.Paragraph; - newNodePath = textNodePath; - } else { - newNode.type = EditorNodeType.ToggleListBlock; - newNodePath = Path.next(path); - } - - Transforms.insertNodes(editor, newNode, { - at: newNodePath, - }); - - editor.select(newNodePath); - - CustomEditor.removeMarks(editor); - editor.collapse({ - edge: 'start', - }); - return; - } - - newNodePath = textNodePath; - - Transforms.insertNodes(editor, newNode, { - at: newNodePath, - }); - - editor.select(newNodePath); - editor.collapse({ - edge: 'start', - }); - - editor.liftNodes({ - at: newNodePath, - }); - - CustomEditor.removeMarks(editor); - }; - - return editor; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/__tests__/action.test.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/__tests__/action.test.ts deleted file mode 100644 index 026ee57222..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/__tests__/action.test.ts +++ /dev/null @@ -1,124 +0,0 @@ -import { applyActions } from './utils/mockBackendService'; -import { generateId } from '$app/components/editor/provider/utils/convert'; -import { Provider } from '$app/components/editor/provider'; -import * as Y from 'yjs'; -import { BlockActionTypePB } from '@/services/backend'; -import { - generateFormulaInsertTextOp, - generateInsertTextOp, - genersteMentionInsertTextOp, -} from '$app/components/editor/provider/__tests__/utils/convert'; - -describe('Transform events to actions', () => { - let provider: Provider; - beforeEach(() => { - provider = new Provider(generateId()); - provider.initialDocument(true); - provider.connect(); - applyActions.mockClear(); - }); - - afterEach(() => { - provider.disconnect(); - }); - - test('should transform insert event to insert action', () => { - const sharedType = provider.sharedType; - - const insertTextOp = generateInsertTextOp('insert text'); - - sharedType?.applyDelta([{ retain: 2 }, insertTextOp]); - - const actions = applyActions.mock.calls[0][1]; - expect(actions).toHaveLength(2); - const textId = actions[0].payload.text_id; - expect(actions[0].action).toBe(BlockActionTypePB.InsertText); - expect(actions[0].payload.delta).toBe('[{"insert":"insert text"}]'); - expect(actions[1].action).toBe(BlockActionTypePB.Insert); - expect(actions[1].payload.block.ty).toBe('paragraph'); - expect(actions[1].payload.block.parent_id).toBe('3EzeCrtxlh'); - expect(actions[1].payload.block.children_id).not.toBeNull(); - expect(actions[1].payload.block.external_id).toBe(textId); - expect(actions[1].payload.parent_id).toBe('3EzeCrtxlh'); - expect(actions[1].payload.prev_id).toBe('2qonPRrNTO'); - }); - - test('should transform delete event to delete action', () => { - const sharedType = provider.sharedType; - - sharedType?.doc?.transact(() => { - sharedType?.applyDelta([{ retain: 4 }, { delete: 1 }]); - }); - - const actions = applyActions.mock.calls[0][1]; - expect(actions).toHaveLength(1); - expect(actions[0].action).toBe(BlockActionTypePB.Delete); - expect(actions[0].payload.block.id).toBe('Fn4KACkt1i'); - }); - - test('should transform update event to update action', () => { - const sharedType = provider.sharedType; - - const yText = sharedType?.toDelta()[4].insert as Y.XmlText; - sharedType?.doc?.transact(() => { - yText.setAttribute('data', { - checked: true, - }); - }); - - const actions = applyActions.mock.calls[0][1]; - expect(actions).toHaveLength(1); - expect(actions[0].action).toBe(BlockActionTypePB.Update); - expect(actions[0].payload.block.id).toBe('Fn4KACkt1i'); - expect(actions[0].payload.block.data).toBe('{"checked":true}'); - }); - - test('should transform apply delta event to apply delta action (insert text)', () => { - const sharedType = provider.sharedType; - - const blockYText = sharedType?.toDelta()[4].insert as Y.XmlText; - const textYText = blockYText.toDelta()[0].insert as Y.XmlText; - sharedType?.doc?.transact(() => { - textYText.applyDelta([{ retain: 1 }, { insert: 'apply delta' }]); - }); - const textId = textYText.getAttribute('textId'); - - const actions = applyActions.mock.calls[0][1]; - expect(actions).toHaveLength(1); - expect(actions[0].action).toBe(BlockActionTypePB.ApplyTextDelta); - expect(actions[0].payload.text_id).toBe(textId); - expect(actions[0].payload.delta).toBe('[{"retain":1},{"insert":"apply delta"}]'); - }); - - test('should transform apply delta event to apply delta action: insert mention', () => { - const sharedType = provider.sharedType; - - const blockYText = sharedType?.toDelta()[4].insert as Y.XmlText; - const yText = blockYText.toDelta()[0].insert as Y.XmlText; - sharedType?.doc?.transact(() => { - yText.applyDelta([{ retain: 1 }, genersteMentionInsertTextOp()]); - }); - - const actions = applyActions.mock.calls[0][1]; - expect(actions).toHaveLength(1); - expect(actions[0].action).toBe(BlockActionTypePB.ApplyTextDelta); - expect(actions[0].payload.delta).toBe('[{"retain":1},{"insert":"@","attributes":{"mention":{"page":"page_id"}}}]'); - }); - - test('should transform apply delta event to apply delta action: insert formula', () => { - const sharedType = provider.sharedType; - - const blockYText = sharedType?.toDelta()[4].insert as Y.XmlText; - const yText = blockYText.toDelta()[0].insert as Y.XmlText; - sharedType?.doc?.transact(() => { - yText.applyDelta([{ retain: 1 }, generateFormulaInsertTextOp()]); - }); - - const actions = applyActions.mock.calls[0][1]; - expect(actions).toHaveLength(1); - expect(actions[0].action).toBe(BlockActionTypePB.ApplyTextDelta); - expect(actions[0].payload.delta).toBe('[{"retain":1},{"insert":"= 1 + 1","attributes":{"formula":true}}]'); - }); -}); - -export {}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/__tests__/observe.test.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/__tests__/observe.test.ts deleted file mode 100644 index 0937d265ed..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/__tests__/observe.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { applyActions } from './utils/mockBackendService'; - -import { Provider } from '$app/components/editor/provider'; -import { generateId } from '$app/components/editor/provider/utils/convert'; -import { generateInsertTextOp } from '$app/components/editor/provider/__tests__/utils/convert'; - -export {}; - -describe('Provider connected', () => { - let provider: Provider; - - beforeEach(() => { - provider = new Provider(generateId()); - provider.initialDocument(true); - provider.connect(); - applyActions.mockClear(); - }); - - afterEach(() => { - provider.disconnect(); - }); - - test('should initial document', () => { - const sharedType = provider.sharedType; - expect(sharedType).not.toBeNull(); - expect(sharedType?.length).toBe(25); - expect(sharedType?.getAttribute('blockId')).toBe('3EzeCrtxlh'); - }); - - test('should send actions when the local changed', () => { - const sharedType = provider.sharedType; - - const parentId = sharedType?.getAttribute('blockId') as string; - const insertTextOp = generateInsertTextOp(''); - - sharedType?.applyDelta([{ retain: 2 }, insertTextOp]); - - expect(sharedType?.length).toBe(26); - expect(applyActions).toBeCalledTimes(1); - }); -}); - -export {}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/__tests__/read_me.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/__tests__/read_me.ts deleted file mode 100644 index adb85f2bfd..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/__tests__/read_me.ts +++ /dev/null @@ -1,437 +0,0 @@ -export default { - viewId: '04acbc17-2265-4e4d-ac80-392bdc81379f', - rootId: '3EzeCrtxlh', - nodeMap: { - '692ooXzoV-': { - id: '692ooXzoV-', - type: 'todo_list', - parent: '3EzeCrtxlh', - children: '4yIcpjxFTQ', - data: { checked: false }, - externalId: '9L10h3UZ7J', - externalType: 'text', - }, - gCjs671FiD: { - id: 'gCjs671FiD', - type: 'paragraph', - parent: '3EzeCrtxlh', - children: 'Bj4M6midh3', - data: {}, - externalId: 'uGR_eATq2B', - externalType: 'text', - }, - whGVOpFJzA: { - id: 'whGVOpFJzA', - type: 'code', - parent: '3EzeCrtxlh', - children: 'eYqZSaUSrF', - data: { language: 'rust' }, - externalId: '2H8dnFhOsJ', - externalType: 'text', - }, - PeUTr8lpaW: { - id: 'PeUTr8lpaW', - type: 'divider', - parent: '3EzeCrtxlh', - children: '7gXZ4anHxc', - data: {}, - externalId: '', - externalType: '', - }, - aRUJ8rTJR9: { - id: 'aRUJ8rTJR9', - type: 'quote', - parent: '3EzeCrtxlh', - children: '877leNxAdX', - data: {}, - externalId: 'Qdn9CIuCJb', - externalType: 'text', - }, - '5OZNiernqA': { - id: '5OZNiernqA', - type: 'paragraph', - parent: '3EzeCrtxlh', - children: 'rnWU0OMG2D', - data: {}, - externalId: '7szeShePbX', - externalType: 'text', - }, - '6okS7LcJz6': { - id: '6okS7LcJz6', - type: 'paragraph', - parent: '3EzeCrtxlh', - children: 'o2VwjuyMqD', - data: {}, - externalId: 'T7KYfpQzkC', - externalType: 'text', - }, - '2qonPRrNTO': { - id: '2qonPRrNTO', - type: 'heading', - parent: '3EzeCrtxlh', - children: 'EwyNm_pVG3', - data: { level: 1 }, - externalId: 'zatA8Lta9U', - externalType: 'text', - }, - G6SPYNOXyd: { - id: 'G6SPYNOXyd', - type: 'heading', - parent: '3EzeCrtxlh', - children: 'dMAT_mdB3t', - data: { level: 2 }, - externalId: 'EZJU9Ks-XL', - externalType: 'text', - }, - 'i-7TRjZWn4': { - id: 'i-7TRjZWn4', - type: 'todo_list', - parent: '3EzeCrtxlh', - children: '3NsAmPWBLT', - data: { checked: true }, - externalId: 'rG9KOmfyQc', - externalType: 'text', - }, - mAce5pJ5iN: { - id: 'mAce5pJ5iN', - type: 'paragraph', - parent: '3EzeCrtxlh', - children: 'PavtRGeb_I', - data: {}, - externalId: 'UUBk3lnHDj', - externalType: 'text', - }, - ViWXVLgaux: { - id: 'ViWXVLgaux', - type: 'numbered_list', - parent: '3EzeCrtxlh', - children: 'osPbZZroOQ', - data: {}, - externalId: 'OkO9CIoWYX', - externalType: 'text', - }, - VrW0GWtvmq: { - id: 'VrW0GWtvmq', - type: 'todo_list', - parent: '3EzeCrtxlh', - children: 'hFX8bE3MQ6', - data: { checked: false }, - externalId: 'wBzhBx7bcM', - externalType: 'text', - }, - YzM4q9vJgy: { - id: 'YzM4q9vJgy', - type: 'paragraph', - parent: '3EzeCrtxlh', - children: 'mlDk334eJ5', - data: {}, - externalId: 'wIXo3cMKpn', - externalType: 'text', - }, - okBQecghDx: { - id: 'okBQecghDx', - type: 'todo_list', - parent: '3EzeCrtxlh', - children: 'r7U-ocUQEj', - data: { checked: false }, - externalId: '8T-1vemF9G', - externalType: 'text', - }, - qJcR2SnePa: { - id: 'qJcR2SnePa', - type: 'callout', - parent: '3EzeCrtxlh', - children: 'X8-C5rdFkI', - data: { icon: '🥰' }, - externalId: 'o3mqcqjEvX', - externalType: 'text', - }, - q8trFTc21J: { - id: 'q8trFTc21J', - type: 'numbered_list', - parent: '3EzeCrtxlh', - children: 'Ye_KrA1Zqb', - data: {}, - externalId: 'E-XQYK1KGP', - externalType: 'text', - }, - '-7trMtJMEt': { - id: '-7trMtJMEt', - type: 'numbered_list', - parent: '3EzeCrtxlh', - children: '5-RQuN9654', - data: {}, - externalId: 'meKXZh3E_1', - externalType: 'text', - }, - Fn4KACkt1i: { - id: 'Fn4KACkt1i', - type: 'todo_list', - parent: '3EzeCrtxlh', - children: 'MM6vCgc7RC', - data: { checked: false }, - externalId: 'v0XWYu0w3F', - externalType: 'text', - }, - TTU0eUzM4G: { - id: 'TTU0eUzM4G', - type: 'paragraph', - parent: '3EzeCrtxlh', - children: 'Jo1ix-lCgZ', - data: {}, - externalId: 'IRpzcwVaU4', - externalType: 'text', - }, - '6QZZccBfnT': { - id: '6QZZccBfnT', - type: 'heading', - parent: '3EzeCrtxlh', - children: 'bdI2gAQB-G', - data: { level: 2 }, - externalId: 'J-oMw2g2_D', - externalType: 'text', - }, - sZT5qbJvLX: { - id: 'sZT5qbJvLX', - type: 'paragraph', - parent: '3EzeCrtxlh', - children: 'yR1_d6lPtR', - data: {}, - externalId: 'anA255zYMV', - externalType: 'text', - }, - '3EzeCrtxlh': { - id: '3EzeCrtxlh', - type: 'page', - parent: '', - children: 'ChrjyUcqp5', - data: {}, - externalId: 'bGaty5Tv88', - externalType: 'text', - }, - 'b3G76VM-nh': { - id: 'b3G76VM-nh', - type: 'heading', - parent: '3EzeCrtxlh', - children: '7PDV2Ev8pz', - data: { level: 2 }, - externalId: 'baM4S6ohnQ', - externalType: 'text', - }, - CxPil0324P: { - id: 'CxPil0324P', - type: 'todo_list', - parent: '3EzeCrtxlh', - children: 'qJkq_FYLux', - data: { checked: false }, - externalId: 'LGUrob79hg', - externalType: 'text', - }, - }, - childrenMap: { - '-7trMtJMEt': [], - okBQecghDx: [], - PeUTr8lpaW: [], - '6QZZccBfnT': [], - aRUJ8rTJR9: [], - G6SPYNOXyd: [], - VrW0GWtvmq: [], - 'i-7TRjZWn4': [], - '6okS7LcJz6': [], - Fn4KACkt1i: [], - qJcR2SnePa: [], - sZT5qbJvLX: [], - '2qonPRrNTO': [], - CxPil0324P: [], - YzM4q9vJgy: [], - mAce5pJ5iN: [], - gCjs671FiD: [], - q8trFTc21J: [], - whGVOpFJzA: [], - '5OZNiernqA': [], - 'b3G76VM-nh': [], - TTU0eUzM4G: [], - '3EzeCrtxlh': [ - '2qonPRrNTO', - 'b3G76VM-nh', - 'CxPil0324P', - 'Fn4KACkt1i', - 'okBQecghDx', - 'VrW0GWtvmq', - 'i-7TRjZWn4', - '692ooXzoV-', - 'sZT5qbJvLX', - 'PeUTr8lpaW', - 'YzM4q9vJgy', - 'G6SPYNOXyd', - '-7trMtJMEt', - 'q8trFTc21J', - 'ViWXVLgaux', - 'whGVOpFJzA', - 'mAce5pJ5iN', - '6QZZccBfnT', - 'aRUJ8rTJR9', - 'gCjs671FiD', - 'qJcR2SnePa', - '5OZNiernqA', - 'TTU0eUzM4G', - '6okS7LcJz6', - ], - ViWXVLgaux: [], - '692ooXzoV-': [], - }, - relativeMap: { - '4yIcpjxFTQ': '692ooXzoV-', - Bj4M6midh3: 'gCjs671FiD', - eYqZSaUSrF: 'whGVOpFJzA', - '7gXZ4anHxc': 'PeUTr8lpaW', - '877leNxAdX': 'aRUJ8rTJR9', - rnWU0OMG2D: '5OZNiernqA', - o2VwjuyMqD: '6okS7LcJz6', - EwyNm_pVG3: '2qonPRrNTO', - dMAT_mdB3t: 'G6SPYNOXyd', - '3NsAmPWBLT': 'i-7TRjZWn4', - PavtRGeb_I: 'mAce5pJ5iN', - osPbZZroOQ: 'ViWXVLgaux', - hFX8bE3MQ6: 'VrW0GWtvmq', - mlDk334eJ5: 'YzM4q9vJgy', - 'r7U-ocUQEj': 'okBQecghDx', - 'X8-C5rdFkI': 'qJcR2SnePa', - Ye_KrA1Zqb: 'q8trFTc21J', - '5-RQuN9654': '-7trMtJMEt', - MM6vCgc7RC: 'Fn4KACkt1i', - 'Jo1ix-lCgZ': 'TTU0eUzM4G', - 'bdI2gAQB-G': '6QZZccBfnT', - yR1_d6lPtR: 'sZT5qbJvLX', - ChrjyUcqp5: '3EzeCrtxlh', - '7PDV2Ev8pz': 'b3G76VM-nh', - qJkq_FYLux: 'CxPil0324P', - }, - deltaMap: { - gCjs671FiD: [], - G6SPYNOXyd: [{ insert: 'Keyboard shortcuts, markdown, and code block' }], - VrW0GWtvmq: [ - { insert: 'Type ' }, - { insert: '/', attributes: { code: true } }, - { insert: ' followed by ' }, - { insert: '/bullet', attributes: { code: true } }, - { insert: ' or ' }, - { insert: '/num', attributes: { code: true } }, - { insert: ' to create a list.', attributes: { code: false } }, - ], - '-7trMtJMEt': [ - { insert: 'Keyboard shortcuts ' }, - { - insert: 'guide', - attributes: { href: 'https://appflowy.gitbook.io/docs/essential-documentation/shortcuts' }, - }, - ], - okBQecghDx: [ - { insert: 'As soon as you type ' }, - { insert: '/', attributes: { font_color: '0xff00b5ff', code: true } }, - { insert: ' a menu will pop up. Select ' }, - { insert: 'different types', attributes: { bg_color: '0x4d9c27b0' } }, - { insert: ' of content blocks you can add.' }, - ], - qJcR2SnePa: [ - { insert: '\nLike AppFlowy? Follow us:\n' }, - { insert: 'GitHub', attributes: { href: 'https://github.com/AppFlowy-IO/AppFlowy' } }, - { insert: '\n' }, - { insert: 'Twitter', attributes: { href: 'https://twitter.com/appflowy' } }, - { insert: ': @appflowy\n' }, - { insert: 'Newsletter', attributes: { href: 'https://blog-appflowy.ghost.io/' } }, - { insert: '\n' }, - ], - whGVOpFJzA: [ - { - insert: - '// This is the main function.\nfn main() {\n // Print text to the console.\n println!("Hello World!");\n}', - }, - ], - YzM4q9vJgy: [], - '692ooXzoV-': [ - { insert: 'Click ' }, - { insert: '+', attributes: { code: true } }, - { insert: ' next to any page title in the sidebar to ' }, - { insert: 'quickly', attributes: { font_color: '0xff8427e0' } }, - { insert: ' add a new subpage, ' }, - { insert: 'Document', attributes: { code: true } }, - { insert: ', ', attributes: { code: false } }, - { insert: 'Grid', attributes: { code: true } }, - { insert: ', or ', attributes: { code: false } }, - { insert: 'Kanban Board', attributes: { code: true } }, - { insert: '.', attributes: { code: false } }, - ], - q8trFTc21J: [ - { insert: 'Markdown ' }, - { - insert: 'reference', - attributes: { href: 'https://appflowy.gitbook.io/docs/essential-documentation/markdown' }, - }, - ], - ViWXVLgaux: [ - { insert: 'Type ' }, - { insert: '/code', attributes: { code: true } }, - { insert: ' to insert a code block', attributes: { code: false } }, - ], - sZT5qbJvLX: [], - '6QZZccBfnT': [{ insert: 'Have a question❓' }], - '5OZNiernqA': [], - '6okS7LcJz6': [], - mAce5pJ5iN: [], - aRUJ8rTJR9: [ - { insert: 'Click ' }, - { insert: '?', attributes: { code: true } }, - { insert: ' at the bottom right for help and support.' }, - ], - Fn4KACkt1i: [ - { insert: 'Highlight ', attributes: { bg_color: '0x4dffeb3b' } }, - { insert: 'any text, and use the editing menu to ' }, - { insert: 'style', attributes: { italic: true } }, - { insert: ' ' }, - { insert: 'your', attributes: { bold: true } }, - { insert: ' ' }, - { insert: 'writing', attributes: { underline: true } }, - { insert: ' ' }, - { insert: 'however', attributes: { code: true } }, - { insert: ' you ' }, - { insert: 'like.', attributes: { strikethrough: true } }, - ], - '3EzeCrtxlh': [], - '2qonPRrNTO': [{ insert: 'Welcome to AppFlowy!' }], - 'b3G76VM-nh': [{ insert: 'Here are the basics' }], - TTU0eUzM4G: [], - CxPil0324P: [{ insert: 'Click anywhere and just start typing.' }], - 'i-7TRjZWn4': [ - { insert: 'Click ' }, - { insert: '+ New Page ', attributes: { code: true } }, - { insert: 'button at the bottom of your sidebar to add a new page.' }, - ], - }, - externalIdMap: { - '9L10h3UZ7J': '692ooXzoV-', - uGR_eATq2B: 'gCjs671FiD', - '2H8dnFhOsJ': 'whGVOpFJzA', - Qdn9CIuCJb: 'aRUJ8rTJR9', - '7szeShePbX': '5OZNiernqA', - T7KYfpQzkC: '6okS7LcJz6', - zatA8Lta9U: '2qonPRrNTO', - 'EZJU9Ks-XL': 'G6SPYNOXyd', - rG9KOmfyQc: 'i-7TRjZWn4', - UUBk3lnHDj: 'mAce5pJ5iN', - OkO9CIoWYX: 'ViWXVLgaux', - wBzhBx7bcM: 'VrW0GWtvmq', - wIXo3cMKpn: 'YzM4q9vJgy', - '8T-1vemF9G': 'okBQecghDx', - o3mqcqjEvX: 'qJcR2SnePa', - 'E-XQYK1KGP': 'q8trFTc21J', - meKXZh3E_1: '-7trMtJMEt', - v0XWYu0w3F: 'Fn4KACkt1i', - IRpzcwVaU4: 'TTU0eUzM4G', - 'J-oMw2g2_D': '6QZZccBfnT', - anA255zYMV: 'sZT5qbJvLX', - bGaty5Tv88: '3EzeCrtxlh', - baM4S6ohnQ: 'b3G76VM-nh', - LGUrob79hg: 'CxPil0324P', - }, -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/__tests__/utils/convert.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/__tests__/utils/convert.ts deleted file mode 100644 index 028fff7419..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/__tests__/utils/convert.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * @jest-environment jsdom - */ -import { slateNodesToInsertDelta } from '@slate-yjs/core'; -import * as Y from 'yjs'; -import { generateId } from '$app/components/editor/provider/utils/convert'; - -export function slateElementToYText({ - children, - ...attributes -}: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - children: any; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [key: string]: any; -}) { - const yElement = new Y.XmlText(); - - Object.entries(attributes).forEach(([key, value]) => { - yElement.setAttribute(key, value); - }); - yElement.applyDelta(slateNodesToInsertDelta(children), { - sanitize: false, - }); - return yElement; -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function generateInsertTextOp(text: string) { - const insertYText = slateElementToYText({ - children: [ - { - type: 'text', - textId: generateId(), - children: [ - { - text, - }, - ], - }, - ], - type: 'paragraph', - data: {}, - blockId: generateId(), - }); - - return { - insert: insertYText, - }; -} - -export function genersteMentionInsertTextOp() { - const mentionYText = slateElementToYText({ - children: [{ text: '@' }], - type: 'mention', - data: { - page: 'page_id', - }, - }); - - return { - insert: mentionYText, - }; -} - -export function generateFormulaInsertTextOp() { - const formulaYText = slateElementToYText({ - children: [{ text: '= 1 + 1' }], - type: 'formula', - data: true, - }); - - return { - insert: formulaYText, - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/__tests__/utils/mockBackendService.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/__tests__/utils/mockBackendService.ts deleted file mode 100644 index 3bd7646268..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/__tests__/utils/mockBackendService.ts +++ /dev/null @@ -1,21 +0,0 @@ -import read_me from '$app/components/editor/provider/__tests__/read_me'; - -const applyActions = jest.fn().mockReturnValue(Promise.resolve()); - -jest.mock('$app/application/notification', () => { - return { - subscribeNotification: jest.fn().mockReturnValue(Promise.resolve(() => ({}))), - }; -}); - -jest.mock('nanoid', () => ({ nanoid: jest.fn().mockReturnValue(String(Math.random())) })); - -jest.mock('$app/application/document/document.service', () => { - return { - openDocument: jest.fn().mockReturnValue(Promise.resolve(read_me)), - applyActions, - closeDocument: jest.fn().mockReturnValue(Promise.resolve()), - }; -}); - -export { applyActions }; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/data_client.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/data_client.ts deleted file mode 100644 index bf0ea2c2a7..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/data_client.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { applyActions, closeDocument, openDocument } from '$app/application/document/document.service'; -import { slateNodesToInsertDelta } from '@slate-yjs/core'; -import { convertToSlateValue } from '$app/components/editor/provider/utils/convert'; -import { EventEmitter } from 'events'; -import { BlockActionPB, DocEventPB, DocumentNotification } from '@/services/backend'; -import { AsyncQueue } from '$app/utils/async_queue'; -import { subscribeNotification } from '$app/application/notification'; -import { YDelta } from '$app/components/editor/provider/types/y_event'; -import { DocEvent2YDelta } from '$app/components/editor/provider/utils/delta'; - -export class DataClient extends EventEmitter { - private queue: AsyncQueue[]>; - private unsubscribe: Promise<() => void>; - public rootId?: string; - - constructor(private id: string) { - super(); - this.queue = new AsyncQueue(this.sendActions); - this.unsubscribe = subscribeNotification(DocumentNotification.DidReceiveUpdate, this.sendMessage); - - this.on('update', this.handleReceiveMessage); - } - - public disconnect() { - this.off('update', this.handleReceiveMessage); - void closeDocument(this.id); - void this.unsubscribe.then((unsubscribe) => unsubscribe()); - } - - public async getInsertDelta(includeRoot = true) { - const data = await openDocument(this.id); - - this.rootId = data.rootId; - - const slateValue = convertToSlateValue(data, includeRoot); - - return slateNodesToInsertDelta(slateValue); - } - - public on(event: 'change', listener: (events: YDelta) => void): this; - public on(event: 'update', listener: (actions: ReturnType[]) => void): this; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - public on(event: string, listener: (...args: any[]) => void): this { - return super.on(event, listener); - } - - public off(event: 'change', listener: (events: YDelta) => void): this; - public off(event: 'update', listener: (actions: ReturnType[]) => void): this; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - public off(event: string, listener: (...args: any[]) => void): this { - return super.off(event, listener); - } - - public emit(event: 'change', events: YDelta): boolean; - public emit(event: 'update', actions: ReturnType[]): boolean; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - public emit(event: string, ...args: any[]): boolean { - return super.emit(event, ...args); - } - - private sendMessage = (docEvent: DocEventPB) => { - // transform events to ops - this.emit('change', DocEvent2YDelta(docEvent)); - }; - - private handleReceiveMessage = (actions: ReturnType[]) => { - this.queue.enqueue(actions); - }; - - private sendActions = async (actions: ReturnType[]) => { - if (!actions.length) return; - await applyActions(this.id, actions); - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/index.ts deleted file mode 100644 index 03be03e588..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './provider'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/provider.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/provider.ts deleted file mode 100644 index 727b33ec69..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/provider.ts +++ /dev/null @@ -1,74 +0,0 @@ -import * as Y from 'yjs'; - -import { DataClient } from '$app/components/editor/provider/data_client'; -import { YDelta } from '$app/components/editor/provider/types/y_event'; -import { YEvents2BlockActions } from '$app/components/editor/provider/utils/action'; -import { EventEmitter } from 'events'; - -const REMOTE_ORIGIN = 'remote'; - -export class Provider extends EventEmitter { - document: Y.Doc = new Y.Doc(); - sharedType: Y.XmlText | null = null; - dataClient: DataClient; - // get origin data after document updated - backupDoc: Y.Doc = new Y.Doc(); - constructor(public id: string) { - super(); - this.dataClient = new DataClient(id); - this.document.on('update', this.documentUpdate); - } - - initialDocument = async (includeRoot = true) => { - const sharedType = this.document.get('sharedType', Y.XmlText) as Y.XmlText; - // Load the initial value into the yjs document - const delta = await this.dataClient.getInsertDelta(includeRoot); - - sharedType.applyDelta(delta); - - const rootId = this.dataClient.rootId as string; - const root = delta[0].insert as Y.XmlText; - const data = root.getAttribute('data'); - - sharedType.setAttribute('blockId', rootId); - sharedType.setAttribute('data', data); - - this.sharedType = sharedType; - this.sharedType?.observeDeep(this.onChange); - this.emit('ready'); - }; - - connect() { - this.dataClient.on('change', this.onRemoteChange); - return; - } - - disconnect() { - this.dataClient.off('change', this.onRemoteChange); - this.dataClient.disconnect(); - this.sharedType?.unobserveDeep(this.onChange); - this.sharedType = null; - } - - onChange = (events: Y.YEvent[], transaction: Y.Transaction) => { - if (transaction.origin === REMOTE_ORIGIN) { - return; - } - - if (!this.sharedType || !events.length) return; - // transform events to actions - this.dataClient.emit('update', YEvents2BlockActions(this.backupDoc, events)); - }; - - onRemoteChange = (delta: YDelta) => { - if (!delta.length) return; - - this.document.transact(() => { - this.sharedType?.applyDelta(delta); - }, REMOTE_ORIGIN); - }; - - documentUpdate = (update: Uint8Array) => { - Y.applyUpdate(this.backupDoc, update); - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/types/y_event.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/types/y_event.ts deleted file mode 100644 index 36ec97aa39..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/types/y_event.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { YXmlText } from 'yjs/dist/src/types/YXmlText'; - -export interface YOp { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - insert?: string | object | any[] | YXmlText | undefined; - retain?: number | undefined; - delete?: number | undefined; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - attributes?: { [p: string]: any } | undefined; -} - -export type YDelta = YOp[]; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/utils/action.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/utils/action.ts deleted file mode 100644 index 447a8f95f9..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/utils/action.ts +++ /dev/null @@ -1,301 +0,0 @@ -import * as Y from 'yjs'; -import { BlockActionPB, BlockActionTypePB } from '@/services/backend'; -import { generateId } from '$app/components/editor/provider/utils/convert'; -import { YDelta2Delta } from '$app/components/editor/provider/utils/delta'; -import { YDelta } from '$app/components/editor/provider/types/y_event'; -import { getInsertTarget, getYTarget } from '$app/components/editor/provider/utils/relation'; -import { EditorInlineNodeType, EditorNodeType } from '$app/application/document/document.types'; -import { Log } from '$app/utils/log'; - -export function YEvents2BlockActions( - backupDoc: Readonly, - events: Y.YEvent[] -): ReturnType[] { - const actions: ReturnType[] = []; - - events.forEach((event) => { - const eventActions = YEvent2BlockActions(backupDoc, event); - - if (eventActions.length === 0) return; - - actions.push(...eventActions); - }); - - return actions; -} - -export function YEvent2BlockActions( - backupDoc: Readonly, - event: Y.YEvent -): ReturnType[] { - const { target: yXmlText, keys, delta, path } = event; - const isBlockEvent = !!yXmlText.getAttribute('blockId'); - const sharedType = backupDoc.get('sharedType', Y.XmlText) as Readonly; - const rootId = sharedType.getAttribute('blockId'); - - const backupTarget = getYTarget(backupDoc, path) as Readonly; - const actions = []; - - if ([EditorInlineNodeType.Formula, EditorInlineNodeType.Mention].includes(yXmlText.getAttribute('type'))) { - const parentYXmlText = yXmlText.parent as Y.XmlText; - const parentDelta = parentYXmlText.toDelta() as YDelta; - const index = parentDelta.findIndex((op) => op.insert === yXmlText); - const ops = YDelta2Delta(parentDelta); - - const retainIndex = ops.reduce((acc, op, currentIndex) => { - if (currentIndex < index) { - return acc + (op.insert as string).length ?? 0; - } - - return acc; - }, 0); - - const newDelta = [ - { - retain: retainIndex, - }, - ...delta, - ]; - - actions.push(...generateApplyTextActions(parentYXmlText, newDelta)); - } - - if (yXmlText.getAttribute('type') === 'text') { - actions.push(...textOps2BlockActions(rootId, yXmlText, delta)); - } - - if (keys.size > 0) { - actions.push(...dataOps2BlockActions(yXmlText, keys)); - } - - if (isBlockEvent) { - actions.push(...blockOps2BlockActions(backupTarget, delta)); - } - - return actions; -} - -function textOps2BlockActions( - rootId: string, - yXmlText: Y.XmlText, - ops: YDelta -): ReturnType[] { - if (ops.length === 0) return []; - const blockYXmlText = yXmlText.parent as Y.XmlText; - const blockId = blockYXmlText.getAttribute('blockId'); - - if (blockId === rootId) { - return []; - } - - return generateApplyTextActions(yXmlText, ops); -} - -function dataOps2BlockActions( - yXmlText: Y.XmlText, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - keys: Map -) { - const dataUpdated = keys.has('data'); - - if (!dataUpdated) return []; - const data = yXmlText.getAttribute('data'); - - return generateUpdateActions(yXmlText, { - data, - }); -} - -function blockOps2BlockActions( - blockYXmlText: Readonly, - ops: YDelta -): ReturnType[] { - const actions: ReturnType[] = []; - - let index = 0; - - ops.forEach((op) => { - if (op.insert) { - if (op.insert instanceof Y.XmlText) { - const insertYXmlText = op.insert; - const blockId = insertYXmlText.getAttribute('blockId'); - const textId = insertYXmlText.getAttribute('textId'); - - if (!blockId && !textId) { - throw new Error('blockId and textId is not exist'); - } - - if (blockId) { - actions.push(...generateInsertBlockActions(insertYXmlText)); - index += 1; - } - - if (textId) { - const target = getInsertTarget(blockYXmlText, [0]); - - if (target) { - const length = target.length; - - const delta = [{ delete: length }, ...insertYXmlText.toDelta()]; - - // restore textId - insertYXmlText.setAttribute('textId', target.getAttribute('textId')); - actions.push(...generateApplyTextActions(target, delta)); - } - } - } - } else if (op.retain) { - index += op.retain; - } else if (op.delete) { - let i = 0; - - for (; i < op.delete; i++) { - const target = getInsertTarget(blockYXmlText, [i + index]); - - if (target && target !== blockYXmlText) { - const deletedId = target.getAttribute('blockId') as string; - - if (deletedId) { - actions.push( - ...generateDeleteBlockActions({ - ids: [deletedId], - }) - ); - } else { - Log.error('blockOps2BlockActions', 'deletedId is not exist'); - } - } - } - - index += i; - } - }); - - return actions; -} - -export function generateUpdateActions( - yXmlText: Y.XmlText, - { - data, - }: { - data?: Record; - external_id?: string; - } -) { - const id = yXmlText.getAttribute('blockId'); - const parentId = yXmlText.getAttribute('parentId'); - - return [ - { - action: BlockActionTypePB.Update, - payload: { - block: { - id, - data: JSON.stringify(data), - }, - parent_id: parentId, - }, - }, - ]; -} - -export function generateApplyTextActions(yXmlText: Y.XmlText, delta: YDelta) { - const externalId = yXmlText.getAttribute('textId'); - - if (!externalId) return []; - - const deltaString = JSON.stringify(YDelta2Delta(delta)); - - return [ - { - action: BlockActionTypePB.ApplyTextDelta, - payload: { - text_id: externalId, - delta: deltaString, - }, - }, - ]; -} - -export function generateDeleteBlockActions({ ids }: { ids: string[] }) { - return ids.map((id) => ({ - action: BlockActionTypePB.Delete, - payload: { - block: { - id, - }, - parent_id: '', - }, - })); -} - -export function generateInsertTextActions(insertYXmlText: Y.XmlText) { - const textId = insertYXmlText.getAttribute('textId'); - const delta = YDelta2Delta(insertYXmlText.toDelta()); - - return [ - { - action: BlockActionTypePB.InsertText, - payload: { - text_id: textId, - delta: JSON.stringify(delta), - }, - }, - ]; -} - -export function generateInsertBlockActions( - insertYXmlText: Y.XmlText -): ReturnType[] { - const childrenId = generateId(); - - const [textInsert, ...childrenInserts] = (insertYXmlText.toDelta() as YDelta).map((op) => op.insert); - const textInsertActions = textInsert instanceof Y.XmlText ? generateInsertTextActions(textInsert) : []; - const externalId = textInsertActions[0]?.payload.text_id; - const prev = insertYXmlText.prevSibling; - const prevId = prev ? prev.getAttribute('blockId') : null; - const parentId = (insertYXmlText.parent as Y.XmlText).getAttribute('blockId'); - - const data = insertYXmlText.getAttribute('data'); - const type = insertYXmlText.getAttribute('type'); - const id = insertYXmlText.getAttribute('blockId'); - - if (!id) { - Log.error('generateInsertBlockActions', 'id is not exist'); - return []; - } - - if (!type || type === 'text' || Object.values(EditorNodeType).indexOf(type) === -1) { - Log.error('generateInsertBlockActions', 'type is error: ' + type); - return []; - } - - const actions: ReturnType[] = [ - ...textInsertActions, - { - action: BlockActionTypePB.Insert, - payload: { - block: { - id, - data: JSON.stringify(data), - ty: type, - parent_id: parentId, - children_id: childrenId, - external_id: externalId, - external_type: externalId ? 'text' : undefined, - }, - prev_id: prevId, - parent_id: parentId, - }, - }, - ]; - - childrenInserts.forEach((insert) => { - if (insert instanceof Y.XmlText) { - actions.push(...generateInsertBlockActions(insert)); - } - }); - - return actions; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/utils/convert.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/utils/convert.ts deleted file mode 100644 index b4da4b3ca7..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/utils/convert.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { nanoid } from 'nanoid'; -import { EditorData, EditorInlineNodeType, Mention } from '$app/application/document/document.types'; -import { Element, Text } from 'slate'; -import { Op } from 'quill-delta'; - -export function generateId() { - return nanoid(10); -} - -export function transformToInlineElement(op: Op): Element[] { - const attributes = op.attributes; - - if (!attributes) return []; - const { formula, mention, ...attrs } = attributes; - - if (formula) { - const texts = (op.insert as string).split(''); - - return texts.map((text) => { - return { - type: EditorInlineNodeType.Formula, - data: formula, - children: [ - { - text, - ...attrs, - }, - ], - }; - }); - } - - if (mention) { - const texts = (op.insert as string).split(''); - - return texts.map((text) => { - return { - type: EditorInlineNodeType.Mention, - children: [ - { - text, - ...attrs, - }, - ], - data: { - ...(mention as Mention), - }, - }; - }); - } - - return []; -} - -export function getInlinesWithDelta(delta?: Op[]): (Text | Element)[] { - const newDelta: (Text | Element)[] = []; - - if (!delta || !delta.length) - return [ - { - text: '', - }, - ]; - - delta.forEach((op) => { - const matchInlines = transformToInlineElement(op); - - if (matchInlines.length > 0) { - newDelta.push(...matchInlines); - return; - } - - if (op.attributes) { - if ('font_color' in op.attributes && op.attributes['font_color'] === '') { - delete op.attributes['font_color']; - } - - if ('bg_color' in op.attributes && op.attributes['bg_color'] === '') { - delete op.attributes['bg_color']; - } - - if ('code' in op.attributes && !op.attributes['code']) { - delete op.attributes['code']; - } - } - - newDelta.push({ - text: op.insert as string, - ...op.attributes, - }); - }); - - return newDelta; -} - -export function convertToSlateValue(data: EditorData, includeRoot: boolean): Element[] { - const traverse = (id: string, isRoot = false) => { - const node = data.nodeMap[id]; - const delta = data.deltaMap[id]; - - const slateNode: Element = { - type: node.type, - data: node.data, - children: [], - blockId: id, - }; - - const textNode: Element | null = - !isRoot && node.externalId - ? { - type: 'text', - children: [], - textId: node.externalId, - } - : null; - - const inlineNodes = getInlinesWithDelta(delta); - - textNode?.children.push(...inlineNodes); - - const children = data.childrenMap[id]; - - slateNode.children = children.map((childId) => traverse(childId)); - if (textNode) { - slateNode.children.unshift(textNode); - } - - return slateNode; - }; - - const rootId = data.rootId; - - const root = traverse(rootId, true); - - const nodes = root.children as Element[]; - - if (includeRoot) { - nodes.unshift({ - ...root, - children: [ - { - type: 'text', - children: [ - { - text: '', - }, - ], - }, - ], - }); - } - - return nodes; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/utils/delta.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/utils/delta.ts deleted file mode 100644 index 630b6fbdf5..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/utils/delta.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { YDelta, YOp } from '$app/components/editor/provider/types/y_event'; -import { Op } from 'quill-delta'; -import * as Y from 'yjs'; -import { inlineNodeTypes } from '$app/application/document/document.types'; -import { DocEventPB } from '@/services/backend'; - -export function YDelta2Delta(yDelta: YDelta): Op[] { - const ops: Op[] = []; - - yDelta.forEach((op) => { - if (op.insert instanceof Y.XmlText) { - const type = op.insert.getAttribute('type'); - - if (inlineNodeTypes.includes(type)) { - ops.push(...YInlineOp2Op(op)); - return; - } - } - - ops.push(op as Op); - }); - return ops; -} - -export function YInlineOp2Op(yOp: YOp): Op[] { - if (!(yOp.insert instanceof Y.XmlText)) { - return [ - { - insert: yOp.insert as string, - attributes: yOp.attributes, - }, - ]; - } - - const type = yOp.insert.getAttribute('type'); - const data = yOp.insert.getAttribute('data'); - - const delta = yOp.insert.toDelta() as Op[]; - - return delta.map((op) => ({ - insert: op.insert, - - attributes: { - [type]: data, - ...op.attributes, - }, - })); -} - -export function DocEvent2YDelta(events: DocEventPB): YDelta { - if (!events.is_remote) return []; - - return []; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/utils/relation.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/utils/relation.ts deleted file mode 100644 index 72b2e126df..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/provider/utils/relation.ts +++ /dev/null @@ -1,24 +0,0 @@ -import * as Y from 'yjs'; - -export function getInsertTarget(root: Y.XmlText, path: (string | number)[]): Y.XmlText { - const delta = root.toDelta(); - const index = path[0]; - - const current = delta[index]; - - if (current && current.insert instanceof Y.XmlText) { - if (path.length === 1) { - return current.insert; - } - - return getInsertTarget(current.insert, path.slice(1)); - } - - return root; -} - -export function getYTarget(doc: Y.Doc, path: (string | number)[]) { - const sharedType = doc.get('sharedType', Y.XmlText) as Y.XmlText; - - return getInsertTarget(sharedType, path); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/stores/block.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/stores/block.ts deleted file mode 100644 index 00992964fb..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/stores/block.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { createContext, useCallback, useContext, useMemo } from 'react'; -import { proxy, useSnapshot } from 'valtio'; -import { EditorNodeType } from '$app/application/document/document.types'; - -export interface EditorBlockState { - [EditorNodeType.ImageBlock]: { - popoverOpen: boolean; - blockId?: string; - }; - [EditorNodeType.EquationBlock]: { - popoverOpen: boolean; - blockId?: string; - }; -} - -const initialState = { - [EditorNodeType.ImageBlock]: { - popoverOpen: false, - blockId: undefined, - }, - [EditorNodeType.EquationBlock]: { - popoverOpen: false, - blockId: undefined, - }, -}; - -export const EditorBlockStateContext = createContext(initialState); - -export const EditorBlockStateProvider = EditorBlockStateContext.Provider; - -export function useEditorInitialBlockState() { - const state = useMemo(() => { - return proxy({ - ...initialState, - }); - }, []); - - return state; -} - -export function useEditorBlockState(key: EditorNodeType.ImageBlock | EditorNodeType.EquationBlock) { - const context = useContext(EditorBlockStateContext); - - return useSnapshot(context[key]); -} - -export function useEditorBlockDispatch() { - const context = useContext(EditorBlockStateContext); - - const openPopover = useCallback( - (key: EditorNodeType.ImageBlock | EditorNodeType.EquationBlock, blockId: string) => { - context[key].popoverOpen = true; - context[key].blockId = blockId; - }, - [context] - ); - - const closePopover = useCallback( - (key: EditorNodeType.ImageBlock | EditorNodeType.EquationBlock) => { - context[key].popoverOpen = false; - context[key].blockId = undefined; - }, - [context] - ); - - return { - openPopover, - closePopover, - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/stores/decorate.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/stores/decorate.ts deleted file mode 100644 index 078296aade..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/stores/decorate.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { createContext, useCallback, useContext, useMemo } from 'react'; -import { BaseRange, Editor, NodeEntry, Range } from 'slate'; -import { proxySet } from 'valtio/utils'; -import { useSnapshot } from 'valtio'; -import { ReactEditor } from 'slate-react'; - -export const DecorateStateContext = createContext< - Set<{ - range: BaseRange; - class_name: string; - type?: 'link'; - }> ->(new Set()); -export const DecorateStateProvider = DecorateStateContext.Provider; - -export function useInitialDecorateState(editor: ReactEditor) { - const decorateState = useMemo( - () => - proxySet<{ - range: BaseRange; - class_name: string; - }>([]), - [] - ); - - const ranges = useSnapshot(decorateState); - - const decorate = useCallback( - ([, path]: NodeEntry): BaseRange[] => { - const highlightRanges: (Range & { - class_name: string; - })[] = []; - - ranges.forEach((state) => { - const intersection = Range.intersection(state.range, Editor.range(editor, path)); - - if (intersection) { - highlightRanges.push({ - ...intersection, - class_name: state.class_name, - }); - } - }); - - return highlightRanges; - }, - [editor, ranges] - ); - - return { - decorate, - decorateState, - }; -} - -export function useDecorateState(type?: 'link') { - const context = useContext(DecorateStateContext); - - const state = useSnapshot(context); - - return useMemo(() => { - return Array.from(state).find((s) => !type || s.type === type); - }, [state, type]); -} - -export function useDecorateDispatch() { - const context = useContext(DecorateStateContext); - - const getStaticState = useCallback(() => { - return Array.from(context)[0]; - }, [context]); - - const add = useCallback( - (state: { range: BaseRange; class_name: string; type?: 'link' }) => { - context.add(state); - }, - [context] - ); - - const clear = useCallback(() => { - context.clear(); - }, [context]); - - return { - add, - clear, - getStaticState, - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/stores/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/stores/index.ts deleted file mode 100644 index 22f0bb81be..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/stores/index.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { ReactEditor } from 'slate-react'; -import { useInitialDecorateState } from '$app/components/editor/stores/decorate'; -import { useInitialSelectedBlocks } from '$app/components/editor/stores/selected'; -import { useInitialSlashState } from '$app/components/editor/stores/slash'; -import { useInitialEditorInlineBlockState } from '$app/components/editor/stores/inline_node'; -import { useEditorInitialBlockState } from '$app/components/editor/stores/block'; - -export * from './decorate'; -export * from './selected'; -export * from './slash'; -export * from './inline_node'; - -export function useInitialEditorState(editor: ReactEditor) { - const { decorate, decorateState } = useInitialDecorateState(editor); - const selectedBlocks = useInitialSelectedBlocks(editor); - const slashState = useInitialSlashState(); - const inlineBlockState = useInitialEditorInlineBlockState(); - const blockState = useEditorInitialBlockState(); - - return { - selectedBlocks, - decorate, - decorateState, - slashState, - inlineBlockState, - blockState, - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/stores/inline_node.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/stores/inline_node.ts deleted file mode 100644 index 6607a546d8..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/stores/inline_node.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { createContext, useCallback, useContext, useMemo } from 'react'; -import { BaseRange, Path } from 'slate'; -import { proxy, useSnapshot } from 'valtio'; - -export interface EditorInlineBlockState { - formula: { - popoverOpen: boolean; - range?: BaseRange; - }; -} -const initialState = { - formula: { - popoverOpen: false, - range: undefined, - }, -}; - -export const EditorInlineBlockStateContext = createContext(initialState); - -export const EditorInlineBlockStateProvider = EditorInlineBlockStateContext.Provider; - -export function useInitialEditorInlineBlockState() { - const state = useMemo(() => { - return proxy({ - ...initialState, - }); - }, []); - - return state; -} - -export function useEditorInlineBlockState(key: 'formula') { - const context = useContext(EditorInlineBlockStateContext); - - const state = useSnapshot(context[key]); - - const openPopover = useCallback(() => { - context[key].popoverOpen = true; - }, [context, key]); - - const closePopover = useCallback(() => { - context[key].popoverOpen = false; - }, [context, key]); - - const setRange = useCallback( - (at: BaseRange | Path) => { - const range = Path.isPath(at) ? { anchor: at, focus: at } : at; - - context[key].range = range as BaseRange; - }, - [context, key] - ); - - return { - ...state, - openPopover, - closePopover, - setRange, - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/stores/selected.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/stores/selected.ts deleted file mode 100644 index 803f474723..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/stores/selected.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { createContext, useEffect, useMemo, useState } from 'react'; -import { proxySet, subscribeKey } from 'valtio/utils'; -import { ReactEditor } from 'slate-react'; -import { Element } from 'slate'; - -export function useInitialSelectedBlocks(editor: ReactEditor) { - const selectedBlocks = useMemo(() => proxySet([]), []); - const [selectedLength, setSelectedLength] = useState(0); - - subscribeKey(selectedBlocks, 'size', (v) => setSelectedLength(v)); - - useEffect(() => { - const { onChange } = editor; - - const onKeydown = (e: KeyboardEvent) => { - if (!ReactEditor.isFocused(editor) && selectedLength > 0) { - e.preventDefault(); - e.stopPropagation(); - const selectedBlockId = selectedBlocks.values().next().value; - const [selectedBlock] = editor.nodes({ - at: [], - match: (n) => Element.isElement(n) && n.blockId === selectedBlockId, - }); - const [, path] = selectedBlock; - - editor.select(path); - ReactEditor.focus(editor); - } - }; - - if (selectedLength > 0) { - editor.onChange = (...args) => { - const isSelectionChange = editor.operations.every((arg) => arg.type === 'set_selection'); - - if (isSelectionChange) { - selectedBlocks.clear(); - } - - onChange(...args); - }; - - document.addEventListener('keydown', onKeydown); - } else { - editor.onChange = onChange; - document.removeEventListener('keydown', onKeydown); - } - - return () => { - editor.onChange = onChange; - document.removeEventListener('keydown', onKeydown); - }; - }, [editor, selectedBlocks, selectedLength]); - - return selectedBlocks; -} - -export const EditorSelectedBlockContext = createContext>(new Set()); -export const EditorSelectedBlockProvider = EditorSelectedBlockContext.Provider; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/editor/stores/slash.ts b/frontend/appflowy_tauri/src/appflowy_app/components/editor/stores/slash.ts deleted file mode 100644 index 13e9447d0c..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/editor/stores/slash.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { createContext, useCallback, useContext, useMemo } from 'react'; -import { proxyMap } from 'valtio/utils'; -import { useSnapshot } from 'valtio'; - -export const SlashStateContext = createContext>(new Map()); -export const SlashStateProvider = SlashStateContext.Provider; - -export function useInitialSlashState() { - const state = useMemo(() => proxyMap([['open', false]]), []); - - return state; -} - -export function useSlashState() { - const context = useContext(SlashStateContext); - const state = useSnapshot(context); - const open = state.get('open'); - - const setOpen = useCallback( - (open: boolean) => { - context.set('open', open); - }, - [context] - ); - - return { - open, - setOpen, - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/error/Error.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/error/Error.hooks.ts deleted file mode 100644 index ceaa5a51a0..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/error/Error.hooks.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { useAppDispatch, useAppSelector } from '$app/stores/store'; -import { errorActions } from '$app_reducers/error/slice'; -import { useCallback, useEffect, useState } from 'react'; - -export const useError = (e: Error) => { - const dispatch = useAppDispatch(); - const error = useAppSelector((state) => state.error); - const [errorMessage, setErrorMessage] = useState(''); - const [displayError, setDisplayError] = useState(false); - - useEffect(() => { - setDisplayError(error.display); - setErrorMessage(error.message); - }, [error]); - - const showError = useCallback( - (msg: string) => { - dispatch(errorActions.showError(msg)); - }, - [dispatch] - ); - - useEffect(() => { - if (e) { - showError(e.message); - } - }, [e, showError]); - - const hideError = () => { - dispatch(errorActions.hideError()); - }; - - return { - showError, - hideError, - errorMessage, - displayError, - }; -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/error/ErrorHandlerPage.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/error/ErrorHandlerPage.tsx deleted file mode 100644 index 1bb15f2ca3..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/error/ErrorHandlerPage.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { useError } from './Error.hooks'; -import { ErrorModal } from './ErrorModal'; - -export const ErrorHandlerPage = ({ error }: { error: Error }) => { - const { hideError, errorMessage, displayError } = useError(error); - - return displayError ? : <>; -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/error/ErrorModal.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/error/ErrorModal.tsx deleted file mode 100644 index 6da2ee96d0..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/error/ErrorModal.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { ReactComponent as InformationSvg } from '$app/assets/information.svg'; -import { ReactComponent as CloseSvg } from '$app/assets/close.svg'; - -export const ErrorModal = ({ message, onClose }: { message: string; onClose: () => void }) => { - return ( -
-
- -
- -
-

Oops.. something went wrong

-

{message}

-
-
- ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/index.ts deleted file mode 100644 index cb0ff5c3b5..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/FooterPanel.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/FooterPanel.tsx deleted file mode 100644 index 4f468f5461..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/FooterPanel.tsx +++ /dev/null @@ -1,12 +0,0 @@ -export const FooterPanel = () => { - return ( -
-
- © 2024 AppFlowy. GitHub -
- {/*
*/} - {/* */} - {/*
*/} -
- ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/Layout.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/layout/Layout.hooks.ts deleted file mode 100644 index 807c1e6811..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/Layout.hooks.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { useCallback } from 'react'; -import { createHotkey, HOT_KEY_NAME } from '$app/utils/hotkeys'; -import { useAppDispatch, useAppSelector } from '$app/stores/store'; -import { currentUserActions, ThemeMode } from '$app_reducers/current-user/slice'; -import { UserService } from '$app/application/user/user.service'; -import { sidebarActions } from '$app_reducers/sidebar/slice'; - -export function useShortcuts() { - const dispatch = useAppDispatch(); - const userSettingState = useAppSelector((state) => state.currentUser.userSetting); - const { isDark } = userSettingState; - - const switchThemeMode = useCallback(() => { - const newSetting = { - themeMode: isDark ? ThemeMode.Light : ThemeMode.Dark, - isDark: !isDark, - }; - - dispatch(currentUserActions.setUserSetting(newSetting)); - void UserService.setAppearanceSetting({ - theme_mode: newSetting.themeMode, - }); - }, [dispatch, isDark]); - - const toggleSidebar = useCallback(() => { - dispatch(sidebarActions.toggleCollapse()); - }, [dispatch]); - - return useCallback( - (e: KeyboardEvent) => { - switch (true) { - /** - * Toggle theme: Mod+L - * Switch between light and dark theme - */ - case createHotkey(HOT_KEY_NAME.TOGGLE_THEME)(e): - switchThemeMode(); - break; - /** - * Toggle sidebar: Mod+. (period) - * Prevent the default behavior of the browser (Exit full screen) - * Collapse or expand the sidebar - */ - case createHotkey(HOT_KEY_NAME.TOGGLE_SIDEBAR)(e): - e.preventDefault(); - toggleSidebar(); - break; - default: - break; - } - }, - [toggleSidebar, switchThemeMode] - ); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/Layout.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/Layout.tsx deleted file mode 100644 index 509aa388cf..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/Layout.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import React, { ReactNode, useEffect, useMemo } from 'react'; -import SideBar from '$app/components/layout/side_bar/SideBar'; -import TopBar from '$app/components/layout/top_bar/TopBar'; -import { useAppSelector } from '$app/stores/store'; -import './layout.scss'; -import { AFScroller } from '../_shared/scroller'; -import { useNavigate } from 'react-router-dom'; -import { pageTypeMap } from '$app_reducers/pages/slice'; -import { useShortcuts } from '$app/components/layout/Layout.hooks'; - -function Layout({ children }: { children: ReactNode }) { - const { isCollapsed, width } = useAppSelector((state) => state.sidebar); - const currentUser = useAppSelector((state) => state.currentUser); - const navigate = useNavigate(); - const { id: latestOpenViewId, layout } = useMemo( - () => - currentUser?.workspaceSetting?.latestView || { - id: undefined, - layout: undefined, - }, - [currentUser?.workspaceSetting?.latestView] - ); - - const onKeyDown = useShortcuts(); - - useEffect(() => { - window.addEventListener('keydown', onKeyDown); - return () => { - window.removeEventListener('keydown', onKeyDown); - }; - }, [onKeyDown]); - - useEffect(() => { - if (latestOpenViewId) { - const pageType = pageTypeMap[layout]; - - navigate(`/page/${pageType}/${latestOpenViewId}`); - } - }, [latestOpenViewId, navigate, layout]); - return ( - <> -
- -
- - - {children} - -
-
- - ); -} - -export default Layout; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/bread_crumb/BreadCrumb.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/bread_crumb/BreadCrumb.tsx deleted file mode 100644 index ec9e990cdb..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/bread_crumb/BreadCrumb.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React, { useCallback } from 'react'; -import { useLoadExpandedPages } from '$app/components/layout/bread_crumb/Breadcrumb.hooks'; -import Breadcrumbs from '@mui/material/Breadcrumbs'; -import Link from '@mui/material/Link'; -import Typography from '@mui/material/Typography'; -import { Page } from '$app_reducers/pages/slice'; -import { useTranslation } from 'react-i18next'; -import { getPageIcon } from '$app/hooks/page.hooks'; -import { useAppDispatch } from '$app/stores/store'; -import { openPage } from '$app_reducers/pages/async_actions'; - -function Breadcrumb() { - const { t } = useTranslation(); - const { isTrash, pagePath, currentPage } = useLoadExpandedPages(); - const dispatch = useAppDispatch(); - - const navigateToPage = useCallback( - (page: Page) => { - void dispatch(openPage(page.id)); - }, - [dispatch] - ); - - if (!currentPage) { - if (isTrash) { - return {t('trash.text')}; - } - - return null; - } - - return ( - - {pagePath?.map((page: Page, index) => { - if (index === pagePath.length - 1) { - return ( -
-
{getPageIcon(page)}
- {page.name.trim() || t('menuAppHeader.defaultNewPageName')} -
- ); - } - - return ( - { - navigateToPage(page); - }} - > -
{getPageIcon(page)}
- - {page.name.trim() || t('menuAppHeader.defaultNewPageName')} - - ); - })} -
- ); -} - -export default Breadcrumb; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/bread_crumb/Breadcrumb.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/layout/bread_crumb/Breadcrumb.hooks.ts deleted file mode 100644 index f2bec915d9..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/bread_crumb/Breadcrumb.hooks.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { useAppSelector } from '$app/stores/store'; -import { useMemo } from 'react'; -import { useParams, useLocation } from 'react-router-dom'; -import { Page } from '$app_reducers/pages/slice'; - -export function useLoadExpandedPages() { - const params = useParams(); - const location = useLocation(); - const isTrash = useMemo(() => location.pathname.includes('trash'), [location.pathname]); - const currentPageId = params.id; - const currentPage = useAppSelector((state) => (currentPageId ? state.pages.pageMap[currentPageId] : undefined)); - - const pagePath = useAppSelector((state) => { - const result: Page[] = []; - - if (!currentPage) return result; - - const findParent = (page: Page) => { - if (!page.parentId) return; - const parent = state.pages.pageMap[page.parentId]; - - if (parent) { - result.unshift(parent); - findParent(parent); - } - }; - - findParent(currentPage); - result.push(currentPage); - return result; - }); - - return { - pagePath, - currentPage, - isTrash, - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/collapse_menu_button/CollapseMenuButton.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/collapse_menu_button/CollapseMenuButton.tsx deleted file mode 100644 index 87662a99bb..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/collapse_menu_button/CollapseMenuButton.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import React, { useCallback, useMemo } from 'react'; -import { IconButton, Tooltip } from '@mui/material'; - -import { useAppDispatch, useAppSelector } from '$app/stores/store'; -import { sidebarActions } from '$app_reducers/sidebar/slice'; -import { ReactComponent as ShowMenuIcon } from '$app/assets/show-menu.svg'; -import { useTranslation } from 'react-i18next'; -import { createHotKeyLabel, HOT_KEY_NAME } from '$app/utils/hotkeys'; - -function CollapseMenuButton() { - const isCollapsed = useAppSelector((state) => state.sidebar.isCollapsed); - const dispatch = useAppDispatch(); - const handleClick = useCallback(() => { - dispatch(sidebarActions.toggleCollapse()); - }, [dispatch]); - - const { t } = useTranslation(); - - const title = useMemo(() => { - return ( -
-
{isCollapsed ? t('sideBar.openSidebar') : t('sideBar.closeSidebar')}
-
{createHotKeyLabel(HOT_KEY_NAME.TOGGLE_SIDEBAR)}
-
- ); - }, [isCollapsed, t]); - - return ( - - - - - - ); -} - -export default CollapseMenuButton; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/layout.scss b/frontend/appflowy_tauri/src/appflowy_app/components/layout/layout.scss deleted file mode 100644 index 43f4f55892..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/layout.scss +++ /dev/null @@ -1,81 +0,0 @@ - - -.sketch-picker { - background-color: var(--bg-body) !important; - border-color: transparent !important; - box-shadow: none !important; -} -.sketch-picker .flexbox-fix { - border-color: var(--line-divider) !important; -} -.sketch-picker [id^='rc-editable-input'] { - background-color: var(--bg-body) !important; - border-color: var(--line-divider) !important; - color: var(--text-title) !important; - box-shadow: var(--line-border) 0px 0px 0px 1px inset !important; -} - -.appflowy-date-picker-calendar { - width: 100%; - -} - -.grid-sticky-header::-webkit-scrollbar { - width: 0; - height: 0; -} -.grid-scroll-container::-webkit-scrollbar { - width: 0; - height: 0; -} - - -.appflowy-scroll-container { - &::-webkit-scrollbar { - width: 0; - } -} - -.appflowy-scrollbar-thumb-horizontal, .appflowy-scrollbar-thumb-vertical { - background-color: var(--scrollbar-thumb); - border-radius: 4px; - opacity: 60%; -} - -.workspaces { - ::-webkit-scrollbar { - width: 0px; - } -} - - - -.MuiPopover-root, .MuiPaper-root { - ::-webkit-scrollbar { - width: 0; - height: 0; - } -} - -.view-icon { - &:hover { - background-color: rgba(156, 156, 156, 0.20); - } -} - -.theme-mode-item { - @apply relative flex h-[72px] w-[88px] cursor-pointer items-end justify-end rounded border hover:shadow; - background: linear-gradient(150.74deg, rgba(231, 231, 231, 0) 17.95%, #C5C5C5 95.51%); -} - -[data-dark-mode="true"] { - .theme-mode-item { - background: linear-gradient(150.74deg, rgba(128, 125, 125, 0) 17.95%, #4d4d4d 95.51%); - } -} - -.document-header { - .view-banner { - @apply items-center; - } -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/nested_page/AddButton.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/nested_page/AddButton.tsx deleted file mode 100644 index 1387f16f4d..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/nested_page/AddButton.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import React, { useCallback, useMemo } from 'react'; -import { ReactComponent as AddSvg } from '$app/assets/add.svg'; -import { useTranslation } from 'react-i18next'; -import { ReactComponent as DocumentSvg } from '$app/assets/document.svg'; -import { ReactComponent as GridSvg } from '$app/assets/grid.svg'; -import { ViewLayoutPB } from '@/services/backend'; -import OperationMenu from '$app/components/layout/nested_page/OperationMenu'; - -function AddButton({ - isHovering, - setHovering, - onAddPage, -}: { - isHovering: boolean; - setHovering: (hovering: boolean) => void; - onAddPage: (layout: ViewLayoutPB) => void; -}) { - const { t } = useTranslation(); - - const onConfirm = useCallback( - (key: string) => { - switch (key) { - case 'document': - onAddPage(ViewLayoutPB.Document); - break; - case 'grid': - onAddPage(ViewLayoutPB.Grid); - break; - default: - break; - } - }, - [onAddPage] - ); - - const options = useMemo( - () => [ - { - key: 'document', - title: t('document.menuName'), - icon: , - }, - { - key: 'grid', - title: t('grid.menuName'), - icon: , - }, - ], - [t] - ); - - return ( - - - - ); -} - -export default AddButton; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/nested_page/DeleteDialog.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/nested_page/DeleteDialog.tsx deleted file mode 100644 index 4af8a2f2f1..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/nested_page/DeleteDialog.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { ViewLayoutPB } from '@/services/backend'; -import DeleteConfirmDialog from '$app/components/_shared/confirm_dialog/DeleteConfirmDialog'; - -function DeleteDialog({ - layout, - open, - onClose, - onOk, -}: { - layout: ViewLayoutPB; - open: boolean; - onClose: () => void; - onOk: () => Promise; -}) { - const { t } = useTranslation(); - - const pageType = { - [ViewLayoutPB.Document]: t('document.menuName'), - [ViewLayoutPB.Grid]: t('grid.menuName'), - [ViewLayoutPB.Board]: t('board.menuName'), - [ViewLayoutPB.Calendar]: t('calendar.menuName'), - }[layout]; - - return ( - - ); -} - -export default DeleteDialog; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/nested_page/MoreButton.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/nested_page/MoreButton.tsx deleted file mode 100644 index 94a86655ac..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/nested_page/MoreButton.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import React, { useCallback, useMemo, useState } from 'react'; -import { useTranslation } from 'react-i18next'; - -import { ReactComponent as DetailsSvg } from '$app/assets/details.svg'; -import { ReactComponent as EditSvg } from '$app/assets/edit.svg'; -import { ReactComponent as CopySvg } from '$app/assets/copy.svg'; -import { ReactComponent as TrashSvg } from '$app/assets/delete.svg'; - -import RenameDialog from '../../_shared/confirm_dialog/RenameDialog'; -import { Page } from '$app_reducers/pages/slice'; -import DeleteDialog from '$app/components/layout/nested_page/DeleteDialog'; -import OperationMenu from '$app/components/layout/nested_page/OperationMenu'; -import { getModifier } from '$app/utils/hotkeys'; -import isHotkey from 'is-hotkey'; - -function MoreButton({ - onDelete, - onDuplicate, - onRename, - page, - isHovering, - setHovering, -}: { - isHovering: boolean; - setHovering: (hovering: boolean) => void; - onDelete: () => Promise; - onDuplicate: () => Promise; - onRename: (newName: string) => Promise; - page: Page; -}) { - const [renameDialogOpen, setRenameDialogOpen] = useState(false); - const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); - - const { t } = useTranslation(); - - const onConfirm = useCallback( - (key: string) => { - switch (key) { - case 'rename': - setRenameDialogOpen(true); - break; - case 'delete': - setDeleteDialogOpen(true); - break; - case 'duplicate': - void onDuplicate(); - - break; - default: - break; - } - }, - [onDuplicate] - ); - - const options = useMemo( - () => [ - { - title: t('button.rename'), - icon: , - key: 'rename', - }, - { - key: 'delete', - title: t('button.delete'), - icon: , - caption: 'Del', - }, - { - key: 'duplicate', - title: t('button.duplicate'), - icon: , - caption: `${getModifier()}+D`, - }, - ], - [t] - ); - - const onKeyDown = useCallback( - (e: KeyboardEvent) => { - if (isHotkey('del', e) || isHotkey('backspace', e)) { - e.preventDefault(); - e.stopPropagation(); - onConfirm('delete'); - return; - } - - if (isHotkey('mod+d', e)) { - e.stopPropagation(); - onConfirm('duplicate'); - return; - } - }, - [onConfirm] - ); - - return ( - <> - - - - - { - setDeleteDialogOpen(false); - }} - onOk={onDelete} - /> - {renameDialogOpen && ( - setRenameDialogOpen(false)} - onOk={onRename} - /> - )} - - ); -} - -export default MoreButton; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/nested_page/NestedPage.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/layout/nested_page/NestedPage.hooks.ts deleted file mode 100644 index d43499e801..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/nested_page/NestedPage.hooks.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { useCallback, useEffect } from 'react'; -import { pagesActions, parserViewPBToPage } from '$app_reducers/pages/slice'; -import { useAppDispatch, useAppSelector } from '$app/stores/store'; -import { FolderNotification, ViewLayoutPB } from '@/services/backend'; -import { useParams } from 'react-router-dom'; -import { openPage, updatePageName } from '$app_reducers/pages/async_actions'; -import { createPage, deletePage, duplicatePage, getChildPages } from '$app/application/folder/page.service'; -import { subscribeNotifications } from '$app/application/notification'; - -export function useLoadChildPages(pageId: string) { - const dispatch = useAppDispatch(); - const childPages = useAppSelector((state) => state.pages.relationMap[pageId]); - const collapsed = useAppSelector((state) => !state.pages.expandedIdMap[pageId]); - const toggleCollapsed = useCallback(() => { - if (collapsed) { - dispatch(pagesActions.expandPage(pageId)); - } else { - dispatch(pagesActions.collapsePage(pageId)); - } - }, [dispatch, pageId, collapsed]); - - const loadPageChildren = useCallback( - async (pageId: string) => { - const childPages = await getChildPages(pageId); - - dispatch( - pagesActions.addChildPages({ - id: pageId, - childPages, - }) - ); - }, - [dispatch] - ); - - useEffect(() => { - void loadPageChildren(pageId); - }, [loadPageChildren, pageId]); - - useEffect(() => { - const unsubscribePromise = subscribeNotifications( - { - [FolderNotification.DidUpdateView]: async (payload) => { - const childViews = payload.child_views; - - if (childViews.length === 0) { - return; - } - - dispatch( - pagesActions.addChildPages({ - id: pageId, - childPages: childViews.map(parserViewPBToPage), - }) - ); - }, - [FolderNotification.DidUpdateChildViews]: async (payload) => { - if (payload.delete_child_views.length === 0 && payload.create_child_views.length === 0) { - return; - } - - void loadPageChildren(pageId); - }, - }, - { - id: pageId, - } - ); - - return () => void unsubscribePromise.then((unsubscribe) => unsubscribe()); - }, [pageId, loadPageChildren, dispatch]); - - return { - toggleCollapsed, - collapsed, - childPages, - }; -} - -export function usePageActions(pageId: string) { - const page = useAppSelector((state) => state.pages.pageMap[pageId]); - const dispatch = useAppDispatch(); - const params = useParams(); - const currentPageId = params.id; - - const onPageClick = useCallback(() => { - void dispatch(openPage(pageId)); - }, [dispatch, pageId]); - - const onAddPage = useCallback( - async (layout: ViewLayoutPB) => { - const newViewId = await createPage({ - layout, - name: '', - parent_view_id: pageId, - }); - - dispatch( - pagesActions.addPage({ - page: { - id: newViewId, - parentId: pageId, - layout, - name: '', - }, - isLast: true, - }) - ); - - dispatch(pagesActions.expandPage(pageId)); - await dispatch(openPage(newViewId)); - }, - [dispatch, pageId] - ); - - const onDeletePage = useCallback(async () => { - if (currentPageId === pageId) { - dispatch(pagesActions.setTrashSnackbar(true)); - } - - await deletePage(pageId); - dispatch(pagesActions.deletePages([pageId])); - }, [dispatch, pageId, currentPageId]); - - const onDuplicatePage = useCallback(async () => { - await duplicatePage(page); - }, [page]); - - const onRenamePage = useCallback( - async (name: string) => { - await dispatch(updatePageName({ id: pageId, name })); - }, - [dispatch, pageId] - ); - - return { - onAddPage, - onPageClick, - onRenamePage, - onDeletePage, - onDuplicatePage, - }; -} - -export function useSelectedPage(pageId: string) { - return useParams().id === pageId; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/nested_page/NestedPage.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/nested_page/NestedPage.tsx deleted file mode 100644 index e423f05517..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/nested_page/NestedPage.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import React, { useCallback, useMemo } from 'react'; -import Collapse from '@mui/material/Collapse'; -import { TransitionGroup } from 'react-transition-group'; -import NestedPageTitle from '$app/components/layout/nested_page/NestedPageTitle'; -import { useLoadChildPages, usePageActions } from '$app/components/layout/nested_page/NestedPage.hooks'; -import { useDrag } from 'src/appflowy_app/components/_shared/drag_block'; -import { useAppDispatch, useAppSelector } from '$app/stores/store'; -import { movePageThunk } from '$app_reducers/pages/async_actions'; -import { ViewLayoutPB } from '@/services/backend'; - -function NestedPage({ pageId }: { pageId: string }) { - const { toggleCollapsed, collapsed, childPages } = useLoadChildPages(pageId); - const { onAddPage, onPageClick, onDeletePage, onDuplicatePage, onRenamePage } = usePageActions(pageId); - const dispatch = useAppDispatch(); - const { page, parentLayout } = useAppSelector((state) => { - const page = state.pages.pageMap[pageId]; - const parent = state.pages.pageMap[page?.parentId || '']; - - return { - page, - parentLayout: parent?.layout, - }; - }); - - const disableChildren = useAppSelector((state) => { - if (!page) return true; - const layout = state.pages.pageMap[page.parentId]?.layout; - - return !(layout === undefined || layout === ViewLayoutPB.Document); - }); - const children = useMemo(() => { - if (disableChildren) { - return []; - } - - return collapsed ? [] : childPages; - }, [collapsed, childPages, disableChildren]); - - const onDragFinished = useCallback( - (result: { dragId: string; position: 'before' | 'after' | 'inside' }) => { - const { dragId, position } = result; - - if (dragId === pageId) return; - if (position === 'inside' && page?.layout !== ViewLayoutPB.Document) return; - void dispatch( - movePageThunk({ - sourceId: dragId, - targetId: pageId, - insertType: position, - }) - ); - }, - [dispatch, page?.layout, pageId] - ); - - const { onDrop, dropPosition, onDragOver, onDragLeave, onDragStart, onDragEnd, isDraggingOver, isDragging } = useDrag({ - onEnd: onDragFinished, - dragId: pageId, - }); - - const className = useMemo(() => { - const defaultClassName = 'relative flex-1 select-none flex flex-col w-full'; - - if (isDragging) { - return `${defaultClassName} opacity-40`; - } - - if (isDraggingOver && dropPosition === 'inside' && page?.layout === ViewLayoutPB.Document) { - if (dropPosition === 'inside') { - return `${defaultClassName} bg-content-blue-100`; - } - } else { - return defaultClassName; - } - }, [dropPosition, isDragging, isDraggingOver, page?.layout]); - - // Only allow dragging if the parent layout is undefined or a document - const draggable = parentLayout === undefined || parentLayout === ViewLayoutPB.Document; - - return ( -
-
- { - onPageClick(); - }} - onAddPage={onAddPage} - onDuplicate={onDuplicatePage} - onDelete={onDeletePage} - onRename={onRenamePage} - collapsed={collapsed} - toggleCollapsed={toggleCollapsed} - pageId={pageId} - /> -
- - {children?.map((pageId) => ( - - - - ))} - -
-
- ); -} - -export default React.memo(NestedPage); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/nested_page/NestedPageTitle.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/nested_page/NestedPageTitle.tsx deleted file mode 100644 index 948aedcae2..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/nested_page/NestedPageTitle.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import React, { useMemo, useState } from 'react'; -import { useAppSelector } from '$app/stores/store'; -import AddButton from './AddButton'; -import MoreButton from './MoreButton'; -import { ViewLayoutPB } from '@/services/backend'; -import { useSelectedPage } from '$app/components/layout/nested_page/NestedPage.hooks'; -import { useTranslation } from 'react-i18next'; -import { ReactComponent as MoreIcon } from '$app/assets/more.svg'; -import { IconButton } from '@mui/material'; -import { Page } from '$app_reducers/pages/slice'; -import { getPageIcon } from '$app/hooks/page.hooks'; - -function NestedPageTitle({ - pageId, - collapsed, - toggleCollapsed, - onAddPage, - onClick, - onDelete, - onDuplicate, - onRename, -}: { - pageId: string; - collapsed: boolean; - toggleCollapsed: () => void; - onAddPage: (layout: ViewLayoutPB) => void; - onClick: () => void; - onDelete: () => Promise; - onDuplicate: () => Promise; - onRename: (newName: string) => Promise; -}) { - const { t } = useTranslation(); - const page = useAppSelector((state) => { - return state.pages.pageMap[pageId] as Page | undefined; - }); - const disableChildren = useAppSelector((state) => { - if (!page) return true; - const layout = state.pages.pageMap[page.parentId]?.layout; - - return !(layout === undefined || layout === ViewLayoutPB.Document); - }); - - const [isHovering, setIsHovering] = useState(false); - const isSelected = useSelectedPage(pageId); - - const pageIcon = useMemo(() => (page ? getPageIcon(page) : null), [page]); - - return ( -
setIsHovering(true)} - onMouseLeave={() => setIsHovering(false)} - > -
-
- {disableChildren ? ( -
- ) : ( - { - e.stopPropagation(); - toggleCollapsed(); - }} - style={{ - transform: collapsed ? 'rotate(0deg)' : 'rotate(90deg)', - }} - > - - - )} - - {pageIcon} - -
- {page?.name.trim() || t('menuAppHeader.defaultNewPageName')} -
-
-
e.stopPropagation()} className={'min:w-14 flex items-center justify-end px-2'}> - {page?.layout === ViewLayoutPB.Document && ( - - )} - {page && ( - - )} -
-
-
- ); -} - -export default NestedPageTitle; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/nested_page/OperationMenu.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/nested_page/OperationMenu.tsx deleted file mode 100644 index cef1d3307c..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/nested_page/OperationMenu.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import React, { ReactNode, useCallback, useMemo, useState } from 'react'; -import { IconButton } from '@mui/material'; -import Popover from '@mui/material/Popover'; -import KeyboardNavigation from '$app/components/_shared/keyboard_navigation/KeyboardNavigation'; -import Tooltip from '@mui/material/Tooltip'; - -function OperationMenu({ - options, - onConfirm, - isHovering, - setHovering, - children, - tooltip, - onKeyDown, -}: { - isHovering: boolean; - setHovering: (hovering: boolean) => void; - options: { - key: string; - title: string; - icon: React.ReactNode; - caption?: string; - }[]; - children: React.ReactNode; - onConfirm: (key: string) => void; - tooltip: string; - onKeyDown?: (e: KeyboardEvent) => void; -}) { - const [anchorEl, setAnchorEl] = useState(null); - const renderItem = useCallback((title: string, icon: ReactNode, caption?: string) => { - return ( -
- {icon} -
{title}
-
{caption || ''}
-
- ); - }, []); - - const handleClose = useCallback(() => { - setAnchorEl(null); - setHovering(false); - }, [setHovering]); - - const optionList = useMemo(() => { - return options.map((option) => { - return { - key: option.key, - content: renderItem(option.title, option.icon, option.caption), - }; - }); - }, [options, renderItem]); - - const open = Boolean(anchorEl); - - const handleConfirm = useCallback( - (key: string) => { - onConfirm(key); - handleClose(); - }, - [handleClose, onConfirm] - ); - - return ( - <> - - { - setAnchorEl(e.currentTarget); - }} - className={`${!isHovering ? 'invisible' : ''} text-icon-primary`} - > - {children} - - - - - - - - ); -} - -export default OperationMenu; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/share/Share.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/layout/share/Share.hooks.ts deleted file mode 100644 index b281706848..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/share/Share.hooks.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { useParams } from 'react-router-dom'; - -export function useShareConfig() { - const params = useParams(); - const id = params.id; - - const showShareButton = !!id; - - return { - showShareButton, - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/share/Share.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/share/Share.tsx deleted file mode 100644 index 1a10cb08e6..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/share/Share.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import { useTranslation } from 'react-i18next'; -import Button from '@mui/material/Button'; -import { useShareConfig } from '$app/components/layout/share/Share.hooks'; - -function ShareButton() { - const { showShareButton } = useShareConfig(); - const { t } = useTranslation(); - - if (!showShareButton) return null; - return ; -} - -export default ShareButton; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/side_bar/Resizer.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/side_bar/Resizer.tsx deleted file mode 100644 index 639d5283e0..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/side_bar/Resizer.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React, { useCallback, useRef } from 'react'; -import { useAppDispatch, useAppSelector } from '$app/stores/store'; -import { sidebarActions } from '$app_reducers/sidebar/slice'; - -const minSidebarWidth = 200; - -function Resizer() { - const dispatch = useAppDispatch(); - const width = useAppSelector((state) => state.sidebar.width); - const startX = useRef(0); - const onResize = useCallback( - (e: MouseEvent) => { - e.preventDefault(); - const diff = e.clientX - startX.current; - const newWidth = width + diff; - - if (newWidth < minSidebarWidth) { - return; - } - - dispatch(sidebarActions.changeWidth(newWidth)); - }, - [dispatch, width] - ); - - const onResizeEnd = useCallback(() => { - dispatch(sidebarActions.stopResizing()); - document.removeEventListener('mousemove', onResize); - document.removeEventListener('mouseup', onResizeEnd); - }, [onResize, dispatch]); - - const onResizeStart = useCallback( - (e: React.MouseEvent) => { - startX.current = e.clientX; - dispatch(sidebarActions.startResizing()); - document.addEventListener('mousemove', onResize); - document.addEventListener('mouseup', onResizeEnd); - }, - [onResize, onResizeEnd, dispatch] - ); - - return ( -
-
-
- ); -} - -export default React.memo(Resizer); diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/side_bar/SideBar.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/side_bar/SideBar.tsx deleted file mode 100644 index 5cdbfb125b..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/side_bar/SideBar.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import React, { useEffect, useRef } from 'react'; -import { useAppDispatch, useAppSelector } from '$app/stores/store'; -import { ReactComponent as AppflowyLogoDark } from '$app/assets/dark-logo.svg'; -import { ReactComponent as AppflowyLogoLight } from '$app/assets/light-logo.svg'; -import CollapseMenuButton from '$app/components/layout/collapse_menu_button/CollapseMenuButton'; -import Resizer from '$app/components/layout/side_bar/Resizer'; -import UserInfo from '$app/components/layout/side_bar/UserInfo'; -import WorkspaceManager from '$app/components/layout/workspace_manager/WorkspaceManager'; -import { ThemeMode } from '$app_reducers/current-user/slice'; -import { sidebarActions } from '$app_reducers/sidebar/slice'; - -function SideBar() { - const { isCollapsed, width, isResizing } = useAppSelector((state) => state.sidebar); - const dispatch = useAppDispatch(); - - const themeMode = useAppSelector((state) => state.currentUser?.userSetting?.themeMode); - const isDark = - themeMode === ThemeMode.Dark || - (themeMode === ThemeMode.System && window.matchMedia('(prefers-color-scheme: dark)').matches); - - const lastCollapsedRef = useRef(isCollapsed); - - useEffect(() => { - const handleResize = () => { - const width = window.innerWidth; - - if (width <= 800 && !isCollapsed) { - lastCollapsedRef.current = false; - dispatch(sidebarActions.setCollapse(true)); - } else if (width > 800 && !lastCollapsedRef.current) { - lastCollapsedRef.current = true; - dispatch(sidebarActions.setCollapse(false)); - } - }; - - window.addEventListener('resize', handleResize); - - return () => { - window.removeEventListener('resize', handleResize); - }; - }, [dispatch, isCollapsed]); - return ( - <> -
-
-
- {isDark ? ( - - ) : ( - - )} - -
-
- -
-
- -
-
-
- - - ); -} - -export default SideBar; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/side_bar/UserInfo.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/side_bar/UserInfo.tsx deleted file mode 100644 index 62763c670e..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/side_bar/UserInfo.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import React, { useState } from 'react'; -import { useAppSelector } from '$app/stores/store'; -import { IconButton } from '@mui/material'; -import { ReactComponent as SettingIcon } from '$app/assets/settings.svg'; -import Tooltip from '@mui/material/Tooltip'; -import { useTranslation } from 'react-i18next'; -import { SettingsDialog } from '$app/components/settings/SettingsDialog'; -import { ProfileAvatar } from '$app/components/_shared/avatar'; - -function UserInfo() { - const currentUser = useAppSelector((state) => state.currentUser); - const [showUserSetting, setShowUserSetting] = useState(false); - - const { t } = useTranslation(); - - return ( - <> -
-
- - {currentUser.displayName} -
- - - { - setShowUserSetting(!showUserSetting); - }} - > - - - -
- - {showUserSetting && setShowUserSetting(false)} />} - - ); -} - -export default UserInfo; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/top_bar/DeletePageSnackbar.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/top_bar/DeletePageSnackbar.tsx deleted file mode 100644 index f5638362b9..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/top_bar/DeletePageSnackbar.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import React, { useEffect } from 'react'; -import { Alert, Snackbar } from '@mui/material'; -import { useAppDispatch, useAppSelector } from '$app/stores/store'; -import { useParams } from 'react-router-dom'; -import { pagesActions } from '$app_reducers/pages/slice'; -import Slide, { SlideProps } from '@mui/material/Slide'; -import { useTranslation } from 'react-i18next'; -import Button from '@mui/material/Button'; -import { useTrashActions } from '$app/components/trash/Trash.hooks'; -import { openPage } from '$app_reducers/pages/async_actions'; - -function SlideTransition(props: SlideProps) { - return ; -} - -function DeletePageSnackbar() { - const firstViewId = useAppSelector((state) => { - const workspaceId = state.workspace.currentWorkspaceId; - const children = workspaceId ? state.pages.relationMap[workspaceId] : undefined; - - if (!children) return null; - - return children[0]; - }); - - const showTrashSnackbar = useAppSelector((state) => state.pages.showTrashSnackbar); - const dispatch = useAppDispatch(); - const { onPutback, onDelete } = useTrashActions(); - const { id } = useParams(); - - const { t } = useTranslation(); - - useEffect(() => { - dispatch(pagesActions.setTrashSnackbar(false)); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [id]); - - const handleBack = () => { - if (firstViewId) { - void dispatch(openPage(firstViewId)); - } - }; - - const handleClose = (toBack = true) => { - dispatch(pagesActions.setTrashSnackbar(false)); - if (toBack) { - handleBack(); - } - }; - - const handleRestore = () => { - if (!id) return; - void onPutback(id); - handleClose(false); - }; - - const handleDelete = () => { - if (!id) return; - void onDelete([id]); - - if (!firstViewId) { - handleClose(false); - return; - } - - handleBack(); - }; - - return ( - - handleClose()} - severity='info' - variant='standard' - sx={{ - width: '100%', - '.MuiAlert-action': { - padding: 0, - }, - }} - > -
- {t('deletePagePrompt.text')} - - -
-
-
- ); -} - -export default DeletePageSnackbar; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/top_bar/FontSizeConfig.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/top_bar/FontSizeConfig.tsx deleted file mode 100644 index 4b439cc3fb..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/top_bar/FontSizeConfig.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { ButtonGroup, Divider } from '@mui/material'; -import Button from '@mui/material/Button'; - -function FontSizeConfig() { - const { t } = useTranslation(); - - return ( - <> -
-
{t('moreAction.fontSize')}
-
- - - - - -
-
- - - ); -} - -export default FontSizeConfig; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/top_bar/MoreButton.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/top_bar/MoreButton.tsx deleted file mode 100644 index d37d1bf060..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/top_bar/MoreButton.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React, { useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; -import { Drawer, IconButton } from '@mui/material'; -import { ReactComponent as Details2Svg } from '$app/assets/details.svg'; -import Tooltip from '@mui/material/Tooltip'; -import MoreOptions from '$app/components/layout/top_bar/MoreOptions'; -import { useMoreOptionsConfig } from '$app/components/layout/top_bar/MoreOptions.hooks'; - -function MoreButton() { - const { t } = useTranslation(); - const [open, setOpen] = React.useState(false); - const toggleDrawer = useCallback((open: boolean) => { - setOpen(open); - }, []); - const { showMoreButton } = useMoreOptionsConfig(); - - if (!showMoreButton) return null; - return ( - <> - - toggleDrawer(true)} className={'text-icon-primary'}> - - - - toggleDrawer(false)}> - - - - ); -} - -export default MoreButton; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/top_bar/MoreOptions.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/layout/top_bar/MoreOptions.hooks.ts deleted file mode 100644 index 63f1173885..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/top_bar/MoreOptions.hooks.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { useLocation } from 'react-router-dom'; -import { useMemo } from 'react'; - -export function useMoreOptionsConfig() { - const location = useLocation(); - - const { type, pageType } = useMemo(() => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [_, type, pageType, id] = location.pathname.split('/'); - - return { - type, - pageType, - id, - }; - }, [location.pathname]); - - const showMoreButton = useMemo(() => { - return type === 'page'; - }, [type]); - - const showStyleOptions = useMemo(() => { - return type === 'page' && pageType === 'document'; - }, [pageType, type]); - - return { - showMoreButton, - showStyleOptions, - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/top_bar/MoreOptions.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/top_bar/MoreOptions.tsx deleted file mode 100644 index 7f77259212..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/top_bar/MoreOptions.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import FontSizeConfig from '$app/components/layout/top_bar/FontSizeConfig'; -import { useMoreOptionsConfig } from '$app/components/layout/top_bar/MoreOptions.hooks'; - -function MoreOptions() { - const { showStyleOptions } = useMoreOptionsConfig(); - - return
{showStyleOptions && }
; -} - -export default MoreOptions; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/top_bar/TopBar.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/top_bar/TopBar.tsx deleted file mode 100644 index 173bf86cab..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/top_bar/TopBar.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; -import CollapseMenuButton from '$app/components/layout/collapse_menu_button/CollapseMenuButton'; -import { useAppSelector } from '$app/stores/store'; -import Breadcrumb from '$app/components/layout/bread_crumb/BreadCrumb'; -import DeletePageSnackbar from '$app/components/layout/top_bar/DeletePageSnackbar'; - -function TopBar() { - const sidebarIsCollapsed = useAppSelector((state) => state.sidebar.isCollapsed); - - return ( -
- {sidebarIsCollapsed && ( -
- -
- )} -
-
- -
-
- -
- ); -} - -export default TopBar; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/workspace_manager/NestedPages.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/workspace_manager/NestedPages.tsx deleted file mode 100644 index ec3335b6b3..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/workspace_manager/NestedPages.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import React from 'react'; -import { useAppSelector } from '$app/stores/store'; -import NestedPage from '$app/components/layout/nested_page/NestedPage'; - -function WorkspaceNestedPages({ workspaceId }: { workspaceId: string }) { - const pageIds = useAppSelector((state) => { - return state.pages.relationMap[workspaceId]; - }); - - return ( -
- {pageIds?.map((pageId) => ( - - ))} -
- ); -} - -export default WorkspaceNestedPages; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/workspace_manager/NewPageButton.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/workspace_manager/NewPageButton.tsx deleted file mode 100644 index 537b7d2d9a..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/workspace_manager/NewPageButton.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React from 'react'; -import { useTranslation } from 'react-i18next'; -import { useWorkspaceActions } from '$app/components/layout/workspace_manager/Workspace.hooks'; -import Button from '@mui/material/Button'; -import { ReactComponent as AddSvg } from '$app/assets/add.svg'; - -function NewPageButton({ workspaceId }: { workspaceId: string }) { - const { t } = useTranslation(); - const { newPage } = useWorkspaceActions(workspaceId); - - return ( -
-
- } - className={'flex w-full items-center justify-start text-xs hover:bg-transparent hover:text-fill-default'} - > - {t('newPageText')} - -
- ); -} - -export default NewPageButton; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/workspace_manager/TrashButton.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/workspace_manager/TrashButton.tsx deleted file mode 100644 index 984ed6f67f..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/workspace_manager/TrashButton.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React, { useCallback } from 'react'; -import { ReactComponent as TrashSvg } from '$app/assets/delete.svg'; -import { useTranslation } from 'react-i18next'; -import { useLocation, useNavigate } from 'react-router-dom'; -import { useDrag } from 'src/appflowy_app/components/_shared/drag_block'; -import { deletePage } from '$app/application/folder/page.service'; - -function TrashButton() { - const { t } = useTranslation(); - const navigate = useNavigate(); - const currentPathType = useLocation().pathname.split('/')[1]; - const navigateToTrash = () => { - navigate('/trash'); - }; - - const selected = currentPathType === 'trash'; - - const onEnd = useCallback((result: { dragId: string; position: 'before' | 'after' | 'inside' }) => { - void deletePage(result.dragId); - }, []); - - const { onDrop, onDragOver, onDragLeave, isDraggingOver } = useDrag({ - onEnd, - }); - - return ( -
- - {t('trash.text')} -
- ); -} - -export default TrashButton; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/workspace_manager/Workspace.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/layout/workspace_manager/Workspace.hooks.ts deleted file mode 100644 index 86bca45ada..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/workspace_manager/Workspace.hooks.ts +++ /dev/null @@ -1,136 +0,0 @@ -import { useCallback, useEffect, useMemo } from 'react'; -import { useAppDispatch, useAppSelector } from '$app/stores/store'; -import { workspaceActions, WorkspaceItem } from '$app_reducers/workspace/slice'; -import { Page, pagesActions, parserViewPBToPage } from '$app_reducers/pages/slice'; -import { subscribeNotifications } from '$app/application/notification'; -import { FolderNotification, ViewLayoutPB } from '@/services/backend'; -import * as workspaceService from '$app/application/folder/workspace.service'; -import { createCurrentWorkspaceChildView } from '$app/application/folder/workspace.service'; -import { openPage } from '$app_reducers/pages/async_actions'; - -export function useLoadWorkspaces() { - const dispatch = useAppDispatch(); - const { workspaces, currentWorkspaceId } = useAppSelector((state) => state.workspace); - - const currentWorkspace = useMemo(() => { - return workspaces.find((workspace) => workspace.id === currentWorkspaceId); - }, [workspaces, currentWorkspaceId]); - - const initializeWorkspaces = useCallback(async () => { - const workspaces = await workspaceService.getWorkspaces(); - - const currentWorkspaceId = await workspaceService.getCurrentWorkspace(); - - dispatch( - workspaceActions.initWorkspaces({ - workspaces, - currentWorkspaceId, - }) - ); - }, [dispatch]); - - return { - workspaces, - currentWorkspace, - initializeWorkspaces, - }; -} - -export function useLoadWorkspace(workspace: WorkspaceItem) { - const { id } = workspace; - const dispatch = useAppDispatch(); - - const openWorkspace = useCallback(async () => { - await workspaceService.openWorkspace(id); - }, [id]); - - const deleteWorkspace = useCallback(async () => { - await workspaceService.deleteWorkspace(id); - }, [id]); - - const onChildPagesChanged = useCallback( - (childPages: Page[]) => { - dispatch( - pagesActions.addChildPages({ - id, - childPages, - }) - ); - }, - [dispatch, id] - ); - - const initializeWorkspace = useCallback(async () => { - const childPages = await workspaceService.getWorkspaceChildViews(id); - - dispatch( - pagesActions.addChildPages({ - id, - childPages, - }) - ); - }, [dispatch, id]); - - useEffect(() => { - void (async () => { - await initializeWorkspace(); - })(); - }, [initializeWorkspace]); - - useEffect(() => { - const unsubscribePromise = subscribeNotifications( - { - [FolderNotification.DidUpdateWorkspace]: async (changeset) => { - dispatch( - workspaceActions.updateWorkspace({ - id: String(changeset.id), - name: changeset.name, - icon: changeset.icon_url, - }) - ); - }, - [FolderNotification.DidUpdateWorkspaceViews]: async (changeset) => { - const res = changeset.items; - - onChildPagesChanged(res.map(parserViewPBToPage)); - }, - }, - { id } - ); - - return () => void unsubscribePromise.then((unsubscribe) => unsubscribe()); - }, [dispatch, id, onChildPagesChanged]); - - return { - openWorkspace, - deleteWorkspace, - }; -} - -export function useWorkspaceActions(workspaceId: string) { - const dispatch = useAppDispatch(); - const newPage = useCallback(async () => { - const { id } = await createCurrentWorkspaceChildView({ - name: '', - layout: ViewLayoutPB.Document, - parent_view_id: workspaceId, - }); - - dispatch( - pagesActions.addPage({ - page: { - id: id, - parentId: workspaceId, - layout: ViewLayoutPB.Document, - name: '', - }, - isLast: true, - }) - ); - void dispatch(openPage(id)); - }, [dispatch, workspaceId]); - - return { - newPage, - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/workspace_manager/Workspace.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/workspace_manager/Workspace.tsx deleted file mode 100644 index 24fc7be91e..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/workspace_manager/Workspace.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import React, { useState } from 'react'; -import { WorkspaceItem } from '$app_reducers/workspace/slice'; -import NestedViews from '$app/components/layout/workspace_manager/NestedPages'; -import { useLoadWorkspace, useWorkspaceActions } from '$app/components/layout/workspace_manager/Workspace.hooks'; -import { useTranslation } from 'react-i18next'; -import { ReactComponent as AddIcon } from '$app/assets/add.svg'; -import { IconButton } from '@mui/material'; -import Tooltip from '@mui/material/Tooltip'; -import { WorkplaceAvatar } from '$app/components/_shared/avatar'; - -function Workspace({ workspace, opened }: { workspace: WorkspaceItem; opened: boolean }) { - useLoadWorkspace(workspace); - const { t } = useTranslation(); - const { newPage } = useWorkspaceActions(workspace.id); - const [showPages, setShowPages] = useState(true); - const [showAdd, setShowAdd] = useState(false); - - return ( - <> -
-
{ - e.stopPropagation(); - e.preventDefault(); - setShowPages((prev) => { - return !prev; - }); - }} - onMouseEnter={() => { - setShowAdd(true); - }} - onMouseLeave={() => { - setShowAdd(false); - }} - className={'mt-2 flex h-[22px] w-full cursor-pointer select-none items-center justify-between px-4'} - > - -
- {!workspace.name ? ( - t('sideBar.personal') - ) : ( - <> - - {workspace.name} - - )} -
-
- {showAdd && ( - - { - e.stopPropagation(); - void newPage(); - }} - size={'small'} - > - - - - )} -
- -
- -
-
- - ); -} - -export default Workspace; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/layout/workspace_manager/WorkspaceManager.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/layout/workspace_manager/WorkspaceManager.tsx deleted file mode 100644 index 083dd61ec3..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/layout/workspace_manager/WorkspaceManager.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React, { useEffect } from 'react'; -import NewPageButton from '$app/components/layout/workspace_manager/NewPageButton'; -import { useLoadWorkspaces } from '$app/components/layout/workspace_manager/Workspace.hooks'; -import Workspace from './Workspace'; -import TrashButton from '$app/components/layout/workspace_manager/TrashButton'; -import { useAppSelector } from '@/appflowy_app/stores/store'; -import { LoginState } from '$app_reducers/current-user/slice'; -import { AFScroller } from '$app/components/_shared/scroller'; - -function WorkspaceManager() { - const { workspaces, currentWorkspace, initializeWorkspaces } = useLoadWorkspaces(); - - const loginState = useAppSelector((state) => state.currentUser.loginState); - - useEffect(() => { - if (loginState === LoginState.Success || loginState === undefined) { - void initializeWorkspaces(); - } - }, [initializeWorkspaces, loginState]); - - return ( -
- -
- {workspaces.map((workspace) => ( - - ))} -
-
-
- -
- {currentWorkspace && } -
- ); -} - -export default WorkspaceManager; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/settings/Login.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/settings/Login.tsx deleted file mode 100644 index d5ecc4bc0c..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/settings/Login.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import Typography from '@mui/material/Typography'; -import { useTranslation } from 'react-i18next'; -import Button from '@mui/material/Button'; -import { LoginButtonGroup } from '$app/components/auth/LoginButtonGroup'; - -export const Login = ({ onBack }: { onBack?: () => void }) => { - const { t } = useTranslation(); - - return ( -
- - {t('button.login')} - -
- - -
-
- ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/settings/Settings.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/settings/Settings.tsx deleted file mode 100644 index 1d9f3c0cd9..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/settings/Settings.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { useMemo, useState } from 'react'; -import { Box, Tab, Tabs } from '@mui/material'; -import { useTranslation } from 'react-i18next'; -import { MyAccount } from '$app/components/settings/my_account'; -import { ReactComponent as AccountIcon } from '$app/assets/settings/account.svg'; -import { ReactComponent as WorkplaceIcon } from '$app/assets/settings/workplace.svg'; -import { Workplace } from '$app/components/settings/workplace'; -import { SettingsRoutes } from '$app/components/settings/workplace/const'; - -interface TabPanelProps { - children?: React.ReactNode; - index: number; - value: number; -} - -function TabPanel(props: TabPanelProps) { - const { children, value, index, ...other } = props; - - return ( - - ); -} - -export const Settings = ({ onForward }: { onForward: (route: SettingsRoutes) => void }) => { - const { t } = useTranslation(); - const [activeTab, setActiveTab] = useState(0); - - const tabOptions = useMemo(() => { - return [ - { - label: t('newSettings.myAccount.title'), - Icon: AccountIcon, - Component: MyAccount, - }, - { - label: t('newSettings.workplace.name'), - Icon: WorkplaceIcon, - Component: Workplace, - }, - ]; - }, [t]); - - const handleChangeTab = (event: React.SyntheticEvent, newValue: number) => { - setActiveTab(newValue); - }; - - return ( - - - {tabOptions.map((tab, index) => ( - - - {tab.label} -
- } - onClick={() => setActiveTab(index)} - sx={{ '&.Mui-selected': { borderColor: 'transparent', backgroundColor: 'var(--fill-list-active)' } }} - /> - ))} - - {tabOptions.map((tab, index) => ( - - - - ))} - - ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/settings/SettingsDialog.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/settings/SettingsDialog.tsx deleted file mode 100644 index b53f8a6002..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/settings/SettingsDialog.tsx +++ /dev/null @@ -1,108 +0,0 @@ -/** - * @figmaUrl https://www.figma.com/file/MF5CWlOzBRuGHp45zAXyUH/Appflowy%3A-Desktop-Settings?type=design&node-id=100%3A2119&mode=design&t=4Wb0Zg5NOFO36kOf-1 - */ - -import Dialog, { DialogProps } from '@mui/material/Dialog'; -import { Settings } from '$app/components/settings/Settings'; -import { useCallback, useEffect, useRef, useState } from 'react'; -import DialogTitle from '@mui/material/DialogTitle'; -import { IconButton } from '@mui/material'; -import { ReactComponent as CloseIcon } from '$app/assets/close.svg'; -import { useTranslation } from 'react-i18next'; -import { ReactComponent as UpIcon } from '$app/assets/up.svg'; -import { SettingsRoutes } from '$app/components/settings/workplace/const'; -import DialogContent from '@mui/material/DialogContent'; -import { Login } from '$app/components/settings/Login'; -import SwipeableViews from 'react-swipeable-views'; -import { useAppDispatch, useAppSelector } from '$app/stores/store'; -import { currentUserActions, LoginState } from '$app_reducers/current-user/slice'; - -export const SettingsDialog = (props: DialogProps) => { - const dispatch = useAppDispatch(); - const [routes, setRoutes] = useState([]); - const loginState = useAppSelector((state) => state.currentUser.loginState); - const lastLoginStateRef = useRef(loginState); - const { t } = useTranslation(); - const handleForward = useCallback((route: SettingsRoutes) => { - setRoutes((prev) => { - return [...prev, route]; - }); - }, []); - - const handleBack = useCallback(() => { - setRoutes((prevState) => { - return prevState.slice(0, -1); - }); - dispatch(currentUserActions.resetLoginState()); - }, [dispatch]); - - const handleClose = useCallback(() => { - dispatch(currentUserActions.resetLoginState()); - props?.onClose?.({}, 'backdropClick'); - }, [dispatch, props]); - - const currentRoute = routes[routes.length - 1]; - - useEffect(() => { - if (lastLoginStateRef.current === LoginState.Loading && loginState === LoginState.Success) { - handleClose(); - return; - } - - lastLoginStateRef.current = loginState; - }, [loginState, handleClose]); - - return ( - { - if (e.key === 'Escape') { - e.preventDefault(); - } - }} - scroll={'paper'} - > - - - - - - ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/settings/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/settings/index.ts deleted file mode 100644 index 0f0a2c23f4..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/settings/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './my_account'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/settings/my_account/AccountLogin.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/settings/my_account/AccountLogin.tsx deleted file mode 100644 index 05b375c920..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/settings/my_account/AccountLogin.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { useTranslation } from 'react-i18next'; -import Typography from '@mui/material/Typography'; -import Button from '@mui/material/Button'; -import { Divider } from '@mui/material'; -import { DeleteAccount } from '$app/components/settings/my_account/DeleteAccount'; -import { SettingsRoutes } from '$app/components/settings/workplace/const'; -import { useAuth } from '$app/components/auth/auth.hooks'; - -export const AccountLogin = ({ onForward }: { onForward?: (route: SettingsRoutes) => void }) => { - const { t } = useTranslation(); - const { currentUser, logout } = useAuth(); - - const isLocal = currentUser.isLocal; - - return ( - <> -
- - {t('newSettings.myAccount.accountLogin')} - - - - -
- - ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/settings/my_account/DeleteAccount.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/settings/my_account/DeleteAccount.tsx deleted file mode 100644 index 82a909180e..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/settings/my_account/DeleteAccount.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { useTranslation } from 'react-i18next'; -import { useState } from 'react'; -import Typography from '@mui/material/Typography'; -import Button from '@mui/material/Button'; -import { DeleteAccountDialog } from '$app/components/settings/my_account/DeleteAccountDialog'; - -export const DeleteAccount = () => { - const { t } = useTranslation(); - - const [openDialog, setOpenDialog] = useState(false); - - return ( -
-
- - {t('newSettings.myAccount.deleteAccount.title')} - - - {t('newSettings.myAccount.deleteAccount.subtitle')} - -
-
- -
- { - setOpenDialog(false); - }} - /> -
- ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/settings/my_account/DeleteAccountDialog.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/settings/my_account/DeleteAccountDialog.tsx deleted file mode 100644 index 2f8cc37258..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/settings/my_account/DeleteAccountDialog.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import Dialog, { DialogProps } from '@mui/material/Dialog'; -import { useTranslation } from 'react-i18next'; -import DialogTitle from '@mui/material/DialogTitle'; -import { DialogActions, DialogContentText, IconButton } from '@mui/material'; -import Button from '@mui/material/Button'; -import DialogContent from '@mui/material/DialogContent'; -import { ReactComponent as CloseIcon } from '$app/assets/close.svg'; - -export const DeleteAccountDialog = (props: DialogProps) => { - const { t } = useTranslation(); - - const handleClose = () => { - props?.onClose?.({}, 'backdropClick'); - }; - - const handleOk = () => { - //123 - }; - - return ( - - {t('newSettings.myAccount.deleteAccount.dialogTitle')} - - {t('newSettings.myAccount.deleteAccount.dialogContent1')} - {t('newSettings.myAccount.deleteAccount.dialogContent2')} - - -
- -
-
- -
-
- - - -
- ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/settings/my_account/MyAccount.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/settings/my_account/MyAccount.tsx deleted file mode 100644 index b3a315994b..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/settings/my_account/MyAccount.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { useTranslation } from 'react-i18next'; -import Typography from '@mui/material/Typography'; -import { Profile } from './Profile'; -import { AccountLogin } from './AccountLogin'; -import { Divider } from '@mui/material'; -import { SettingsRoutes } from '$app/components/settings/workplace/const'; - -export const MyAccount = ({ onForward }: { onForward?: (route: SettingsRoutes) => void }) => { - const { t } = useTranslation(); - - return ( -
- - {t('newSettings.myAccount.title')} - - - {t('newSettings.myAccount.subtitle')} - - - - -
- ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/settings/my_account/Profile.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/settings/my_account/Profile.tsx deleted file mode 100644 index 2ac672b0e5..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/settings/my_account/Profile.tsx +++ /dev/null @@ -1,180 +0,0 @@ -import Typography from '@mui/material/Typography'; -import { useTranslation } from 'react-i18next'; -import { IconButton, InputAdornment, OutlinedInput } from '@mui/material'; -import { useAppSelector } from '$app/stores/store'; -import React, { useState } from 'react'; -import { ReactComponent as CheckIcon } from '$app/assets/select-check.svg'; -import { ReactComponent as CloseIcon } from '$app/assets/close.svg'; -import { ReactComponent as EditIcon } from '$app/assets/edit.svg'; - -import Tooltip from '@mui/material/Tooltip'; -import { UserService } from '$app/application/user/user.service'; -import { notify } from '$app/components/_shared/notify'; -import { ProfileAvatar } from '$app/components/_shared/avatar'; -import Popover from '@mui/material/Popover'; -import { PopoverCommonProps } from '$app/components/editor/components/tools/popover'; -import EmojiPicker from '$app/components/_shared/emoji_picker/EmojiPicker'; -import Button from '@mui/material/Button'; - -export const Profile = () => { - const { displayName, id } = useAppSelector((state) => state.currentUser); - const { t } = useTranslation(); - const [isEditing, setIsEditing] = useState(false); - const [newName, setNewName] = useState(displayName ?? 'Me'); - const [error, setError] = useState(false); - const [emojiPickerAnchor, setEmojiPickerAnchor] = useState(null); - const openEmojiPicker = Boolean(emojiPickerAnchor); - const handleSave = async () => { - setError(false); - if (!newName) { - setError(true); - return; - } - - if (newName === displayName) { - setIsEditing(false); - return; - } - - try { - await UserService.updateUserProfile({ - id, - name: newName, - }); - setIsEditing(false); - } catch { - setError(true); - notify.error(t('newSettings.myAccount.updateNameError')); - } - }; - - const handleEmojiSelect = async (emoji: string) => { - try { - await UserService.updateUserProfile({ - id, - icon_url: emoji, - }); - } catch { - notify.error(t('newSettings.myAccount.updateIconError')); - } - }; - - const handleCancel = () => { - setNewName(displayName ?? 'Me'); - setIsEditing(false); - }; - - const handleKeyDown = (e: React.KeyboardEvent) => { - if (e.key === 'Enter') { - e.stopPropagation(); - e.preventDefault(); - void handleSave(); - } - - if (e.key === 'Escape') { - e.stopPropagation(); - e.preventDefault(); - handleCancel(); - } - }; - - return ( -
- - {t('newSettings.myAccount.profileLabel')} - -
- - -
- {isEditing ? ( - setNewName(e.target.value)} - spellCheck={false} - autoFocus={true} - autoCorrect={'off'} - autoCapitalize={'off'} - fullWidth - endAdornment={ - -
- - - - - - - - - - -
-
- } - sx={{ - '&.MuiOutlinedInput-root': { - borderRadius: '8px', - }, - }} - placeholder={t('newSettings.myAccount.profileNamePlaceholder')} - value={newName} - /> - ) : ( - - {newName} - - setIsEditing(true)} size={'small'} className={'ml-1'}> - - - - - )} -
-
- {openEmojiPicker && ( - { - setEmojiPickerAnchor(null); - }} - anchorOrigin={{ - vertical: 'bottom', - horizontal: 'left', - }} - transformOrigin={{ - vertical: 'top', - horizontal: 'left', - }} - > - { - setEmojiPickerAnchor(null); - }} - onEmojiSelect={handleEmojiSelect} - /> - - )} -
- ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/settings/my_account/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/settings/my_account/index.ts deleted file mode 100644 index d923fcefce..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/settings/my_account/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './MyAccount'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/settings/workplace/Appearance.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/settings/workplace/Appearance.tsx deleted file mode 100644 index 1dc8581dae..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/settings/workplace/Appearance.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { useTranslation } from 'react-i18next'; -import { ThemeModeSwitch } from '$app/components/settings/workplace/appearance/ThemeModeSwitch'; -import Typography from '@mui/material/Typography'; -import { Divider } from '@mui/material'; -import { LanguageSetting } from '$app/components/settings/workplace/appearance/LanguageSetting'; - -export const Appearance = () => { - const { t } = useTranslation(); - - return ( - <> - - {t('newSettings.workplace.appearance.name')} - - - - - - ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/settings/workplace/Workplace.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/settings/workplace/Workplace.tsx deleted file mode 100644 index 8af69eec51..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/settings/workplace/Workplace.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { useTranslation } from 'react-i18next'; -import Typography from '@mui/material/Typography'; -import { WorkplaceDisplay } from '$app/components/settings/workplace/WorkplaceDisplay'; -import { Divider } from '@mui/material'; -import { Appearance } from '$app/components/settings/workplace/Appearance'; - -export const Workplace = () => { - const { t } = useTranslation(); - - return ( -
- - {t('newSettings.workplace.title')} - - - {t('newSettings.workplace.subtitle')} - - - - -
- ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/settings/workplace/WorkplaceDisplay.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/settings/workplace/WorkplaceDisplay.tsx deleted file mode 100644 index 3a71c5f070..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/settings/workplace/WorkplaceDisplay.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import { useTranslation } from 'react-i18next'; -import Typography from '@mui/material/Typography'; -import { Divider, OutlinedInput } from '@mui/material'; -import React, { useMemo, useState } from 'react'; -import Button from '@mui/material/Button'; -import { useAppDispatch, useAppSelector } from '$app/stores/store'; -import { changeWorkspaceIcon, renameWorkspace } from '$app/application/folder/workspace.service'; -import { notify } from '$app/components/_shared/notify'; -import { WorkplaceAvatar } from '$app/components/_shared/avatar'; -import Popover from '@mui/material/Popover'; -import { PopoverCommonProps } from '$app/components/editor/components/tools/popover'; -import EmojiPicker from '$app/components/_shared/emoji_picker/EmojiPicker'; -import { workspaceActions } from '$app_reducers/workspace/slice'; -import debounce from 'lodash-es/debounce'; - -export const WorkplaceDisplay = () => { - const { t } = useTranslation(); - const isLocal = useAppSelector((state) => state.currentUser.isLocal); - const { workspaces, currentWorkspaceId } = useAppSelector((state) => state.workspace); - const workspace = useMemo( - () => workspaces.find((workspace) => workspace.id === currentWorkspaceId), - [workspaces, currentWorkspaceId] - ); - const [name, setName] = useState(workspace?.name ?? ''); - const [emojiPickerAnchor, setEmojiPickerAnchor] = useState(null); - const openEmojiPicker = Boolean(emojiPickerAnchor); - const dispatch = useAppDispatch(); - - const debounceUpdateWorkspace = useMemo(() => { - return debounce(async ({ id, name, icon }: { id: string; name?: string; icon?: string }) => { - if (!id || !name) return; - - if (icon) { - try { - await changeWorkspaceIcon(id, icon); - } catch { - notify.error(t('newSettings.workplace.updateIconError')); - } - } - - if (name) { - try { - await renameWorkspace(id, name); - } catch { - notify.error(t('newSettings.workplace.renameError')); - } - } - }, 500); - }, [t]); - - const handleSave = async () => { - if (!workspace || !name) return; - dispatch(workspaceActions.updateWorkspace({ id: workspace.id, name })); - - await debounceUpdateWorkspace({ id: workspace.id, name }); - }; - - const handleEmojiSelect = async (icon: string) => { - if (!workspace) return; - dispatch(workspaceActions.updateWorkspace({ id: workspace.id, icon })); - - await debounceUpdateWorkspace({ id: workspace.id, icon }); - }; - - const handleKeyDown = (e: React.KeyboardEvent) => { - if (e.key === 'Enter') { - e.stopPropagation(); - e.preventDefault(); - void handleSave(); - } - }; - - return ( -
- - {t('newSettings.workplace.workplaceName')} - -
-
- setName(e.target.value)} - sx={{ - '&.MuiOutlinedInput-root': { - borderRadius: '8px', - }, - }} - placeholder={t('newSettings.workplace.workplaceNamePlaceholder')} - value={name} - /> -
- -
- - - {t('newSettings.workplace.workplaceIcon')} - - - {t('newSettings.workplace.workplaceIconSubtitle')} - - - {openEmojiPicker && ( - { - setEmojiPickerAnchor(null); - }} - anchorOrigin={{ - vertical: 'top', - horizontal: 'right', - }} - transformOrigin={{ - vertical: 'top', - horizontal: 'left', - }} - > - { - setEmojiPickerAnchor(null); - }} - onEmojiSelect={handleEmojiSelect} - /> - - )} -
- ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/settings/workplace/appearance/LanguageSetting.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/settings/workplace/appearance/LanguageSetting.tsx deleted file mode 100644 index 41a42bd011..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/settings/workplace/appearance/LanguageSetting.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import Typography from '@mui/material/Typography'; -import { useTranslation } from 'react-i18next'; -import MenuItem from '@mui/material/MenuItem'; -import Select from '@mui/material/Select'; -import React, { useCallback } from 'react'; -import { useAppDispatch, useAppSelector } from '$app/stores/store'; -import { currentUserActions } from '$app_reducers/current-user/slice'; -import { UserService } from '$app/application/user/user.service'; - -const languages = [ - { - key: 'ar-SA', - title: 'العربية', - }, - { key: 'ca-ES', title: 'Català' }, - { key: 'de-DE', title: 'Deutsch' }, - { key: 'en', title: 'English' }, - { key: 'es-VE', title: 'Español (Venezuela)' }, - { key: 'eu-ES', title: 'Español' }, - { key: 'fr-FR', title: 'Français' }, - { key: 'hu-HU', title: 'Magyar' }, - { key: 'id-ID', title: 'Bahasa Indonesia' }, - { key: 'it-IT', title: 'Italiano' }, - { key: 'ja-JP', title: '日本語' }, - { key: 'ko-KR', title: '한국어' }, - { key: 'pl-PL', title: 'Polski' }, - { key: 'pt-BR', title: 'Português' }, - { key: 'pt-PT', title: 'Português' }, - { key: 'ru-RU', title: 'Русский' }, - { key: 'sv', title: 'Svenska' }, - { key: 'th-TH', title: 'ไทย' }, - { key: 'tr-TR', title: 'Türkçe' }, - { key: 'zh-CN', title: '简体中文' }, - { key: 'zh-TW', title: '繁體中文' }, -]; - -export const LanguageSetting = () => { - const { t, i18n } = useTranslation(); - const userSettingState = useAppSelector((state) => state.currentUser.userSetting); - const dispatch = useAppDispatch(); - const selectedLanguage = userSettingState.language; - - const [hoverKey, setHoverKey] = React.useState(null); - - const handleChange = useCallback( - (language: string) => { - const newSetting = { ...userSettingState, language }; - - dispatch(currentUserActions.setUserSetting(newSetting)); - const newLanguage = newSetting.language || 'en'; - - void UserService.setAppearanceSetting({ - theme: newSetting.theme, - theme_mode: newSetting.themeMode, - locale: { - language_code: newLanguage.split('-')[0], - country_code: newLanguage.split('-')[1], - }, - }); - }, - [dispatch, userSettingState] - ); - - const handleKeyDown = useCallback((e: React.KeyboardEvent) => { - if (e.key === 'Escape') { - e.preventDefault(); - } - }, []); - - return ( - <> - - {t('newSettings.workplace.appearance.language')} - - - - - ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/settings/workplace/appearance/ThemeModeSwitch.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/settings/workplace/appearance/ThemeModeSwitch.tsx deleted file mode 100644 index 34fdb8e598..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/settings/workplace/appearance/ThemeModeSwitch.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import { useTranslation } from 'react-i18next'; -import { useAppDispatch, useAppSelector } from '$app/stores/store'; -import { useCallback, useMemo } from 'react'; -import { ThemeModePB } from '@/services/backend'; -import darkSrc from '$app/assets/settings/dark.png'; -import lightSrc from '$app/assets/settings/light.png'; -import { currentUserActions, ThemeMode } from '$app_reducers/current-user/slice'; -import { UserService } from '$app/application/user/user.service'; -import { ReactComponent as CheckCircle } from '$app/assets/settings/check_circle.svg'; - -export const ThemeModeSwitch = () => { - const { t } = useTranslation(); - const userSettingState = useAppSelector((state) => state.currentUser.userSetting); - const dispatch = useAppDispatch(); - - const selectedMode = userSettingState.themeMode; - const themeModes = useMemo(() => { - return [ - { - name: t('newSettings.workplace.appearance.themeMode.auto'), - value: ThemeModePB.System, - img: ( -
- - -
- ), - }, - { - name: t('newSettings.workplace.appearance.themeMode.light'), - value: ThemeModePB.Light, - img: , - }, - { - name: t('newSettings.workplace.appearance.themeMode.dark'), - value: ThemeModePB.Dark, - img: , - }, - ]; - }, [t]); - - const handleChange = useCallback( - (newValue: ThemeModePB) => { - const newSetting = { - ...userSettingState, - ...{ - themeMode: newValue, - isDark: - newValue === ThemeMode.Dark || - (newValue === ThemeMode.System && window.matchMedia('(prefers-color-scheme: dark)').matches), - }, - }; - - dispatch(currentUserActions.setUserSetting(newSetting)); - - void UserService.setAppearanceSetting({ - theme_mode: newSetting.themeMode, - }); - }, - [dispatch, userSettingState] - ); - - const renderThemeModeItem = useCallback( - (option: { name: string; value: ThemeModePB; img: JSX.Element }) => { - return ( -
handleChange(option.value)} - className={'flex cursor-pointer flex-col items-center gap-2'} - > -
- {option.img} - -
-
{option.name}
-
- ); - }, - [handleChange, selectedMode] - ); - - return
{themeModes.map((mode) => renderThemeModeItem(mode))}
; -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/settings/workplace/const.ts b/frontend/appflowy_tauri/src/appflowy_app/components/settings/workplace/const.ts deleted file mode 100644 index 075e2744a5..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/settings/workplace/const.ts +++ /dev/null @@ -1,3 +0,0 @@ -export enum SettingsRoutes { - LOGIN = 'login', -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/settings/workplace/index.ts b/frontend/appflowy_tauri/src/appflowy_app/components/settings/workplace/index.ts deleted file mode 100644 index a64592ac8b..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/settings/workplace/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './Workplace'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/trash/Trash.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/components/trash/Trash.hooks.ts deleted file mode 100644 index b6748614b8..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/trash/Trash.hooks.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { useCallback, useEffect, useState } from 'react'; -import { useAppDispatch, useAppSelector } from '@/appflowy_app/stores/store'; -import { trashActions, trashPBToTrash } from '$app_reducers/trash/slice'; -import { subscribeNotifications } from '$app/application/notification'; -import { FolderNotification } from '@/services/backend'; -import { deleteTrashItem, getTrash, putback, deleteAll, restoreAll } from '$app/application/folder/trash.service'; - -export function useLoadTrash() { - const trash = useAppSelector((state) => state.trash.list); - const dispatch = useAppDispatch(); - - const initializeTrash = useCallback(async () => { - const trash = await getTrash(); - - dispatch(trashActions.initTrash(trash.map(trashPBToTrash))); - }, [dispatch]); - - useEffect(() => { - void initializeTrash(); - }, [initializeTrash]); - - useEffect(() => { - const unsubscribePromise = subscribeNotifications({ - [FolderNotification.DidUpdateTrash]: async (changeset) => { - dispatch(trashActions.onTrashChanged(changeset.items.map(trashPBToTrash))); - }, - }); - - return () => { - void unsubscribePromise.then((fn) => fn()); - }; - }, [dispatch]); - - return { - trash, - }; -} - -export function useTrashActions() { - const [restoreAllDialogOpen, setRestoreAllDialogOpen] = useState(false); - const [deleteAllDialogOpen, setDeleteAllDialogOpen] = useState(false); - const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); - - const [deleteId, setDeleteId] = useState(''); - - const onClickRestoreAll = () => { - setRestoreAllDialogOpen(true); - }; - - const onClickDeleteAll = () => { - setDeleteAllDialogOpen(true); - }; - - const closeDialog = () => { - setRestoreAllDialogOpen(false); - setDeleteAllDialogOpen(false); - setDeleteDialogOpen(false); - }; - - const onClickDelete = (id: string) => { - setDeleteId(id); - setDeleteDialogOpen(true); - }; - - return { - onClickDelete, - deleteDialogOpen, - deleteId, - onPutback: putback, - onDelete: deleteTrashItem, - onDeleteAll: deleteAll, - onRestoreAll: restoreAll, - onClickRestoreAll, - onClickDeleteAll, - restoreAllDialogOpen, - deleteAllDialogOpen, - closeDialog, - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/trash/Trash.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/trash/Trash.tsx deleted file mode 100644 index f10848dc9b..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/trash/Trash.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import React, { useState } from 'react'; -import { useTranslation } from 'react-i18next'; -import Button from '@mui/material/Button'; -import { DeleteOutline, RestoreOutlined } from '@mui/icons-material'; -import { useLoadTrash, useTrashActions } from '$app/components/trash/Trash.hooks'; -import { List } from '@mui/material'; -import TrashItem from '$app/components/trash/TrashItem'; -import DeleteConfirmDialog from '$app/components/_shared/confirm_dialog/DeleteConfirmDialog'; - -function Trash() { - const { t } = useTranslation(); - const { trash } = useLoadTrash(); - const { - onPutback, - onDelete, - onClickRestoreAll, - onClickDeleteAll, - restoreAllDialogOpen, - deleteAllDialogOpen, - onRestoreAll, - onDeleteAll, - closeDialog, - deleteDialogOpen, - deleteId, - onClickDelete, - } = useTrashActions(); - const [hoverId, setHoverId] = useState(''); - - return ( -
-
-
{t('trash.text')}
-
- - -
-
-
-
{t('trash.pageHeader.fileName')}
-
{t('trash.pageHeader.lastModified')}
-
{t('trash.pageHeader.created')}
-
-
- - {trash.map((item) => ( - - ))} - - - - onDelete([deleteId])} - onClose={closeDialog} - /> -
- ); -} - -export default Trash; diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/trash/TrashItem.tsx b/frontend/appflowy_tauri/src/appflowy_app/components/trash/TrashItem.tsx deleted file mode 100644 index d266005612..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/components/trash/TrashItem.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import React from 'react'; -import dayjs from 'dayjs'; -import { IconButton, ListItem } from '@mui/material'; -import { DeleteOutline, RestoreOutlined } from '@mui/icons-material'; -import Tooltip from '@mui/material/Tooltip'; -import { useTranslation } from 'react-i18next'; -import { Trash } from '$app_reducers/trash/slice'; - -function TrashItem({ - item, - hoverId, - setHoverId, - onDelete, - onPutback, -}: { - setHoverId: (id: string) => void; - item: Trash; - hoverId: string; - onPutback: (id: string) => void; - onDelete: (id: string) => void; -}) { - const { t } = useTranslation(); - - return ( - { - setHoverId(item.id); - }} - onMouseLeave={() => { - setHoverId(''); - }} - key={item.id} - style={{ - paddingInline: 0, - }} - > -
-
- {item.name.trim() || t('menuAppHeader.defaultNewPageName')} -
-
{dayjs.unix(item.modifiedTime).format('MM/DD/YYYY hh:mm A')}
-
{dayjs.unix(item.createTime).format('MM/DD/YYYY hh:mm A')}
-
- - onPutback(item.id)} className={'mr-2'}> - - - - - onDelete(item.id)}> - - - -
-
-
- ); -} - -export default TrashItem; diff --git a/frontend/appflowy_tauri/src/appflowy_app/hooks/ViewId.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/hooks/ViewId.hooks.ts deleted file mode 100644 index 6711ece8c8..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/hooks/ViewId.hooks.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { createContext, useContext } from 'react'; - -const ViewIdContext = createContext(''); - -export const ViewIdProvider = ViewIdContext.Provider; -export const useViewId = () => useContext(ViewIdContext); diff --git a/frontend/appflowy_tauri/src/appflowy_app/hooks/index.ts b/frontend/appflowy_tauri/src/appflowy_app/hooks/index.ts deleted file mode 100644 index c29ddd04aa..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/hooks/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './notification.hooks'; -export * from './ViewId.hooks'; diff --git a/frontend/appflowy_tauri/src/appflowy_app/hooks/notification.hooks.ts b/frontend/appflowy_tauri/src/appflowy_app/hooks/notification.hooks.ts deleted file mode 100644 index f8669852d3..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/hooks/notification.hooks.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* eslint-disable no-redeclare */ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { useEffect } from 'react'; -import { NotificationEnum, NotificationHandler, subscribeNotification } from '$app/application/notification'; - -export function useNotification( - notification: K, - callback: NotificationHandler, - options: { id?: string } -): void { - const { id } = options; - - useEffect(() => { - const unsubscribePromise = subscribeNotification(notification, callback, { id }); - - return () => { - void unsubscribePromise.then((fn) => fn()); - }; - }, [callback, id, notification]); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/hooks/page.hooks.tsx b/frontend/appflowy_tauri/src/appflowy_app/hooks/page.hooks.tsx deleted file mode 100644 index 49e01e75c0..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/hooks/page.hooks.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { ViewLayoutPB } from '@/services/backend'; -import React from 'react'; -import { Page } from '$app_reducers/pages/slice'; -import { ReactComponent as DocumentIcon } from '$app/assets/document.svg'; -import { ReactComponent as GridIcon } from '$app/assets/grid.svg'; -import { ReactComponent as BoardIcon } from '$app/assets/board.svg'; -import { ReactComponent as CalendarIcon } from '$app/assets/date.svg'; - -export function getPageIcon(page: Page) { - if (page.icon) { - return page.icon.value; - } - - switch (page.layout) { - case ViewLayoutPB.Document: - return ; - case ViewLayoutPB.Grid: - return ; - case ViewLayoutPB.Board: - return ; - case ViewLayoutPB.Calendar: - return ; - default: - return null; - } -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/i18n/config.ts b/frontend/appflowy_tauri/src/appflowy_app/i18n/config.ts deleted file mode 100644 index d36dba3bb2..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/i18n/config.ts +++ /dev/null @@ -1,15 +0,0 @@ -import i18next from 'i18next'; -import LanguageDetector from 'i18next-browser-languagedetector'; -import { initReactI18next } from 'react-i18next'; -import resourcesToBackend from 'i18next-resources-to-backend'; - -void i18next - .use(resourcesToBackend((language: string) => import(`./translations/${language}.json`))) - .use(LanguageDetector) - .use(initReactI18next) - .init({ - lng: 'en', - defaultNS: 'translation', - debug: false, - fallbackLng: 'en', - }); diff --git a/frontend/appflowy_tauri/src/appflowy_app/slate-editor.d.ts b/frontend/appflowy_tauri/src/appflowy_app/slate-editor.d.ts deleted file mode 100644 index 26b713e333..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/slate-editor.d.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { ReactEditor } from 'slate-react'; - -interface EditorInlineAttributes { - bold?: boolean; - italic?: boolean; - underline?: boolean; - strikethrough?: boolean; - font_color?: string; - bg_color?: string; - href?: string; - code?: boolean; - formula?: string; - prism_token?: string; - class_name?: string; - mention?: { - type: string; - // inline page ref id - page?: string; - // reminder date ref id - date?: string; - }; -} - -type CustomElement = { - children: (CustomText | CustomElement)[]; - type: string; - data?: unknown; - blockId?: string; - textId?: string; -}; - -type CustomText = { text: string } & EditorInlineAttributes; - -declare module 'slate' { - interface CustomTypes { - Editor: BaseEditor & ReactEditor; - Element: CustomElement; - Text: CustomText; - } - - interface BaseEditor { - isEmbed: (element: CustomElement) => boolean; - } -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/current-user/slice.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/current-user/slice.ts deleted file mode 100644 index 464b7428a3..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/current-user/slice.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { WorkspaceSettingPB } from '@/services/backend/models/flowy-folder/workspace'; -import { ThemeModePB as ThemeMode } from '@/services/backend'; -import { Page, parserViewPBToPage } from '$app_reducers/pages/slice'; - -export { ThemeMode }; - -export interface UserSetting { - theme?: Theme; - themeMode?: ThemeMode; - language?: string; - isDark?: boolean; -} - -export enum Theme { - Default = 'default', - Dandelion = 'dandelion', - Lavender = 'lavender', -} - -export enum LoginState { - Loading = 'loading', - Success = 'success', - Error = 'error', -} - -export interface UserWorkspaceSetting { - workspaceId: string; - latestView?: Page; - hasLatestView: boolean; -} - -export function parseWorkspaceSettingPBToSetting(workspaceSetting: WorkspaceSettingPB): UserWorkspaceSetting { - return { - workspaceId: workspaceSetting.workspace_id, - latestView: workspaceSetting.latest_view ? parserViewPBToPage(workspaceSetting.latest_view) : undefined, - hasLatestView: !!workspaceSetting.latest_view, - }; -} - -export interface ICurrentUser { - id?: number; - deviceId?: string; - displayName?: string; - email?: string; - token?: string; - iconUrl?: string; - isAuthenticated: boolean; - workspaceSetting?: UserWorkspaceSetting; - userSetting: UserSetting; - isLocal: boolean; - loginState?: LoginState; -} - -const initialState: ICurrentUser | null = { - isAuthenticated: false, - userSetting: {}, - isLocal: true, -}; - -export const currentUserSlice = createSlice({ - name: 'currentUser', - initialState: initialState, - reducers: { - updateUser: (state, action: PayloadAction>) => { - return { - ...state, - ...action.payload, - loginState: LoginState.Success, - }; - }, - logout: () => { - return initialState; - }, - setUserSetting: (state, action: PayloadAction>) => { - state.userSetting = { - ...state.userSetting, - ...action.payload, - }; - }, - - setLoginState: (state, action: PayloadAction) => { - state.loginState = action.payload; - }, - - resetLoginState: (state) => { - state.loginState = undefined; - }, - - setLatestView: (state, action: PayloadAction) => { - if (state.workspaceSetting) { - state.workspaceSetting.latestView = action.payload; - state.workspaceSetting.hasLatestView = true; - } - }, - }, -}); - -export const currentUserActions = currentUserSlice.actions; diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/error/slice.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/error/slice.ts deleted file mode 100644 index 9b47df7777..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/error/slice.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; - -export interface IErrorOptions { - display: boolean; - message: string; -} - -const initialState: IErrorOptions = { - display: false, - message: '', -}; - -export const errorSlice = createSlice({ - name: 'error', - initialState: initialState, - reducers: { - showError(state, action: PayloadAction) { - return { - display: true, - message: action.payload, - }; - }, - hideError() { - return { - display: false, - message: '', - }; - }, - }, -}); - -export const errorActions = errorSlice.actions; diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/pages/async_actions.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/pages/async_actions.ts deleted file mode 100644 index 90014c1e7f..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/pages/async_actions.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { createAsyncThunk } from '@reduxjs/toolkit'; -import { RootState } from '$app/stores/store'; -import { pagesActions } from '$app_reducers/pages/slice'; -import { movePage, setLatestOpenedPage, updatePage } from '$app/application/folder/page.service'; -import debounce from 'lodash-es/debounce'; -import { currentUserActions } from '$app_reducers/current-user/slice'; - -export const movePageThunk = createAsyncThunk( - 'pages/movePage', - async ( - payload: { - sourceId: string; - targetId: string; - insertType: 'before' | 'after' | 'inside'; - }, - thunkAPI - ) => { - const { sourceId, targetId, insertType } = payload; - const { getState, dispatch } = thunkAPI; - const { pageMap, relationMap } = (getState() as RootState).pages; - const sourcePage = pageMap[sourceId]; - const targetPage = pageMap[targetId]; - - if (!sourcePage || !targetPage) return; - const sourceParentId = sourcePage.parentId; - const targetParentId = targetPage.parentId; - - if (!sourceParentId || !targetParentId) return; - - const targetParentChildren = relationMap[targetParentId] || []; - const targetIndex = targetParentChildren.indexOf(targetId); - - if (targetIndex < 0) return; - - let prevId, parentId; - - if (insertType === 'before') { - const prevIndex = targetIndex - 1; - - parentId = targetParentId; - if (prevIndex >= 0) { - prevId = targetParentChildren[prevIndex]; - } - } else if (insertType === 'after') { - prevId = targetId; - parentId = targetParentId; - } else { - const targetChildren = relationMap[targetId] || []; - - parentId = targetId; - if (targetChildren.length > 0) { - prevId = targetChildren[targetChildren.length - 1]; - } - } - - dispatch(pagesActions.movePage({ id: sourceId, newParentId: parentId, prevId })); - - await movePage({ - view_id: sourceId, - new_parent_id: parentId, - prev_view_id: prevId, - }); - } -); - -const debounceUpdateName = debounce(updatePage, 1000); - -export const updatePageName = createAsyncThunk( - 'pages/updateName', - async (payload: { id: string; name: string; immediate?: boolean }, thunkAPI) => { - const { dispatch, getState } = thunkAPI; - const { pageMap } = (getState() as RootState).pages; - const { id, name, immediate } = payload; - const page = pageMap[id]; - - if (name === page.name) return; - - dispatch( - pagesActions.onPageChanged({ - ...page, - name, - }) - ); - - if (immediate) { - await updatePage({ id, name }); - } else { - await debounceUpdateName({ - id, - name, - }); - } - } -); - -export const openPage = createAsyncThunk('pages/openPage', async (id: string, thunkAPI) => { - const { dispatch, getState } = thunkAPI; - const { pageMap } = (getState() as RootState).pages; - - const page = pageMap[id]; - - if (!page) return; - - dispatch(currentUserActions.setLatestView(page)); - await setLatestOpenedPage(id); -}); diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/pages/slice.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/pages/slice.ts deleted file mode 100644 index dbf313ecc1..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/pages/slice.ts +++ /dev/null @@ -1,223 +0,0 @@ -import { ViewIconTypePB, ViewLayoutPB, ViewPB } from '@/services/backend'; -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import isEqual from 'lodash-es/isEqual'; -import { ImageType } from '$app/application/document/document.types'; -import { Nullable } from 'unsplash-js/dist/helpers/typescript'; - -export const pageTypeMap = { - [ViewLayoutPB.Document]: 'document', - [ViewLayoutPB.Board]: 'board', - [ViewLayoutPB.Grid]: 'grid', - [ViewLayoutPB.Calendar]: 'calendar', -}; -export interface Page { - id: string; - parentId: string; - name: string; - layout: ViewLayoutPB; - icon?: PageIcon; - cover?: PageCover; -} - -export interface PageIcon { - ty: ViewIconTypePB; - value: string; -} - -export enum CoverType { - Color = 'CoverType.color', - Image = 'CoverType.file', - Asset = 'CoverType.asset', -} -export type PageCover = Nullable<{ - image_type?: ImageType; - cover_selection_type?: CoverType; - cover_selection?: string; -}>; - -export function parserViewPBToPage(view: ViewPB): Page { - const icon = view.icon; - - return { - id: view.id, - name: view.name, - parentId: view.parent_view_id, - layout: view.layout, - icon: icon - ? { - ty: icon.ty, - value: icon.value, - } - : undefined, - }; -} - -export interface PageState { - pageMap: Record; - relationMap: Record; - expandedIdMap: Record; - showTrashSnackbar: boolean; -} - -export const initialState: PageState = { - pageMap: {}, - relationMap: {}, - expandedIdMap: getExpandedPageIds().reduce((acc, id) => { - acc[id] = true; - return acc; - }, {} as Record), - showTrashSnackbar: false, -}; - -export const pagesSlice = createSlice({ - name: 'pages', - initialState, - reducers: { - addChildPages( - state, - action: PayloadAction<{ - childPages: Page[]; - id: string; - }> - ) { - const { childPages, id } = action.payload; - const pageMap: Record = {}; - - const children: string[] = []; - - childPages.forEach((page) => { - pageMap[page.id] = page; - children.push(page.id); - }); - - state.pageMap = { - ...state.pageMap, - ...pageMap, - }; - state.relationMap[id] = children; - }, - - onPageChanged(state, action: PayloadAction) { - const page = action.payload; - - if (!isEqual(state.pageMap[page.id], page)) { - state.pageMap[page.id] = page; - } - }, - - addPage( - state, - action: PayloadAction<{ - page: Page; - isLast?: boolean; - prevId?: string; - }> - ) { - const { page, prevId, isLast } = action.payload; - - state.pageMap[page.id] = page; - state.relationMap[page.id] = []; - - const parentId = page.parentId; - - if (isLast) { - state.relationMap[parentId]?.push(page.id); - } else { - const index = prevId ? state.relationMap[parentId]?.indexOf(prevId) ?? -1 : -1; - - state.relationMap[parentId]?.splice(index + 1, 0, page.id); - } - }, - - deletePages(state, action: PayloadAction) { - const ids = action.payload; - - ids.forEach((id) => { - const parentId = state.pageMap[id].parentId; - const parentChildren = state.relationMap[parentId]; - - state.relationMap[parentId] = parentChildren && parentChildren.filter((childId) => childId !== id); - delete state.relationMap[id]; - delete state.expandedIdMap[id]; - delete state.pageMap[id]; - }); - }, - - duplicatePage( - state, - action: PayloadAction<{ - id: string; - newId: string; - }> - ) { - const { id, newId } = action.payload; - const page = state.pageMap[id]; - const newPage = { ...page, id: newId }; - - state.pageMap[newPage.id] = newPage; - - const index = state.relationMap[page.parentId]?.indexOf(id); - - state.relationMap[page.parentId]?.splice(index ?? 0, 0, newId); - }, - - movePage( - state, - action: PayloadAction<{ - id: string; - newParentId: string; - prevId?: string; - }> - ) { - const { id, newParentId, prevId } = action.payload; - const parentId = state.pageMap[id].parentId; - const parentChildren = state.relationMap[parentId]; - - const index = parentChildren?.indexOf(id) ?? -1; - - if (index > -1) { - state.relationMap[parentId]?.splice(index, 1); - } - - state.pageMap[id].parentId = newParentId; - const newParentChildren = state.relationMap[newParentId] || []; - const prevIndex = prevId ? newParentChildren.indexOf(prevId) : -1; - - state.relationMap[newParentId]?.splice(prevIndex + 1, 0, id); - }, - - expandPage(state, action: PayloadAction) { - const id = action.payload; - - state.expandedIdMap[id] = true; - const ids = Object.keys(state.expandedIdMap).filter((id) => state.expandedIdMap[id]); - - storeExpandedPageIds(ids); - }, - - collapsePage(state, action: PayloadAction) { - const id = action.payload; - - state.expandedIdMap[id] = false; - const ids = Object.keys(state.expandedIdMap).filter((id) => state.expandedIdMap[id]); - - storeExpandedPageIds(ids); - }, - - setTrashSnackbar(state, action: PayloadAction) { - state.showTrashSnackbar = action.payload; - }, - }, -}); - -export const pagesActions = pagesSlice.actions; - -function storeExpandedPageIds(expandedPageIds: string[]) { - localStorage.setItem('expandedPageIds', JSON.stringify(expandedPageIds)); -} - -function getExpandedPageIds(): string[] { - const expandedPageIds = localStorage.getItem('expandedPageIds'); - - return expandedPageIds ? JSON.parse(expandedPageIds) : []; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/sidebar/slice.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/sidebar/slice.ts deleted file mode 100644 index fae1d59214..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/sidebar/slice.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; - -interface SidebarState { - isCollapsed: boolean; - width: number; - isResizing: boolean; -} - -const initialState: SidebarState = { - isCollapsed: false, - width: 250, - isResizing: false, -}; - -export const sidebarSlice = createSlice({ - name: 'sidebar', - initialState: initialState, - reducers: { - toggleCollapse(state) { - state.isCollapsed = !state.isCollapsed; - }, - setCollapse(state, action: PayloadAction) { - state.isCollapsed = action.payload; - }, - changeWidth(state, action: PayloadAction) { - state.width = action.payload; - }, - startResizing(state) { - state.isResizing = true; - }, - stopResizing(state) { - state.isResizing = false; - }, - }, -}); - -export const sidebarActions = sidebarSlice.actions; diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/trash/slice.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/trash/slice.ts deleted file mode 100644 index 98d850f6fe..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/trash/slice.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; -import { TrashPB } from '@/services/backend'; - -export interface Trash { - id: string; - name: string; - modifiedTime: number; - createTime: number; -} - -export function trashPBToTrash(trash: TrashPB) { - return { - id: trash.id, - name: trash.name, - modifiedTime: trash.modified_time, - createTime: trash.create_time, - }; -} - -interface TrashState { - list: Trash[]; -} - -const initialState: TrashState = { - list: [], -}; - -export const trashSlice = createSlice({ - name: 'trash', - initialState, - reducers: { - initTrash: (state, action: PayloadAction) => { - state.list = action.payload; - }, - onTrashChanged: (state, action: PayloadAction) => { - state.list = action.payload; - }, - }, -}); - -export const trashActions = trashSlice.actions; diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/workspace/slice.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/workspace/slice.ts deleted file mode 100644 index d071de846e..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/stores/reducers/workspace/slice.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { createSlice, PayloadAction } from '@reduxjs/toolkit'; - -export interface WorkspaceItem { - id: string; - name: string; - icon?: string; -} - -interface WorkspaceState { - workspaces: WorkspaceItem[]; - currentWorkspaceId: string | null; -} - -const initialState: WorkspaceState = { - workspaces: [], - currentWorkspaceId: null, -}; - -export const workspaceSlice = createSlice({ - name: 'workspace', - initialState, - reducers: { - initWorkspaces: ( - state, - action: PayloadAction<{ - workspaces: WorkspaceItem[]; - currentWorkspaceId: string | null; - }> - ) => { - return action.payload; - }, - - updateWorkspace: (state, action: PayloadAction>) => { - const index = state.workspaces.findIndex((workspace) => workspace.id === action.payload.id); - - if (index !== -1) { - state.workspaces[index] = { - ...state.workspaces[index], - ...action.payload, - }; - } - }, - }, -}); - -export const workspaceActions = workspaceSlice.actions; diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/store.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/store.ts deleted file mode 100644 index 269f46884c..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/stores/store.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; -import { - configureStore, - createListenerMiddleware, - TypedStartListening, - TypedAddListener, - ListenerEffectAPI, - addListener, -} from '@reduxjs/toolkit'; -import { pagesSlice } from './reducers/pages/slice'; -import { currentUserSlice } from './reducers/current-user/slice'; -import { workspaceSlice } from './reducers/workspace/slice'; -import { errorSlice } from './reducers/error/slice'; -import { sidebarSlice } from '$app_reducers/sidebar/slice'; -import { trashSlice } from '$app_reducers/trash/slice'; - -const listenerMiddlewareInstance = createListenerMiddleware({ - onError: () => console.error, -}); - -const store = configureStore({ - reducer: { - [pagesSlice.name]: pagesSlice.reducer, - [currentUserSlice.name]: currentUserSlice.reducer, - [workspaceSlice.name]: workspaceSlice.reducer, - [errorSlice.name]: errorSlice.reducer, - [sidebarSlice.name]: sidebarSlice.reducer, - [trashSlice.name]: trashSlice.reducer, - }, - middleware: (gDM) => gDM({ serializableCheck: false }).prepend(listenerMiddlewareInstance.middleware), -}); - -export { store }; - -// Infer the `RootState` and `AppDispatch` types from the store itself -export type RootState = ReturnType; -// @see https://redux-toolkit.js.org/usage/usage-with-typescript#getting-the-dispatch-type -export type AppDispatch = typeof store.dispatch; - -export type AppListenerEffectAPI = ListenerEffectAPI; - -// @see https://redux-toolkit.js.org/api/createListenerMiddleware#typescript-usage -export type AppStartListening = TypedStartListening; -export type AppAddListener = TypedAddListener; - -export const startAppListening = listenerMiddlewareInstance.startListening as AppStartListening; -export const addAppListener = addListener as AppAddListener; - -// Use throughout your app instead of plain `useDispatch` and `useSelector` -export const useAppDispatch = () => useDispatch(); -export const useAppSelector: TypedUseSelectorHook = useSelector; diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/async_queue.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/async_queue.ts deleted file mode 100644 index 7e673506de..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/utils/async_queue.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { Log } from '$app/utils/log'; - -export class AsyncQueue { - private queue: T[] = []; - private isProcessing = false; - private executeFunction: (item: T) => Promise; - - constructor(executeFunction: (item: T) => Promise) { - this.executeFunction = executeFunction; - } - - enqueue(item: T): void { - this.queue.push(item); - this.processQueue(); - } - - private processQueue(): void { - if (this.isProcessing || this.queue.length === 0) { - return; - } - - const item = this.queue.shift(); - - if (!item) { - return; - } - - this.isProcessing = true; - - const executeFn = async (item: T) => { - try { - await this.processItem(item); - } catch (error) { - Log.error('queue processing error:', error); - } finally { - this.isProcessing = false; - this.processQueue(); - } - }; - - void executeFn(item); - } - - private async processItem(item: T): Promise { - try { - await this.executeFunction(item); - } catch (error) { - Log.error('queue processing error:', error); - } - } -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/avatar.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/avatar.ts deleted file mode 100644 index a9a752c579..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/utils/avatar.ts +++ /dev/null @@ -1,26 +0,0 @@ -export function stringToColor(string: string) { - let hash = 0; - let i; - - /* eslint-disable no-bitwise */ - for (i = 0; i < string.length; i += 1) { - hash = string.charCodeAt(i) + ((hash << 5) - hash); - } - - let color = '#'; - - for (i = 0; i < 3; i += 1) { - const value = (hash >> (i * 8)) & 0xff; - - color += `00${value.toString(16)}`.slice(-2); - } - /* eslint-enable no-bitwise */ - - return color; -} - -export function stringToShortName(string: string) { - const [firstName, lastName = ''] = string.split(' '); - - return `${firstName.charAt(0)}${lastName.charAt(0)}`; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/change_notifier.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/change_notifier.ts deleted file mode 100644 index 57d9f2a370..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/utils/change_notifier.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Observable, Subject } from 'rxjs'; - -export class ChangeNotifier { - private isUnsubscribe = false; - private subject = new Subject(); - - notify(value: T) { - this.subject.next(value); - } - - get observer(): Observable | null { - if (this.isUnsubscribe) { - return null; - } - - return this.subject.asObservable(); - } - - // Unsubscribe the subject might cause [UnsubscribedError] error if there is - // ongoing Observable execution. - // - // Maybe you should use the [Subscription] that returned when call subscribe on - // [Observable] to unsubscribe. - unsubscribe = () => { - if (!this.isUnsubscribe) { - this.isUnsubscribe = true; - this.subject.unsubscribe(); - } - }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/color.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/color.ts deleted file mode 100644 index 025c8c45ed..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/utils/color.ts +++ /dev/null @@ -1,50 +0,0 @@ -export enum ColorEnum { - Purple = 'appflowy_them_color_tint1', - Pink = 'appflowy_them_color_tint2', - LightPink = 'appflowy_them_color_tint3', - Orange = 'appflowy_them_color_tint4', - Yellow = 'appflowy_them_color_tint5', - Lime = 'appflowy_them_color_tint6', - Green = 'appflowy_them_color_tint7', - Aqua = 'appflowy_them_color_tint8', - Blue = 'appflowy_them_color_tint9', -} - -export const colorMap = { - [ColorEnum.Purple]: 'var(--tint-purple)', - [ColorEnum.Pink]: 'var(--tint-pink)', - [ColorEnum.LightPink]: 'var(--tint-red)', - [ColorEnum.Orange]: 'var(--tint-orange)', - [ColorEnum.Yellow]: 'var(--tint-yellow)', - [ColorEnum.Lime]: 'var(--tint-lime)', - [ColorEnum.Green]: 'var(--tint-green)', - [ColorEnum.Aqua]: 'var(--tint-aqua)', - [ColorEnum.Blue]: 'var(--tint-blue)', -}; - -// Convert ARGB to RGBA -// Flutter uses ARGB, but CSS uses RGBA -function argbToRgba(color: string): string { - const hex = color.replace(/^#|0x/, ''); - - const hasAlpha = hex.length === 8; - - if (!hasAlpha) { - return color.replace('0x', '#'); - } - - const r = parseInt(hex.slice(2, 4), 16); - const g = parseInt(hex.slice(4, 6), 16); - const b = parseInt(hex.slice(6, 8), 16); - const a = hasAlpha ? parseInt(hex.slice(0, 2), 16) / 255 : 1; - - return `rgba(${r}, ${g}, ${b}, ${a})`; -} - -export function renderColor(color: string) { - if (colorMap[color as ColorEnum]) { - return colorMap[color as ColorEnum]; - } - - return argbToRgba(color); -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/emoji.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/emoji.ts deleted file mode 100644 index 8d5adb5df6..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/utils/emoji.ts +++ /dev/null @@ -1,9 +0,0 @@ -import emojiData, { EmojiMartData } from '@emoji-mart/data'; - -export const randomEmoji = (skin = 0) => { - const emojis = (emojiData as EmojiMartData).emojis; - const keys = Object.keys(emojis); - const randomKey = keys[Math.floor(Math.random() * keys.length)]; - - return emojis[randomKey].skins[skin].native; -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/env.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/env.ts deleted file mode 100644 index 064dc042aa..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/utils/env.ts +++ /dev/null @@ -1,11 +0,0 @@ -export function isApple() { - return typeof navigator !== 'undefined' && /Mac OS X/.test(navigator.userAgent); -} - -export function isTauri() { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - const isTauri = window.__TAURI__; - - return isTauri; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/hotkeys.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/hotkeys.ts deleted file mode 100644 index 20aa05db27..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/utils/hotkeys.ts +++ /dev/null @@ -1,134 +0,0 @@ -import isHotkey from 'is-hotkey'; - -export const isMac = () => { - return navigator.userAgent.includes('Mac OS X'); -}; - -const MODIFIERS = { - control: 'Ctrl', - meta: '⌘', -}; - -export const getModifier = () => { - return isMac() ? MODIFIERS.meta : MODIFIERS.control; -}; - -export enum HOT_KEY_NAME { - LEFT = 'left', - RIGHT = 'right', - SELECT_ALL = 'select-all', - ESCAPE = 'escape', - ALIGN_LEFT = 'align-left', - ALIGN_CENTER = 'align-center', - ALIGN_RIGHT = 'align-right', - BOLD = 'bold', - ITALIC = 'italic', - UNDERLINE = 'underline', - STRIKETHROUGH = 'strikethrough', - CODE = 'code', - TOGGLE_TODO = 'toggle-todo', - TOGGLE_COLLAPSE = 'toggle-collapse', - INDENT_BLOCK = 'indent-block', - OUTDENT_BLOCK = 'outdent-block', - INSERT_SOFT_BREAK = 'insert-soft-break', - SPLIT_BLOCK = 'split-block', - BACKSPACE = 'backspace', - OPEN_LINK = 'open-link', - OPEN_LINKS = 'open-links', - EXTEND_LINE_BACKWARD = 'extend-line-backward', - EXTEND_LINE_FORWARD = 'extend-line-forward', - PASTE = 'paste', - PASTE_PLAIN_TEXT = 'paste-plain-text', - HIGH_LIGHT = 'high-light', - EXTEND_DOCUMENT_BACKWARD = 'extend-document-backward', - EXTEND_DOCUMENT_FORWARD = 'extend-document-forward', - SCROLL_TO_TOP = 'scroll-to-top', - SCROLL_TO_BOTTOM = 'scroll-to-bottom', - FORMAT_LINK = 'format-link', - FIND_REPLACE = 'find-replace', - /** - * Navigation - */ - TOGGLE_THEME = 'toggle-theme', - TOGGLE_SIDEBAR = 'toggle-sidebar', -} - -const defaultHotKeys = { - [HOT_KEY_NAME.ALIGN_LEFT]: ['control+shift+l'], - [HOT_KEY_NAME.ALIGN_CENTER]: ['control+shift+e'], - [HOT_KEY_NAME.ALIGN_RIGHT]: ['control+shift+r'], - [HOT_KEY_NAME.BOLD]: ['mod+b'], - [HOT_KEY_NAME.ITALIC]: ['mod+i'], - [HOT_KEY_NAME.UNDERLINE]: ['mod+u'], - [HOT_KEY_NAME.STRIKETHROUGH]: ['mod+shift+s', 'mod+shift+x'], - [HOT_KEY_NAME.CODE]: ['mod+e'], - [HOT_KEY_NAME.TOGGLE_TODO]: ['mod+enter'], - [HOT_KEY_NAME.TOGGLE_COLLAPSE]: ['mod+enter'], - [HOT_KEY_NAME.SELECT_ALL]: ['mod+a'], - [HOT_KEY_NAME.ESCAPE]: ['esc'], - [HOT_KEY_NAME.INDENT_BLOCK]: ['tab'], - [HOT_KEY_NAME.OUTDENT_BLOCK]: ['shift+tab'], - [HOT_KEY_NAME.SPLIT_BLOCK]: ['enter'], - [HOT_KEY_NAME.INSERT_SOFT_BREAK]: ['shift+enter'], - [HOT_KEY_NAME.BACKSPACE]: ['backspace', 'shift+backspace'], - [HOT_KEY_NAME.OPEN_LINK]: ['opt+enter'], - [HOT_KEY_NAME.OPEN_LINKS]: ['opt+shift+enter'], - [HOT_KEY_NAME.EXTEND_LINE_BACKWARD]: ['opt+shift+left'], - [HOT_KEY_NAME.EXTEND_LINE_FORWARD]: ['opt+shift+right'], - [HOT_KEY_NAME.PASTE]: ['mod+v'], - [HOT_KEY_NAME.PASTE_PLAIN_TEXT]: ['mod+shift+v'], - [HOT_KEY_NAME.HIGH_LIGHT]: ['mod+shift+h'], - [HOT_KEY_NAME.EXTEND_DOCUMENT_BACKWARD]: ['mod+shift+up'], - [HOT_KEY_NAME.EXTEND_DOCUMENT_FORWARD]: ['mod+shift+down'], - [HOT_KEY_NAME.SCROLL_TO_TOP]: ['home'], - [HOT_KEY_NAME.SCROLL_TO_BOTTOM]: ['end'], - [HOT_KEY_NAME.TOGGLE_THEME]: ['mod+shift+l'], - [HOT_KEY_NAME.TOGGLE_SIDEBAR]: ['mod+.'], - [HOT_KEY_NAME.FORMAT_LINK]: ['mod+k'], - [HOT_KEY_NAME.LEFT]: ['left'], - [HOT_KEY_NAME.RIGHT]: ['right'], - [HOT_KEY_NAME.FIND_REPLACE]: ['mod+f'], -}; - -const replaceModifier = (hotkey: string) => { - return hotkey.replace('mod', getModifier()).replace('control', 'ctrl'); -}; - -/** - * Create a hotkey checker. - * @example trigger strike through when user press "Cmd + Shift + S" or "Cmd + Shift + X" - * @param hotkeyName - * @param customHotKeys - */ -export const createHotkey = (hotkeyName: HOT_KEY_NAME, customHotKeys?: Record) => { - const keys = customHotKeys || defaultHotKeys; - const hotkeys = keys[hotkeyName]; - - return (event: KeyboardEvent) => { - return hotkeys.some((hotkey) => { - return isHotkey(hotkey, event); - }); - }; -}; - -/** - * Create a hotkey label. - * eg. "Ctrl + B / ⌘ + B" - * @param hotkeyName - * @param customHotKeys - */ -export const createHotKeyLabel = (hotkeyName: HOT_KEY_NAME, customHotKeys?: Record) => { - const keys = customHotKeys || defaultHotKeys; - const hotkeys = keys[hotkeyName].map((key) => replaceModifier(key)); - - return hotkeys - .map((hotkey) => - hotkey - .split('+') - .map((key) => { - return key === ' ' ? 'Space' : key.charAt(0).toUpperCase() + key.slice(1); - }) - .join(' + ') - ) - .join(' / '); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/list.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/list.ts deleted file mode 100644 index 6e5d22ccda..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/utils/list.ts +++ /dev/null @@ -1,45 +0,0 @@ -const romanMap: [number, string][] = [ - [1000, 'M'], - [900, 'CM'], - [500, 'D'], - [400, 'CD'], - [100, 'C'], - [90, 'XC'], - [50, 'L'], - [40, 'XL'], - [10, 'X'], - [9, 'IX'], - [5, 'V'], - [4, 'IV'], - [1, 'I'], -]; - -export function romanize(num: number): string { - let result = ''; - let nextNum = num; - - for (const [value, symbol] of romanMap) { - const count = Math.floor(nextNum / value); - - nextNum -= value * count; - result += symbol.repeat(count); - if (nextNum === 0) break; - } - - return result; -} - -export function letterize(num: number): string { - let nextNum = num; - let letters = ''; - - while (nextNum > 0) { - nextNum--; - const letter = String.fromCharCode((nextNum % 26) + 'a'.charCodeAt(0)); - - letters = letter + letters; - nextNum = Math.floor(nextNum / 26); - } - - return letters; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/log.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/log.ts deleted file mode 100644 index daccf21d0a..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/utils/log.ts +++ /dev/null @@ -1,20 +0,0 @@ -export class Log { - static error(...msg: unknown[]) { - console.error(...msg); - } - static info(...msg: unknown[]) { - console.info(...msg); - } - - static debug(...msg: unknown[]) { - console.debug(...msg); - } - - static trace(...msg: unknown[]) { - console.trace(...msg); - } - - static warn(...msg: unknown[]) { - console.warn(...msg); - } -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/mui.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/mui.ts deleted file mode 100644 index 94e2cf94d5..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/utils/mui.ts +++ /dev/null @@ -1,168 +0,0 @@ -import { ThemeOptions } from '@mui/material'; - -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -export const getDesignTokens = (isDark: boolean): ThemeOptions => { - return { - typography: { - fontFamily: ['Poppins'].join(','), - fontSize: 12, - button: { - textTransform: 'none', - }, - }, - components: { - MuiMenuItem: { - defaultProps: { - sx: { - '&.Mui-selected.Mui-focusVisible': { - backgroundColor: 'var(--fill-list-hover)', - }, - '&.Mui-focusVisible': { - backgroundColor: 'unset', - }, - }, - }, - }, - MuiIconButton: { - styleOverrides: { - root: { - '&:hover': { - backgroundColor: 'var(--fill-list-hover)', - }, - borderRadius: '4px', - padding: '2px', - }, - }, - }, - MuiButton: { - styleOverrides: { - contained: { - color: 'var(--content-on-fill)', - boxShadow: 'var(--shadow)', - }, - containedPrimary: { - '&:hover': { - backgroundColor: 'var(--fill-default)', - }, - }, - containedInherit: { - color: 'var(--text-title)', - backgroundColor: isDark ? 'rgba(0, 0, 0, 0.4)' : 'rgba(255, 255, 255, 0.4)', - '&:hover': { - backgroundColor: 'var(--bg-body)', - boxShadow: 'var(--shadow)', - }, - }, - outlinedInherit: { - color: 'var(--text-title)', - borderColor: 'var(--line-border)', - '&:hover': { - boxShadow: 'var(--shadow)', - }, - }, - }, - }, - MuiButtonBase: { - defaultProps: { - sx: { - '&.Mui-selected:hover': { - backgroundColor: 'var(--fill-list-hover)', - }, - }, - }, - styleOverrides: { - root: { - '&:hover': { - backgroundColor: 'var(--fill-list-hover)', - }, - '&:active': { - backgroundColor: 'var(--fill-list-hover)', - }, - borderRadius: '4px', - padding: '2px', - boxShadow: 'none', - }, - }, - }, - MuiPaper: { - styleOverrides: { - root: { - backgroundImage: 'none', - }, - }, - }, - MuiDialog: { - defaultProps: { - sx: { - '& .MuiBackdrop-root': { - backgroundColor: 'var(--bg-mask)', - }, - }, - }, - }, - - MuiTooltip: { - styleOverrides: { - arrow: { - color: 'var(--bg-tips)', - }, - tooltip: { - backgroundColor: 'var(--bg-tips)', - color: 'var(--text-title)', - fontSize: '0.85rem', - borderRadius: '8px', - fontWeight: 400, - }, - }, - }, - MuiInputBase: { - styleOverrides: { - input: { - backgroundColor: 'transparent !important', - }, - }, - }, - MuiDivider: { - styleOverrides: { - root: { - borderColor: 'var(--line-divider)', - }, - }, - }, - }, - palette: { - mode: isDark ? 'dark' : 'light', - primary: { - main: '#00BCF0', - dark: '#00BCF0', - }, - error: { - main: '#FB006D', - dark: '#D32772', - }, - warning: { - main: '#FFC107', - dark: '#E9B320', - }, - info: { - main: '#00BCF0', - dark: '#2E9DBB', - }, - success: { - main: '#66CF80', - dark: '#3BA856', - }, - text: { - primary: isDark ? '#E2E9F2' : '#333333', - secondary: isDark ? '#7B8A9D' : '#828282', - disabled: isDark ? '#363D49' : '#F2F2F2', - }, - divider: isDark ? '#59647A' : '#BDBDBD', - background: { - default: isDark ? '#1A202C' : '#FFFFFF', - paper: isDark ? '#1A202C' : '#FFFFFF', - }, - }, - }; -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/open_url.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/open_url.ts deleted file mode 100644 index d854be5211..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/utils/open_url.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { open as openWindow } from '@tauri-apps/api/shell'; - -const urlPattern = /^(https?:\/\/)?([\da-z.-]+)\.([a-z.]{2,6})(\S*)*\/?(\?[=&\w.%-]*)?(#[\w.\-!~*'()]*)?$/; -const ipPattern = /^(https?:\/\/)?(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(:\d{1,5})?$/; - -export function isUrl(str: string) { - return urlPattern.test(str) || ipPattern.test(str); -} - -export function openUrl(str: string) { - if (isUrl(str)) { - const linkPrefix = ['http://', 'https://', 'file://', 'ftp://', 'ftps://', 'mailto:']; - - if (linkPrefix.some((prefix) => str.startsWith(prefix))) { - void openWindow(str); - } else { - void openWindow('https://' + str); - } - } else { - // open google search - void openWindow('https://www.google.com/search?q=' + encodeURIComponent(str)); - } -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/tool.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/tool.ts deleted file mode 100644 index afcd7a32b4..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/utils/tool.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ - -/** - * Creates an interval that repeatedly calls the given function with a specified delay. - * - * @param {Function} fn - The function to be called repeatedly. - * @param {number} [delay] - The delay between function calls in milliseconds. - * @param {Object} [options] - Additional options for the interval. - * @param {boolean} [options.immediate] - Whether to immediately call the function when the interval is created. Default is true. - * - * @return {Function} - The function that runs the interval. - * @return {Function.cancel} - A method to cancel the interval. - * - * @example - * const log = interval((message) => console.log(message), 1000); - * - * log('foo'); // prints 'foo' every second. - * - * log('bar'); // change to prints 'bar' every second. - * - * log.cancel(); // stops the interval. - */ -export function interval any = (...args: any[]) => any>( - fn: T, - delay?: number, - options?: { immediate?: boolean } -): T & { cancel: () => void } { - const { immediate = true } = options || {}; - let intervalId: NodeJS.Timer | null = null; - let parameters: any[] = []; - - function run(...args: Parameters) { - parameters = args; - - if (intervalId !== null) { - return; - } - - immediate && fn.apply(undefined, parameters); - intervalId = setInterval(() => { - fn.apply(undefined, parameters); - }, delay); - } - - function cancel() { - if (intervalId === null) { - return; - } - - clearInterval(intervalId); - intervalId = null; - parameters = []; - } - - run.cancel = cancel; - return run as T & { cancel: () => void }; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/utils/upload_image.ts b/frontend/appflowy_tauri/src/appflowy_app/utils/upload_image.ts deleted file mode 100644 index 22213ac8b3..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/utils/upload_image.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const MAX_IMAGE_SIZE = 10 * 1024 * 1024; // 10MB -export const ALLOWED_IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp']; -export const IMAGE_DIR = 'images'; - -export function getFileName(url: string) { - const [...parts] = url.split('/'); - - return parts.pop() ?? url; -} diff --git a/frontend/appflowy_tauri/src/appflowy_app/views/DatabasePage.tsx b/frontend/appflowy_tauri/src/appflowy_app/views/DatabasePage.tsx deleted file mode 100644 index 004fc4355b..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/views/DatabasePage.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { useParams } from 'react-router-dom'; -import { ViewIdProvider } from '$app/hooks'; -import { Database, DatabaseTitle, useSelectDatabaseView } from '../components/database'; - -export const DatabasePage = () => { - const viewId = useParams().id; - - const { selectedViewId, onChange } = useSelectDatabaseView({ - viewId, - }); - - if (!viewId) { - return null; - } - - return ( -
- - - - -
- ); -}; diff --git a/frontend/appflowy_tauri/src/appflowy_app/views/DocumentPage.tsx b/frontend/appflowy_tauri/src/appflowy_app/views/DocumentPage.tsx deleted file mode 100644 index 03ba493c10..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/views/DocumentPage.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import { useParams } from 'react-router-dom'; -import { Document } from '$app/components/document'; - -function DocumentPage() { - const params = useParams(); - - const documentId = params.id; - - if (!documentId) return null; - return ; -} - -export default DocumentPage; diff --git a/frontend/appflowy_tauri/src/appflowy_app/views/TrashPage.tsx b/frontend/appflowy_tauri/src/appflowy_app/views/TrashPage.tsx deleted file mode 100644 index 78baa9872d..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/views/TrashPage.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import Trash from '$app/components/trash/Trash'; - -function TrashPage() { - return ( -
- -
- ); -} - -export default TrashPage; diff --git a/frontend/appflowy_tauri/src/appflowy_app/vite-env.d.ts b/frontend/appflowy_tauri/src/appflowy_app/vite-env.d.ts deleted file mode 100644 index b1f45c7866..0000000000 --- a/frontend/appflowy_tauri/src/appflowy_app/vite-env.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -/// -/// diff --git a/frontend/appflowy_tauri/src/main.tsx b/frontend/appflowy_tauri/src/main.tsx deleted file mode 100644 index e53dc96c43..0000000000 --- a/frontend/appflowy_tauri/src/main.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import App from './appflowy_app/App'; -import './styles/tailwind.css'; -import './styles/font.css'; -import './styles/template.css'; - -ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(); diff --git a/frontend/appflowy_tauri/src/services/backend/index.ts b/frontend/appflowy_tauri/src/services/backend/index.ts deleted file mode 100644 index 3e02ff7183..0000000000 --- a/frontend/appflowy_tauri/src/services/backend/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export * from "./models/flowy-user"; -export * from "./models/flowy-database2"; -export * from "./models/flowy-folder"; -export * from "./models/flowy-document"; -export * from "./models/flowy-error"; -export * from "./models/flowy-config"; -export * from "./models/flowy-date"; -export * from "./models/flowy-search"; -export * from "./models/flowy-storage"; diff --git a/frontend/appflowy_tauri/src/styles/font.css b/frontend/appflowy_tauri/src/styles/font.css deleted file mode 100644 index 514b5a6e38..0000000000 --- a/frontend/appflowy_tauri/src/styles/font.css +++ /dev/null @@ -1,125 +0,0 @@ -@font-face { - font-family: 'Poppins'; - src: url('/google_fonts/Poppins/Poppins-Thin.ttf') format('truetype'); - font-weight: 100; - font-style: normal; -} - -@font-face { - font-family: 'Poppins'; - src: url('/google_fonts/Poppins/Poppins-ThinItalic.ttf') format('truetype'); - font-weight: 100; - font-style: italic; -} - -@font-face { - font-family: 'Poppins'; - src: url('/google_fonts/Poppins/Poppins-ExtraLight.ttf') format('truetype'); - font-weight: 200; - font-style: normal; -} - -@font-face { - font-family: 'Poppins'; - src: url('/google_fonts/Poppins/Poppins-ExtraLightItalic.ttf') format('truetype'); - font-weight: 200; - font-style: italic; -} - -@font-face { - font-family: 'Poppins'; - src: url('/google_fonts/Poppins/Poppins-Light.ttf') format('truetype'); - font-weight: 300; - font-style: normal; -} - -@font-face { - font-family: 'Poppins'; - src: url('/google_fonts/Poppins/Poppins-LightItalic.ttf') format('truetype'); - font-weight: 300; - font-style: italic; -} - -@font-face { - font-family: 'Poppins'; - src: url('/google_fonts/Poppins/Poppins-Regular.ttf') format('truetype'); - font-weight: 400; - font-style: normal; -} - -@font-face { - font-family: 'Poppins'; - src: url('/google_fonts/Poppins/Poppins-Italic.ttf') format('truetype'); - font-weight: 400; - font-style: italic; -} - -@font-face { - font-family: 'Poppins'; - src: url('/google_fonts/Poppins/Poppins-Medium.ttf') format('truetype'); - font-weight: 500; - font-style: normal; -} - -@font-face { - font-family: 'Poppins'; - src: url('/google_fonts/Poppins/Poppins-MediumItalic.ttf') format('truetype'); - font-weight: 500; - font-style: italic; -} - -@font-face { - font-family: 'Poppins'; - src: url('/google_fonts/Poppins/Poppins-SemiBold.ttf') format('truetype'); - font-weight: 600; - font-style: normal; -} - -@font-face { - font-family: 'Poppins'; - src: url('/google_fonts/Poppins/Poppins-SemiBoldItalic.ttf') format('truetype'); - font-weight: 600; - font-style: italic; -} - -@font-face { - font-family: 'Poppins'; - src: url('/google_fonts/Poppins/Poppins-Bold.ttf') format('truetype'); - font-weight: 700; - font-style: normal; -} - -@font-face { - font-family: 'Poppins'; - src: url('/google_fonts/Poppins/Poppins-BoldItalic.ttf') format('truetype'); - font-weight: 700; - font-style: italic; -} - -@font-face { - font-family: 'Poppins'; - src: url('/google_fonts/Poppins/Poppins-ExtraBold.ttf') format('truetype'); - font-weight: 800; - font-style: normal; -} - -@font-face { - font-family: 'Poppins'; - src: url('/google_fonts/Poppins/Poppins-ExtraBoldItalic.ttf') format('truetype'); - font-weight: 800; - font-style: italic; -} - -@font-face { - font-family: 'Poppins'; - src: url('/google_fonts/Poppins/Poppins-Black.ttf') format('truetype'); - font-weight: 900; - font-style: normal; -} - -@font-face { - font-family: 'Poppins'; - src: url('/google_fonts/Poppins/Poppins-BlackItalic.ttf') format('truetype'); - font-weight: 900; - font-style: italic; -} diff --git a/frontend/appflowy_tauri/src/styles/tailwind.css b/frontend/appflowy_tauri/src/styles/tailwind.css deleted file mode 100644 index b5c61c9567..0000000000 --- a/frontend/appflowy_tauri/src/styles/tailwind.css +++ /dev/null @@ -1,3 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; diff --git a/frontend/appflowy_tauri/src/styles/template.css b/frontend/appflowy_tauri/src/styles/template.css deleted file mode 100644 index 1bff6bdc76..0000000000 --- a/frontend/appflowy_tauri/src/styles/template.css +++ /dev/null @@ -1,60 +0,0 @@ -@import './variables/index.css'; - -* { - margin: 0; - padding: 0; -} - -/* stop body from scrolling */ -html, -body { - margin: 0; - height: 100%; - overflow: hidden; -} -[contenteditable] { - -webkit-tap-highlight-color: transparent; -} -input, -textarea { - outline: 0; - background: transparent; -} - -body { - font-family: Poppins, serif; -} - -::-webkit-scrollbar { - width: 8px; -} - - - -:root[data-dark-mode=true] body { - scrollbar-color: #fff var(--bg-body); -} - -body { - scrollbar-track-color: var(--bg-body); - scrollbar-shadow-color: var(--bg-body); -} - - -.btn { - @apply rounded-xl border border-line-divider px-4 py-3; -} - -.btn-primary { - @apply bg-fill-default text-text-title hover:bg-fill-list-hover; -} - -.input { - @apply rounded-xl border border-line-divider px-[18px] py-[14px] text-sm; -} - - -th { - @apply text-left font-normal; -} - diff --git a/frontend/appflowy_tauri/src/styles/variables/dark.variables.css b/frontend/appflowy_tauri/src/styles/variables/dark.variables.css deleted file mode 100644 index ca7544687b..0000000000 --- a/frontend/appflowy_tauri/src/styles/variables/dark.variables.css +++ /dev/null @@ -1,121 +0,0 @@ -/** -* Do not edit directly -* Generated on Tue, 19 Mar 2024 03:48:58 GMT -* Generated from $pnpm css:variables -*/ - -:root[data-dark-mode=true] { - --base-light-neutral-50: #f9fafd; - --base-light-neutral-100: #edeef2; - --base-light-neutral-200: #e2e4eb; - --base-light-neutral-300: #f2f2f2; - --base-light-neutral-400: #e0e0e0; - --base-light-neutral-500: #bdbdbd; - --base-light-neutral-600: #828282; - --base-light-neutral-700: #4f4f4f; - --base-light-neutral-800: #333333; - --base-light-neutral-900: #1f2329; - --base-light-neutral-1000: #000000; - --base-light-neutral-00: #ffffff; - --base-light-blue-50: #f2fcff; - --base-light-blue-100: #e0f8ff; - --base-light-blue-200: #a6ecff; - --base-light-blue-300: #52d1f4; - --base-light-blue-400: #00bcf0; - --base-light-blue-500: #05ade2; - --base-light-blue-600: #009fd1; - --base-light-color-deep-red: #fb006d; - --base-light-color-deep-yellow: #ffd667; - --base-light-color-deep-green: #66cf80; - --base-light-color-deep-blue: #00bcf0; - --base-light-color-light-purple: #e8e0ff; - --base-light-color-light-pink: #ffe7ee; - --base-light-color-light-orange: #ffefe3; - --base-light-color-light-yellow: #fff2cd; - --base-light-color-light-lime: #f5ffdc; - --base-light-color-light-green: #ddffd6; - --base-light-color-light-aqua: #defff1; - --base-light-color-light-blue: #e1fbff; - --base-light-color-light-red: #ffdddd; - --base-black-neutral-100: #252F41; - --base-black-neutral-200: #313c51; - --base-black-neutral-300: #3c4557; - --base-black-neutral-400: #525A69; - --base-black-neutral-500: #59647a; - --base-black-neutral-600: #87A0BF; - --base-black-neutral-700: #99a6b8; - --base-black-neutral-800: #e2e9f2; - --base-black-neutral-900: #eff4fb; - --base-black-neutral-1000: #ffffff; - --base-black-neutral-n50: #232b38; - --base-black-neutral-n00: #1a202c; - --base-black-blue-50: #232b38; - --base-black-blue-100: #005174; - --base-black-blue-200: #a6ecff; - --base-black-blue-300: #52d1f4; - --base-black-blue-400: #00bcf0; - --base-black-blue-500: #05ade2; - --base-black-blue-600: #009fd1; - --base-black-color-deep-red: #d32772; - --base-black-color-deep-yellow: #e9b320; - --base-black-color-deep-green: #3ba856; - --base-black-color-deep-blue: #2e9dbb; - --base-black-color-light-purple: #4D4078; - --base-black-color-light-blue: #2C3B58; - --base-black-color-light-green: #3C5133; - --base-black-color-light-yellow: #695E3E; - --base-black-color-light-pink: #5E3C5E; - --base-black-color-light-red: #56363F; - --base-black-color-light-aqua: #1B3849; - --base-black-color-light-lime: #394027; - --base-black-color-light-orange: #5E3C3C; - --base-else-brand: #2c144b; - --text-title: #e2e9f2; - --text-caption: #87A0BF; - --text-placeholder: #3c4557; - --text-link-default: #00bcf0; - --text-link-hover: #52d1f4; - --text-link-pressed: #009fd1; - --text-link-disabled: #005174; - --icon-primary: #e2e9f2; - --icon-secondary: #59647a; - --icon-disabled: #525A69; - --icon-on-toolbar: white; - --line-border: #59647a; - --line-divider: #252F41; - --line-on-toolbar: #99a6b8; - --fill-default: #00bcf0; - --fill-hover: #005174; - --fill-toolbar: #0F111C; - --fill-selector: #232b38; - --fill-list-active: #3c4557; - --fill-list-hover: #005174; - --content-blue-400: #00bcf0; - --content-blue-300: #52d1f4; - --content-blue-600: #009fd1; - --content-blue-100: #005174; - --content-on-fill: #1a202c; - --content-on-tag: #99a6b8; - --content-blue-50: #232b38; - --bg-body: #1a202c; - --bg-base: #232b38; - --bg-mask: rgba(0,0,0,0.7); - --bg-tips: #005174; - --bg-brand: #2c144b; - --function-error: #d32772; - --function-warning: #e9b320; - --function-success: #3ba856; - --function-info: #2e9dbb; - --tint-red: #56363F; - --tint-green: #3C5133; - --tint-purple: #4D4078; - --tint-blue: #2C3B58; - --tint-yellow: #695E3E; - --tint-pink: #5E3C5E; - --tint-lime: #394027; - --tint-aqua: #1B3849; - --tint-orange: #5E3C3C; - --shadow: 0px 0px 25px 0px rgba(0,0,0,0.3); - --scrollbar-track: #252F41; - --scrollbar-thumb: #3c4557; -} \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/styles/variables/index.css b/frontend/appflowy_tauri/src/styles/variables/index.css deleted file mode 100644 index 08d6a948f1..0000000000 --- a/frontend/appflowy_tauri/src/styles/variables/index.css +++ /dev/null @@ -1,7 +0,0 @@ -@import "./light.variables.css"; -@import "./dark.variables.css"; - -:root { - /* resize popover shadow */ - --shadow-resize-popover: 0px 5px 5px -3px rgba(0,0,0,0.2),0px 8px 10px 1px rgba(0,0,0,0.14),0px 3px 14px 2px rgba(0,0,0,0.12); -} \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/styles/variables/light.variables.css b/frontend/appflowy_tauri/src/styles/variables/light.variables.css deleted file mode 100644 index 26acc76f0a..0000000000 --- a/frontend/appflowy_tauri/src/styles/variables/light.variables.css +++ /dev/null @@ -1,124 +0,0 @@ -/** -* Do not edit directly -* Generated on Tue, 19 Mar 2024 03:48:58 GMT -* Generated from $pnpm css:variables -*/ - -:root { - --base-light-neutral-50: #f9fafd; - --base-light-neutral-100: #edeef2; - --base-light-neutral-200: #e2e4eb; - --base-light-neutral-300: #f2f2f2; - --base-light-neutral-400: #e0e0e0; - --base-light-neutral-500: #bdbdbd; - --base-light-neutral-600: #828282; - --base-light-neutral-700: #4f4f4f; - --base-light-neutral-800: #333333; - --base-light-neutral-900: #1f2329; - --base-light-neutral-1000: #000000; - --base-light-neutral-00: #ffffff; - --base-light-blue-50: #f2fcff; - --base-light-blue-100: #e0f8ff; - --base-light-blue-200: #a6ecff; - --base-light-blue-300: #52d1f4; - --base-light-blue-400: #00bcf0; - --base-light-blue-500: #05ade2; - --base-light-blue-600: #009fd1; - --base-light-color-deep-red: #fb006d; - --base-light-color-deep-yellow: #ffd667; - --base-light-color-deep-green: #66cf80; - --base-light-color-deep-blue: #00bcf0; - --base-light-color-light-purple: #e8e0ff; - --base-light-color-light-pink: #ffe7ee; - --base-light-color-light-orange: #ffefe3; - --base-light-color-light-yellow: #fff2cd; - --base-light-color-light-lime: #f5ffdc; - --base-light-color-light-green: #ddffd6; - --base-light-color-light-aqua: #defff1; - --base-light-color-light-blue: #e1fbff; - --base-light-color-light-red: #ffdddd; - --base-black-neutral-100: #252F41; - --base-black-neutral-200: #313c51; - --base-black-neutral-300: #3c4557; - --base-black-neutral-400: #525A69; - --base-black-neutral-500: #59647a; - --base-black-neutral-600: #87A0BF; - --base-black-neutral-700: #99a6b8; - --base-black-neutral-800: #e2e9f2; - --base-black-neutral-900: #eff4fb; - --base-black-neutral-1000: #ffffff; - --base-black-neutral-n50: #232b38; - --base-black-neutral-n00: #1a202c; - --base-black-blue-50: #232b38; - --base-black-blue-100: #005174; - --base-black-blue-200: #a6ecff; - --base-black-blue-300: #52d1f4; - --base-black-blue-400: #00bcf0; - --base-black-blue-500: #05ade2; - --base-black-blue-600: #009fd1; - --base-black-color-deep-red: #d32772; - --base-black-color-deep-yellow: #e9b320; - --base-black-color-deep-green: #3ba856; - --base-black-color-deep-blue: #2e9dbb; - --base-black-color-light-purple: #4D4078; - --base-black-color-light-blue: #2C3B58; - --base-black-color-light-green: #3C5133; - --base-black-color-light-yellow: #695E3E; - --base-black-color-light-pink: #5E3C5E; - --base-black-color-light-red: #56363F; - --base-black-color-light-aqua: #1B3849; - --base-black-color-light-lime: #394027; - --base-black-color-light-orange: #5E3C3C; - --base-else-brand: #2c144b; - --text-title: #333333; - --text-caption: #828282; - --text-placeholder: #bdbdbd; - --text-disabled: #e0e0e0; - --text-link-default: #00bcf0; - --text-link-hover: #52d1f4; - --text-link-pressed: #009fd1; - --text-link-disabled: #e0f8ff; - --icon-primary: #333333; - --icon-secondary: #59647a; - --icon-disabled: #e0e0e0; - --icon-on-toolbar: #ffffff; - --line-border: #bdbdbd; - --line-divider: #edeef2; - --line-on-toolbar: #4f4f4f; - --fill-toolbar: #333333; - --fill-default: #00bcf0; - --fill-hover: #52d1f4; - --fill-pressed: #009fd1; - --fill-active: #e0f8ff; - --fill-list-hover: #e0f8ff; - --fill-list-active: #edeef2; - --content-blue-400: #00bcf0; - --content-blue-300: #52d1f4; - --content-blue-600: #009fd1; - --content-blue-100: #e0f8ff; - --content-blue-50: #f2fcff; - --content-on-fill-hover: #00bcf0; - --content-on-fill: #ffffff; - --content-on-tag: #4f4f4f; - --bg-body: #ffffff; - --bg-base: #f9fafd; - --bg-mask: rgba(0,0,0,0.55); - --bg-tips: #e0f8ff; - --bg-brand: #2c144b; - --function-error: #fb006d; - --function-waring: #ffd667; - --function-success: #66cf80; - --function-info: #00bcf0; - --tint-purple: #e8e0ff; - --tint-pink: #ffe7ee; - --tint-red: #ffdddd; - --tint-lime: #f5ffdc; - --tint-green: #ddffd6; - --tint-aqua: #defff1; - --tint-blue: #e1fbff; - --tint-orange: #ffefe3; - --tint-yellow: #fff2cd; - --shadow: 0px 0px 10px 0px rgba(0,0,0,0.1); - --scrollbar-thumb: #bdbdbd; - --scrollbar-track: #edeef2; -} \ No newline at end of file diff --git a/frontend/appflowy_tauri/src/tests/helpers/init.ts b/frontend/appflowy_tauri/src/tests/helpers/init.ts deleted file mode 100644 index cb0ff5c3b5..0000000000 --- a/frontend/appflowy_tauri/src/tests/helpers/init.ts +++ /dev/null @@ -1 +0,0 @@ -export {}; diff --git a/frontend/appflowy_tauri/style-dictionary/config.cjs b/frontend/appflowy_tauri/style-dictionary/config.cjs deleted file mode 100644 index 10d7084060..0000000000 --- a/frontend/appflowy_tauri/style-dictionary/config.cjs +++ /dev/null @@ -1,114 +0,0 @@ -const StyleDictionary = require('style-dictionary'); -const fs = require('fs'); -const path = require('path'); - -// Add comment header to generated files -StyleDictionary.registerFormat({ - name: 'css/variables', - formatter: function(dictionary, config) { - const header = `/**\n` + '* Do not edit directly\n' + `* Generated on ${new Date().toUTCString()}\n` + `* Generated from $pnpm css:variables \n` + `*/\n\n`; - const allProperties = dictionary.allProperties; - const properties = allProperties.map(prop => { - const { name, value } = prop; - return ` --${name}: ${value};` - }).join('\n'); - // generate tailwind config - generateTailwindConfig(allProperties); - return header + `:root${this.selector} {\n${properties}\n}` - } -}); - -// expand shadow tokens into a single string -StyleDictionary.registerTransform({ - name: 'shadow/spreadShadow', - type: 'value', - matcher: function (prop) { - return prop.type === 'boxShadow'; - }, - transformer: function (prop) { - // destructure shadow values from original token value - const { x, y, blur, spread, color } = prop.original.value; - - return `${x}px ${y}px ${blur}px ${spread}px ${color}`; - }, -}); - -const transforms = ['attribute/cti', 'name/cti/kebab', 'shadow/spreadShadow']; - -// Generate Light CSS variables -StyleDictionary.extend({ - source: ['./style-dictionary/tokens/base.json', './style-dictionary/tokens/light.json'], - platforms: { - css: { - transformGroup: 'css', - buildPath: './src/styles/variables/', - files: [ - { - format: 'css/variables', - destination: 'light.variables.css', - selector: '', - options: { - outputReferences: true - } - }, - ], - transforms, - }, - }, -}).buildAllPlatforms(); - -// Generate Dark CSS variables -StyleDictionary.extend({ - source: ['./style-dictionary/tokens/base.json', './style-dictionary/tokens/dark.json'], - platforms: { - css: { - transformGroup: 'css', - buildPath: './src/styles/variables/', - files: [ - { - format: 'css/variables', - destination: 'dark.variables.css', - selector: '[data-dark-mode=true]', - }, - ], - transforms, - }, - }, -}).buildAllPlatforms(); - - -function set(obj, path, value) { - const lastKey = path.pop(); - const lastObj = path.reduce((obj, key) => - obj[key] = obj[key] || {}, - obj); - lastObj[lastKey] = value; -} - -function writeFile (file, data) { - const header = `/**\n` + '* Do not edit directly\n' + `* Generated on ${new Date().toUTCString()}\n` + `* Generated from $pnpm css:variables \n` + `*/\n\n`; - const exportString = `module.exports = ${JSON.stringify(data, null, 2)}`; - fs.writeFileSync(path.join(__dirname, file), header + exportString); -} - -function generateTailwindConfig(allProperties) { - const tailwindColors = {}; - const tailwindBoxShadow = {}; - allProperties.forEach(prop => { - const { path, type, name, value } = prop; - if (path[0] === 'Base') { - return; - } - if (type === 'color') { - if (name.includes('fill')) { - console.log(prop); - } - set(tailwindColors, path, `var(--${name})`); - } - if (type === 'boxShadow') { - set(tailwindBoxShadow, ['md'], `var(--${name})`); - } - }); - writeFile('./tailwind/colors.cjs', tailwindColors); - writeFile('./tailwind/box-shadow.cjs', tailwindBoxShadow); -} \ No newline at end of file diff --git a/frontend/appflowy_tauri/style-dictionary/tailwind/box-shadow.cjs b/frontend/appflowy_tauri/style-dictionary/tailwind/box-shadow.cjs deleted file mode 100644 index e9d8024320..0000000000 --- a/frontend/appflowy_tauri/style-dictionary/tailwind/box-shadow.cjs +++ /dev/null @@ -1,9 +0,0 @@ -/** -* Do not edit directly -* Generated on Tue, 19 Mar 2024 03:48:58 GMT -* Generated from $pnpm css:variables -*/ - -module.exports = { - "md": "var(--shadow)" -} \ No newline at end of file diff --git a/frontend/appflowy_tauri/style-dictionary/tailwind/colors.cjs b/frontend/appflowy_tauri/style-dictionary/tailwind/colors.cjs deleted file mode 100644 index bfa25fa56f..0000000000 --- a/frontend/appflowy_tauri/style-dictionary/tailwind/colors.cjs +++ /dev/null @@ -1,75 +0,0 @@ -/** -* Do not edit directly -* Generated on Tue, 19 Mar 2024 03:48:58 GMT -* Generated from $pnpm css:variables -*/ - -module.exports = { - "text": { - "title": "var(--text-title)", - "caption": "var(--text-caption)", - "placeholder": "var(--text-placeholder)", - "link-default": "var(--text-link-default)", - "link-hover": "var(--text-link-hover)", - "link-pressed": "var(--text-link-pressed)", - "link-disabled": "var(--text-link-disabled)" - }, - "icon": { - "primary": "var(--icon-primary)", - "secondary": "var(--icon-secondary)", - "disabled": "var(--icon-disabled)", - "on-toolbar": "var(--icon-on-toolbar)" - }, - "line": { - "border": "var(--line-border)", - "divider": "var(--line-divider)", - "on-toolbar": "var(--line-on-toolbar)" - }, - "fill": { - "default": "var(--fill-default)", - "hover": "var(--fill-hover)", - "toolbar": "var(--fill-toolbar)", - "selector": "var(--fill-selector)", - "list": { - "active": "var(--fill-list-active)", - "hover": "var(--fill-list-hover)" - } - }, - "content": { - "blue-400": "var(--content-blue-400)", - "blue-300": "var(--content-blue-300)", - "blue-600": "var(--content-blue-600)", - "blue-100": "var(--content-blue-100)", - "on-fill": "var(--content-on-fill)", - "on-tag": "var(--content-on-tag)", - "blue-50": "var(--content-blue-50)" - }, - "bg": { - "body": "var(--bg-body)", - "base": "var(--bg-base)", - "mask": "var(--bg-mask)", - "tips": "var(--bg-tips)", - "brand": "var(--bg-brand)" - }, - "function": { - "error": "var(--function-error)", - "warning": "var(--function-warning)", - "success": "var(--function-success)", - "info": "var(--function-info)" - }, - "tint": { - "red": "var(--tint-red)", - "green": "var(--tint-green)", - "purple": "var(--tint-purple)", - "blue": "var(--tint-blue)", - "yellow": "var(--tint-yellow)", - "pink": "var(--tint-pink)", - "lime": "var(--tint-lime)", - "aqua": "var(--tint-aqua)", - "orange": "var(--tint-orange)" - }, - "scrollbar": { - "track": "var(--scrollbar-track)", - "thumb": "var(--scrollbar-thumb)" - } -} \ No newline at end of file diff --git a/frontend/appflowy_tauri/style-dictionary/tokens/base.json b/frontend/appflowy_tauri/style-dictionary/tokens/base.json deleted file mode 100644 index fb58a867b1..0000000000 --- a/frontend/appflowy_tauri/style-dictionary/tokens/base.json +++ /dev/null @@ -1,290 +0,0 @@ -{ - "Base": { - "Light": { - "neutral": { - "50": { - "value": "#f9fafd", - "type": "color" - }, - "100": { - "value": "#dadbdd", - "type": "color" - }, - "200": { - "value": "#e2e4eb", - "type": "color" - }, - "300": { - "value": "#f2f2f2", - "type": "color" - }, - "400": { - "value": "#e0e0e0", - "type": "color" - }, - "500": { - "value": "#bdbdbd", - "type": "color" - }, - "600": { - "value": "#828282", - "type": "color" - }, - "700": { - "value": "#4f4f4f", - "type": "color" - }, - "800": { - "value": "#333333", - "type": "color" - }, - "900": { - "value": "#1f2329", - "type": "color" - }, - "1000": { - "value": "#000000", - "type": "color" - }, - "00": { - "value": "#ffffff", - "type": "color" - } - }, - "blue": { - "50": { - "value": "#f2fcff", - "type": "color" - }, - "100": { - "value": "#e0f8ff", - "type": "color" - }, - "200": { - "value": "#a6ecff", - "type": "color" - }, - "300": { - "value": "#52d1f4", - "type": "color" - }, - "400": { - "value": "#00bcf0", - "type": "color" - }, - "500": { - "value": "#05ade2", - "type": "color" - }, - "600": { - "value": "#009fd1", - "type": "color" - } - }, - "color": { - "deep": { - "red": { - "value": "#fb006d", - "type": "color" - }, - "yellow": { - "value": "#ffd667", - "type": "color" - }, - "green": { - "value": "#66cf80", - "type": "color" - }, - "blue": { - "value": "#00bcf0", - "type": "color" - } - }, - "light": { - "purple": { - "value": "#e8e0ff", - "type": "color" - }, - "pink": { - "value": "#ffe7ee", - "type": "color" - }, - "orange": { - "value": "#ffefe3", - "type": "color" - }, - "yellow": { - "value": "#fff2cd", - "type": "color" - }, - "lime": { - "value": "#f5ffdc", - "type": "color" - }, - "green": { - "value": "#ddffd6", - "type": "color" - }, - "aqua": { - "value": "#defff1", - "type": "color" - }, - "blue": { - "value": "#e1fbff", - "type": "color" - }, - "red": { - "value": "#ffdddd", - "type": "color" - } - } - } - }, - "black": { - "neutral": { - "100": { - "value": "#252F41", - "type": "color" - }, - "200": { - "value": "#313c51", - "type": "color" - }, - "300": { - "value": "#3c4557", - "type": "color" - }, - "400": { - "value": "#525A69", - "type": "color" - }, - "500": { - "value": "#59647a", - "type": "color" - }, - "600": { - "value": "#87A0BF", - "type": "color" - }, - "700": { - "value": "#99a6b8", - "type": "color" - }, - "800": { - "value": "#e2e9f2", - "type": "color" - }, - "900": { - "value": "#eff4fb", - "type": "color" - }, - "1000": { - "value": "#ffffff", - "type": "color" - }, - "N50": { - "value": "#232b38", - "type": "color" - }, - "N00": { - "value": "#1a202c", - "type": "color" - } - }, - "blue": { - "50": { - "value": "#232b38", - "type": "color" - }, - "100": { - "value": "#005174", - "type": "color" - }, - "200": { - "value": "#a6ecff", - "type": "color" - }, - "300": { - "value": "#52d1f4", - "type": "color" - }, - "400": { - "value": "#00bcf0", - "type": "color" - }, - "500": { - "value": "#05ade2", - "type": "color" - }, - "600": { - "value": "#009fd1", - "type": "color" - } - }, - "color": { - "deep": { - "red": { - "value": "#d32772", - "type": "color" - }, - "yellow": { - "value": "#e9b320", - "type": "color" - }, - "green": { - "value": "#3ba856", - "type": "color" - }, - "blue": { - "value": "#2e9dbb", - "type": "color" - } - }, - "light": { - "purple": { - "value": "#4D4078", - "type": "color" - }, - "blue": { - "value": "#2C3B58", - "type": "color" - }, - "green": { - "value": "#3C5133", - "type": "color" - }, - "yellow": { - "value": "#695E3E", - "type": "color" - }, - "pink": { - "value": "#5E3C5E", - "type": "color" - }, - "red": { - "value": "#56363F", - "type": "color" - }, - "aqua": { - "value": "#1B3849", - "type": "color" - }, - "lime": { - "value": "#394027", - "type": "color" - }, - "orange": { - "value": "#5E3C3C", - "type": "color" - } - } - } - }, - "else": { - "brand": { - "value": "#2c144b", - "type": "color" - } - } - } -} \ No newline at end of file diff --git a/frontend/appflowy_tauri/style-dictionary/tokens/dark.json b/frontend/appflowy_tauri/style-dictionary/tokens/dark.json deleted file mode 100644 index c67af7c9ec..0000000000 --- a/frontend/appflowy_tauri/style-dictionary/tokens/dark.json +++ /dev/null @@ -1,221 +0,0 @@ -{ - "text": { - "title": { - "value": "{Base.black.neutral.800}", - "type": "color" - }, - "caption": { - "value": "{Base.black.neutral.600}", - "type": "color" - }, - "placeholder": { - "value": "{Base.black.neutral.300}", - "type": "color" - }, - "link-default": { - "value": "{Base.black.blue.400}", - "type": "color" - }, - "link-hover": { - "value": "{Base.black.blue.300}", - "type": "color" - }, - "link-pressed": { - "value": "{Base.black.blue.600}", - "type": "color" - }, - "link-disabled": { - "value": "{Base.black.blue.100}", - "type": "color" - } - }, - "icon": { - "primary": { - "value": "{Base.black.neutral.800}", - "type": "color" - }, - "secondary": { - "value": "{Base.black.neutral.500}", - "type": "color" - }, - "disabled": { - "value": "{Base.black.neutral.400}", - "type": "color" - }, - "on-toolbar": { - "value": "white", - "type": "color" - } - }, - "line": { - "border": { - "value": "{Base.black.neutral.500}", - "type": "color" - }, - "divider": { - "value": "{Base.black.neutral.100}", - "type": "color" - }, - "on-toolbar": { - "value": "{Base.black.neutral.700}", - "type": "color" - } - }, - "fill": { - "default": { - "value": "{Base.black.blue.400}", - "type": "color" - }, - "hover": { - "value": "{Base.black.blue.100}", - "type": "color" - }, - "toolbar": { - "value": "#0F111C", - "type": "color" - }, - "selector": { - "value": "{Base.black.blue.50}", - "type": "color" - }, - "list": { - "active": { - "value": "{Base.black.neutral.300}", - "type": "color" - }, - "hover": { - "value": "{Base.black.blue.100}", - "type": "color" - } - } - }, - "content": { - "blue-400": { - "value": "{Base.black.blue.400}", - "type": "color" - }, - "blue-300": { - "value": "{Base.black.blue.300}", - "type": "color" - }, - "blue-600": { - "value": "{Base.black.blue.600}", - "type": "color" - }, - "blue-100": { - "value": "{Base.black.blue.100}", - "type": "color" - }, - "on-fill": { - "value": "{Base.black.neutral.N00}", - "type": "color" - }, - "on-tag": { - "value": "{Base.black.neutral.700}", - "type": "color" - }, - "blue-50": { - "value": "{Base.black.blue.50}", - "type": "color" - } - }, - "bg": { - "body": { - "value": "{Base.black.neutral.N00}", - "type": "color" - }, - "base": { - "value": "{Base.black.blue.50}", - "type": "color" - }, - "mask": { - "value": "rgba(0,0,0,0.7)", - "type": "color" - }, - "tips": { - "value": "{Base.black.blue.100}", - "type": "color" - }, - "brand": { - "value": "{Base.else.brand}", - "type": "color" - } - }, - "function": { - "error": { - "value": "{Base.black.color.deep.red}", - "type": "color" - }, - "warning": { - "value": "{Base.black.color.deep.yellow}", - "type": "color" - }, - "success": { - "value": "#3ba856", - "type": "color" - }, - "info": { - "value": "#2e9dbb", - "type": "color" - } - }, - "tint": { - "red": { - "value": "{Base.black.color.light.red}", - "type": "color" - }, - "green": { - "value": "{Base.black.color.light.green}", - "type": "color" - }, - "purple": { - "value": "{Base.black.color.light.purple}", - "type": "color" - }, - "blue": { - "value": "{Base.black.color.light.blue}", - "type": "color" - }, - "yellow": { - "value": "{Base.black.color.light.yellow}", - "type": "color" - }, - "pink": { - "value": "{Base.black.color.light.pink}", - "type": "color" - }, - "lime": { - "value": "{Base.black.color.light.lime}", - "type": "color" - }, - "aqua": { - "value": "{Base.black.color.light.aqua}", - "type": "color" - }, - "orange": { - "value": "{Base.black.color.light.orange}", - "type": "color" - } - }, - "shadow": { - "value": { - "x": "0", - "y": "0", - "blur": "25", - "spread": "0", - "color": "rgba(0,0,0,0.3)", - "type": "innerShadow" - }, - "type": "boxShadow" - }, - "scrollbar": { - "track": { - "value": "{Base.black.neutral.100}", - "type": "color" - }, - "thumb": { - "value": "{Base.black.neutral.300}", - "type": "color" - } - } -} \ No newline at end of file diff --git a/frontend/appflowy_tauri/style-dictionary/tokens/light.json b/frontend/appflowy_tauri/style-dictionary/tokens/light.json deleted file mode 100644 index 173f3d35aa..0000000000 --- a/frontend/appflowy_tauri/style-dictionary/tokens/light.json +++ /dev/null @@ -1,233 +0,0 @@ -{ - "text": { - "title": { - "value": "{Base.Light.neutral.800}", - "type": "color" - }, - "caption": { - "value": "{Base.Light.neutral.600}", - "type": "color" - }, - "placeholder": { - "value": "{Base.Light.neutral.500}", - "type": "color" - }, - "disabled": { - "value": "{Base.Light.neutral.400}", - "type": "color" - }, - "link-default": { - "value": "{Base.Light.blue.400}", - "type": "color" - }, - "link-hover": { - "value": "{Base.Light.blue.300}", - "type": "color" - }, - "link-pressed": { - "value": "{Base.Light.blue.600}", - "type": "color" - }, - "link-disabled": { - "value": "{Base.Light.blue.100}", - "type": "color" - } - }, - "icon": { - "primary": { - "value": "{Base.Light.neutral.800}", - "type": "color" - }, - "secondary": { - "value": "{Base.black.neutral.500}", - "type": "color" - }, - "disabled": { - "value": "{Base.Light.neutral.400}", - "type": "color" - }, - "on-toolbar": { - "value": "{Base.Light.neutral.00}", - "type": "color" - } - }, - "line": { - "border": { - "value": "{Base.Light.neutral.500}", - "type": "color" - }, - "divider": { - "value": "{Base.Light.neutral.100}", - "type": "color" - }, - "on-toolbar": { - "value": "{Base.Light.neutral.700}", - "type": "color" - } - }, - "fill": { - "toolbar": { - "value": "{Base.Light.neutral.800}", - "type": "color" - }, - "default": { - "value": "{Base.Light.blue.400}", - "type": "color" - }, - "hover": { - "value": "{Base.Light.blue.300}", - "type": "color" - }, - "pressed": { - "value": "{Base.Light.blue.600}", - "type": "color" - }, - "active": { - "value": "{Base.Light.blue.100}", - "type": "color" - }, - "list": { - "hover": { - "value": "{Base.Light.blue.100}", - "type": "color" - }, - "active": { - "value": "{Base.Light.neutral.100}", - "type": "color" - } - } - }, - "content": { - "blue-400": { - "value": "{Base.Light.blue.400}", - "type": "color" - }, - "blue-300": { - "value": "{Base.Light.blue.300}", - "type": "color" - }, - "blue-600": { - "value": "{Base.Light.blue.600}", - "type": "color" - }, - "blue-100": { - "value": "{Base.Light.blue.100}", - "type": "color" - }, - "blue-50": { - "value": "{Base.Light.blue.50}", - "type": "color" - }, - "on-fill-hover": { - "value": "{Base.Light.blue.400}", - "type": "color" - }, - "on-fill": { - "value": "{Base.Light.neutral.00}", - "type": "color" - }, - "on-tag": { - "value": "{Base.Light.neutral.700}", - "type": "color" - } - }, - "bg": { - "body": { - "value": "{Base.Light.neutral.00}", - "type": "color" - }, - "base": { - "value": "{Base.Light.neutral.50}", - "type": "color" - }, - "mask": { - "value": "rgba(0,0,0,0.55)", - "type": "color" - }, - "tips": { - "value": "{Base.Light.blue.100}", - "type": "color" - }, - "brand": { - "value": "{Base.else.brand}", - "type": "color" - } - }, - "function": { - "error": { - "value": "{Base.Light.color.deep.red}", - "type": "color" - }, - "waring": { - "value": "{Base.Light.color.deep.yellow}", - "type": "color" - }, - "success": { - "value": "{Base.Light.color.deep.green}", - "type": "color" - }, - "info": { - "value": "{Base.Light.color.deep.blue}", - "type": "color" - } - }, - "tint": { - "purple": { - "value": "{Base.Light.color.light.purple}", - "type": "color" - }, - "pink": { - "value": "{Base.Light.color.light.pink}", - "type": "color" - }, - "red": { - "value": "{Base.Light.color.light.red}", - "type": "color" - }, - "lime": { - "value": "{Base.Light.color.light.lime}", - "type": "color" - }, - "green": { - "value": "{Base.Light.color.light.green}", - "type": "color" - }, - "aqua": { - "value": "{Base.Light.color.light.aqua}", - "type": "color" - }, - "blue": { - "value": "{Base.Light.color.light.blue}", - "type": "color" - }, - "orange": { - "value": "{Base.Light.color.light.orange}", - "type": "color" - }, - "yellow": { - "value": "{Base.Light.color.light.yellow}", - "type": "color" - } - }, - "shadow": { - "value": { - "x": "0", - "y": "0", - "blur": "10", - "spread": "0", - "color": "rgba(0,0,0,0.1)", - "type": "dropShadow" - }, - "type": "boxShadow" - }, - "scrollbar": { - "thumb": { - "value": "{Base.Light.neutral.500}", - "type": "color" - }, - "track": { - "value": "{Base.Light.neutral.100}", - "type": "color" - } - } -} \ No newline at end of file diff --git a/frontend/appflowy_tauri/tailwind.config.cjs b/frontend/appflowy_tauri/tailwind.config.cjs deleted file mode 100644 index 06390d938f..0000000000 --- a/frontend/appflowy_tauri/tailwind.config.cjs +++ /dev/null @@ -1,20 +0,0 @@ -const colors = require('./style-dictionary/tailwind/colors.cjs'); -const boxShadow = require('./style-dictionary/tailwind/box-shadow.cjs'); - -/** @type {import('tailwindcss').Config} */ -module.exports = { - content: [ - './index.html', - './src/**/*.{js,ts,jsx,tsx}', - './node_modules/react-tailwindcss-datepicker/dist/index.esm.js', - ], - important: '#body', - darkMode: 'class', - theme: { - extend: { - colors, - boxShadow, - }, - }, - plugins: [], -}; diff --git a/frontend/appflowy_tauri/tsconfig.json b/frontend/appflowy_tauri/tsconfig.json deleted file mode 100644 index 63b15b6039..0000000000 --- a/frontend/appflowy_tauri/tsconfig.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "compilerOptions": { - "target": "ESNext", - "useDefineForClassFields": true, - "lib": ["DOM", "DOM.Iterable", "ESNext"], - "allowJs": false, - "skipLibCheck": true, - "esModuleInterop": false, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "ESNext", - "moduleResolution": "Node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "types": ["node", "jest"], - "baseUrl": "./", - "paths": { - "@/*": ["src/*"], - "$app/*": ["src/appflowy_app/*"], - "$app_reducers/*": ["src/appflowy_app/stores/reducers/*"], - "src/*": ["src/*"] - } - }, - "include": ["src", "vite.config.ts"], - "exclude": ["node_modules"], - "references": [{ "path": "./tsconfig.node.json" }] -} diff --git a/frontend/appflowy_tauri/tsconfig.node.json b/frontend/appflowy_tauri/tsconfig.node.json deleted file mode 100644 index 9d31e2aed9..0000000000 --- a/frontend/appflowy_tauri/tsconfig.node.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "compilerOptions": { - "composite": true, - "module": "ESNext", - "moduleResolution": "Node", - "allowSyntheticDefaultImports": true - }, - "include": ["vite.config.ts"] -} diff --git a/frontend/appflowy_tauri/vite.config.ts b/frontend/appflowy_tauri/vite.config.ts deleted file mode 100644 index b571cc40de..0000000000 --- a/frontend/appflowy_tauri/vite.config.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; -import svgr from 'vite-plugin-svgr'; - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [ - react(), - svgr({ - svgrOptions: { - prettier: false, - plugins: ['@svgr/plugin-svgo', '@svgr/plugin-jsx'], - icon: true, - svgoConfig: { - multipass: true, - plugins: [ - { - name: 'preset-default', - params: { - overrides: { - removeViewBox: false, - }, - }, - }, - ], - }, - svgProps: { - role: 'img', - }, - replaceAttrValues: { - '#333': 'currentColor', - }, - }, - }), - ], - - // Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build` - // prevent vite from obscuring rust errors - clearScreen: false, - // tauri expects a fixed port, fail if that port is not available - server: { - port: 1420, - strictPort: true, - watch: { - ignored: ['**/__tests__/**'], - }, - }, - // to make use of `TAURI_DEBUG` and other env variables - // https://tauri.studio/v1/api/config#buildconfig.beforedevcommand - envPrefix: ['VITE_', 'TAURI_'], - build: { - // Tauri supports es2021 - target: process.env.TAURI_PLATFORM === 'windows' ? 'chrome105' : 'safari13', - // don't minify for debug builds - minify: !process.env.TAURI_DEBUG ? 'esbuild' : false, - // produce sourcemaps for debug builds - sourcemap: !!process.env.TAURI_DEBUG, - }, - resolve: { - alias: [ - { find: 'src/', replacement: `${__dirname}/src/` }, - { find: '@/', replacement: `${__dirname}/src/` }, - { find: '$app/', replacement: `${__dirname}/src/appflowy_app/` }, - { find: '$app_reducers/', replacement: `${__dirname}/src/appflowy_app/stores/reducers/` }, - ], - }, - optimizeDeps: { - include: ['@mui/material/Tooltip'], - }, -}); diff --git a/frontend/appflowy_tauri/webdriver/selenium/package.json b/frontend/appflowy_tauri/webdriver/selenium/package.json deleted file mode 100644 index 78bbd20aad..0000000000 --- a/frontend/appflowy_tauri/webdriver/selenium/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "selenium", - "version": "1.0.0", - "private": true, - "scripts": { - "test": "mocha" - }, - "dependencies": { - "chai": "^4.3.4", - "mocha": "^9.0.3", - "selenium-webdriver": "^4.0.0-beta.4" - } -} diff --git a/frontend/appflowy_tauri/webdriver/selenium/test/test.cjs b/frontend/appflowy_tauri/webdriver/selenium/test/test.cjs deleted file mode 100644 index 7a57bdbbaf..0000000000 --- a/frontend/appflowy_tauri/webdriver/selenium/test/test.cjs +++ /dev/null @@ -1,76 +0,0 @@ -const os = require("os"); -const path = require("path"); -const { expect } = require("chai"); -const { spawn, spawnSync } = require("child_process"); -const { Builder, By, Capabilities, until } = require("selenium-webdriver"); -const { elementIsVisible, elementLocated } = require("selenium-webdriver/lib/until.js"); - -// create the path to the expected application binary -const application = path.resolve( - __dirname, - "..", - "..", - "..", - "src-tauri", - "target", - "release", - "appflowy_tauri" -); - -// keep track of the webdriver instance we create -let driver; - -// keep track of the tauri-driver process we start -let tauriDriver; - -before(async function() { - // set timeout to 2 minutes to allow the program to build if it needs to - this.timeout(120000); - - // ensure the program has been built - spawnSync("cargo", ["build", "--release"]); - - // start tauri-driver - tauriDriver = spawn( - path.resolve(os.homedir(), ".cargo", "bin", "tauri-driver"), - [], - { stdio: [null, process.stdout, process.stderr] } - ); - - const capabilities = new Capabilities(); - capabilities.set("tauri:options", { application }); - capabilities.setBrowserName("wry"); - - // start the webdriver client - driver = await new Builder() - .withCapabilities(capabilities) - .usingServer("http://localhost:4444/") - .build(); -}); - -after(async function() { - // stop the webdriver session - await driver.quit(); - - // kill the tauri-driver process - tauriDriver.kill(); -}); - -describe("AppFlowy Unit Test", () => { - it("should find get started button", async () => { - // should sign out if already sign in - const getStartedButton = await driver.wait(until.elementLocated(By.xpath("//*[@id=\"root\"]/form/div/div[3]"))); - getStartedButton.click(); - }); - - it("should get sign out button", async (done) => { - // const optionButton = await driver.wait(until.elementLocated(By.css('*[test-id=option-button]'))); - // const optionButton = await driver.wait(until.elementLocated(By.id('option-button'))); - // const optionButton = await driver.wait(until.elementLocated(By.css('[aria-label=option]'))); - - // Currently, only the find className is work - const optionButton = await driver.wait(until.elementLocated(By.className("relative h-8 w-8"))); - optionButton.click(); - await new Promise((resolve) => setTimeout(resolve, 4000)); - }); -}); diff --git a/frontend/appflowy_web_app/.eslintignore b/frontend/appflowy_web_app/.eslintignore deleted file mode 100644 index b921919753..0000000000 --- a/frontend/appflowy_web_app/.eslintignore +++ /dev/null @@ -1,10 +0,0 @@ -node_modules/ -dist/ -src-tauri/ -.eslintrc.cjs -tsconfig.json -**/backend/** -vite.config.ts -**/*.cy.tsx -*.config.ts -coverage/ \ No newline at end of file diff --git a/frontend/appflowy_web_app/.eslintignore.web b/frontend/appflowy_web_app/.eslintignore.web deleted file mode 100644 index 44dcf6dda2..0000000000 --- a/frontend/appflowy_web_app/.eslintignore.web +++ /dev/null @@ -1,8 +0,0 @@ -node_modules/ -dist/ -src-tauri/ -.eslintrc.cjs -tsconfig.json -src/application/services/tauri-services/ -vite.config.ts -coverage/ \ No newline at end of file diff --git a/frontend/appflowy_web_app/.eslintrc.cjs b/frontend/appflowy_web_app/.eslintrc.cjs deleted file mode 100644 index be02b0d022..0000000000 --- a/frontend/appflowy_web_app/.eslintrc.cjs +++ /dev/null @@ -1,73 +0,0 @@ -module.exports = { - // https://eslint.org/docs/latest/use/configure/configuration-files - env: { - browser: true, - es6: true, - node: true, - }, - extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], - parser: '@typescript-eslint/parser', - parserOptions: { - project: 'tsconfig.json', - sourceType: 'module', - tsconfigRootDir: __dirname, - extraFileExtensions: ['.json'], - }, - plugins: ['@typescript-eslint', 'react-hooks'], - rules: { - 'react-hooks/rules-of-hooks': 'error', - 'react-hooks/exhaustive-deps': 'error', - '@typescript-eslint/adjacent-overload-signatures': 'error', - '@typescript-eslint/no-empty-function': 'error', - '@typescript-eslint/no-empty-interface': 'error', - '@typescript-eslint/no-floating-promises': 'error', - '@typescript-eslint/await-thenable': 'error', - '@typescript-eslint/no-namespace': 'error', - '@typescript-eslint/no-unnecessary-type-assertion': 'error', - '@typescript-eslint/no-redeclare': 'error', - '@typescript-eslint/prefer-for-of': 'error', - '@typescript-eslint/triple-slash-reference': 'error', - '@typescript-eslint/unified-signatures': 'error', - 'no-shadow': 'off', - '@typescript-eslint/no-shadow': 'off', - 'constructor-super': 'error', - eqeqeq: ['error', 'always'], - 'no-cond-assign': 'error', - 'no-duplicate-case': 'error', - 'no-duplicate-imports': 'error', - 'no-empty': [ - 'error', - { - allowEmptyCatch: true, - }, - ], - 'no-invalid-this': 'error', - 'no-new-wrappers': 'error', - 'no-param-reassign': 'error', - 'no-sequences': 'error', - 'no-throw-literal': 'error', - 'no-unsafe-finally': 'error', - 'no-unused-labels': 'error', - 'no-var': 'error', - 'no-void': 'off', - 'prefer-const': 'error', - 'prefer-spread': 'off', - '@typescript-eslint/no-unused-vars': [ - 'error', - { - argsIgnorePattern: '^_', - }, - ], - 'padding-line-between-statements': [ - 'error', - { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' }, - { blankLine: 'any', prev: ['const', 'let', 'var'], next: ['const', 'let', 'var'] }, - { blankLine: 'always', prev: 'import', next: '*' }, - { blankLine: 'any', prev: 'import', next: 'import' }, - { blankLine: 'always', prev: 'block-like', next: '*' }, - { blankLine: 'always', prev: 'block', next: '*' }, - - ], - }, - ignorePatterns: ['src/**/*.test.ts', '**/__tests__/**/*.json', 'package.json', '__mocks__/*.ts'], -}; diff --git a/frontend/appflowy_web_app/.gitignore b/frontend/appflowy_web_app/.gitignore deleted file mode 100644 index 1b38f28edf..0000000000 --- a/frontend/appflowy_web_app/.gitignore +++ /dev/null @@ -1,37 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist/** -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? - -src/@types/translations/*.json - - -src/application/services/tauri-services/backend/models/ -src/application/services/tauri-services/backend/events/ - -.env - -coverage -.nyc_output - -cypress/snapshots/**/__diff_output__/ diff --git a/frontend/appflowy_web_app/.nycrc b/frontend/appflowy_web_app/.nycrc deleted file mode 100644 index dc571c1abb..0000000000 --- a/frontend/appflowy_web_app/.nycrc +++ /dev/null @@ -1,23 +0,0 @@ -{ - "all": true, - "extends": "@istanbuljs/nyc-config-babel", - "include": [ - "src/**/*.ts", - "src/**/*.tsx" - ], - "exclude": [ - "cypress/**/*.*", - "**/*.d.ts", - "**/*.cy.tsx", - "**/*.cy.ts" - ], - "reporter": [ - "text", - "html", - "text-summary", - "json", - "lcov" - ], - "temp-dir": "coverage/.nyc_output", - "report-dir": "coverage/cypress" -} \ No newline at end of file diff --git a/frontend/appflowy_web_app/.prettierrc.cjs b/frontend/appflowy_web_app/.prettierrc.cjs deleted file mode 100644 index f283db53a2..0000000000 --- a/frontend/appflowy_web_app/.prettierrc.cjs +++ /dev/null @@ -1,20 +0,0 @@ -module.exports = { - arrowParens: 'always', - bracketSpacing: true, - endOfLine: 'lf', - htmlWhitespaceSensitivity: 'css', - insertPragma: false, - jsxBracketSameLine: false, - jsxSingleQuote: true, - printWidth: 121, - plugins: [require('prettier-plugin-tailwindcss')], - proseWrap: 'preserve', - quoteProps: 'as-needed', - requirePragma: false, - semi: true, - singleQuote: true, - tabWidth: 2, - trailingComma: 'es5', - useTabs: false, - vueIndentScriptAndStyle: false, -}; diff --git a/frontend/appflowy_web_app/README.md b/frontend/appflowy_web_app/README.md deleted file mode 100644 index 30777f7abb..0000000000 --- a/frontend/appflowy_web_app/README.md +++ /dev/null @@ -1,163 +0,0 @@ -
-
-

AppFlowy Web

-
- - - - - -
- -## 🌟 Introduction - -Welcome to the AppFlowy Web project! This project aims to bring the powerful features of AppFlowy to the web. Whether -you're a developer looking to contribute or a user eager to try out the latest features, this guide will help you get -started. - -AppFlowy Web is built with the following technologies: - -- **React**: A JavaScript library for building user interfaces. -- **TypeScript**: A typed superset of JavaScript that compiles to plain JavaScript. -- **Bun**: A fast all-in-one JavaScript runtime. -- **Nginx**: A high-performance web server. -- **Docker**: A platform to develop, ship, and run applications in containers. - -### Resource Sharing - -To maintain consistency across different platforms, the Web project shares i18n translation files and Icons with the -Flutter project. This ensures a unified user experience and reduces duplication of effort in maintaining these -resources. - -- **i18n Translation Files**: The translation files are shared to provide a consistent localization experience across - both Web and Flutter applications. The path to the translation files is `frontend/resources/translations/`. - - > The translation files are stored in JSON format and contain translations for different languages. The files are - named according to the language code (e.g., `en.json` for English, `es.json` for Spanish, etc.). - -- **Icons**: The icon set used in the Web project is the same as the one used in the Flutter project, ensuring visual - consistency. The icons are stored in the `frontend/resources/flowy_icons/` directory. - -Let's dive in and get the project up and running! 🚀 - -## 🛠 Getting Started - -### Prerequisites - -Before you begin, make sure you have the following installed on your system: - -- [Node.js](https://nodejs.org/) (v18.6.0) 🌳 -- [pnpm](https://pnpm.io/) (package manager) 📦 -- [Jest](https://jestjs.io/) (testing framework) 🃏 -- [Cypress](https://www.cypress.io/) (end-to-end testing) 🧪 - -### Clone the Repository - -First, clone the repository to your local machine: - -```bash -git clone https://github.com/AppFlowy-IO/AppFlowy.git -cd frontend/appflowy_web_app -``` - -### Install Dependencies - -Install the required dependencies using pnpm: - -```bash -## ensure you have pnpm installed, if not run the following command -# npm install -g pnpm@8.5.0 - -pnpm install -``` - -### Configure Environment Variables - -Create a `.env` file in the root of the project and add the following environment variables: - -```bash -AF_BASE_URL=http://localhost:8080 -AF_GOTRUE_URL=http://localhost:9999 -AF_WS_URL=ws://localhost:8080/ws/v1 -``` - -### Start the Development Server - -To start the development server, run the following command: - -```bash -pnpm run dev -``` - -### 🚀 Building for Production(Optional) - -if you want to run the production build, use the following commands - -```bash -pnpm run build -pnpm run start -``` - -This will start the application in development mode. Open http://localhost:3000 to view it in the browser. - -## 🧪 Running Tests - -### Unit Tests - -We use **Jest** for running unit tests. To run the tests, use the following command: - -```bash -pnpm run test:unit -``` - -This will execute all the unit tests in the project and provide a summary of the results. ✅ - -### Components Tests - -We use **Cypress** for end-to-end testing. To run the Cypress tests, use the following command: - -```bash -pnpm run cypress:open -``` - -This will open the Cypress Test Runner where you can run your end-to-end tests. 🧪 - -Alternatively, to run Cypress tests in the headless mode, use: - -```bash -pnpm run test:components -``` - -Both commands will provide detailed test results and generate a code coverage report. - -## 🔄 Development Workflow - -### Linting - -To maintain code quality, we use **ESLint**. To run the linter and fix any linting errors, use the following command: - -```bash -pnpm run lint -``` - -## 🚀 Production Deployment - -Our production deployment process is automated using GitHub Actions. The process involves: - -1. **Setting up an AWS EC2 instance**: We use an EC2 instance to host the application. -2. **Installing Docker and Docker Compose**: Docker is installed on the AWS instance. -3. **Configuring SSH Access**: SSH access is set up with a user and password. -4. **Preparing Project Configuration**: We configure `Dockerfile`, `nginx.conf`, and `server.cjs` in the web project. -5. **Using GitHub Actions**: We use the easingthemes/ssh-deploy@main action to deploy the project to the remote server. - -The deployment steps include building the Docker image and running the Docker container with the necessary port -mappings: - -```bash -docker build -t appflowy-web-app . -docker rm -f appflowy-web-app || true -docker run -d -p 80:80 -p 443:443 --name appflowy-web-app appflowy-web-app -``` - -The Web server runs on Bun. For more details about Bun, please refer to the [Bun documentation](https://bun.sh/). - diff --git a/frontend/appflowy_web_app/__mocks__/nanoid.ts b/frontend/appflowy_web_app/__mocks__/nanoid.ts deleted file mode 100644 index f001b10d5a..0000000000 --- a/frontend/appflowy_web_app/__mocks__/nanoid.ts +++ /dev/null @@ -1,10 +0,0 @@ -const generateRandomId = (length: number): string => { - const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - let result = ''; - for (let i = 0; i < length; i++) { - result += characters.charAt(Math.floor(Math.random() * characters.length)); - } - return result; -}; - -export const nanoid = jest.fn(() => generateRandomId(8)); diff --git a/frontend/appflowy_web_app/cypress.config.ts b/frontend/appflowy_web_app/cypress.config.ts deleted file mode 100644 index f06cce3289..0000000000 --- a/frontend/appflowy_web_app/cypress.config.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { defineConfig } from 'cypress'; -import registerCodeCoverageTasks from '@cypress/code-coverage/task'; - -import { addMatchImageSnapshotPlugin } from 'cypress-image-snapshot/plugin'; - -export default defineConfig({ - env: { - codeCoverage: { - exclude: ['cypress/**/*.*', '**/__tests__/**/*.*', '**/*.test.*'], - }, - }, - watchForFileChanges: false, - component: { - devServer: { - framework: 'react', - bundler: 'vite', - }, - setupNodeEvents(on, config) { - registerCodeCoverageTasks(on, config); - addMatchImageSnapshotPlugin(on, config); - return config; - }, - supportFile: 'cypress/support/component.ts', - }, - chromeWebSecurity: false, - retries: { - // Configure retry attempts for `cypress run` - // Default is 0 - runMode: 10, - // Configure retry attempts for `cypress open` - // Default is 0 - openMode: 0, - }, -}); diff --git a/frontend/appflowy_web_app/cypress/fixtures/database/4c658817-20db-4f56-b7f9-0637a22dfeb6.json b/frontend/appflowy_web_app/cypress/fixtures/database/4c658817-20db-4f56-b7f9-0637a22dfeb6.json deleted file mode 100644 index f2dc3ef4fb..0000000000 --- a/frontend/appflowy_web_app/cypress/fixtures/database/4c658817-20db-4f56-b7f9-0637a22dfeb6.json +++ /dev/null @@ -1 +0,0 @@ -{"data":{"state_vector":[65,128,137,148,150,4,39,132,238,182,192,14,5,134,200,133,143,5,2,135,173,169,205,15,4,137,227,133,241,2,170,1,140,242,215,248,4,35,141,132,223,206,14,5,142,215,187,158,14,10,146,198,138,224,6,18,149,154,146,112,20,150,194,135,131,8,12,154,253,168,186,13,6,157,197,217,249,6,3,158,173,179,170,6,81,160,159,229,236,10,34,162,129,240,225,15,19,165,237,195,173,1,8,168,211,203,155,8,88,171,216,132,162,10,97,174,158,229,225,9,2,175,150,167,163,14,4,174,182,200,164,11,2,177,178,255,174,1,38,178,161,242,226,13,154,1,174,250,146,158,5,2,180,149,168,150,13,10,180,132,165,192,8,1,182,201,218,189,1,12,182,139,168,140,5,36,183,238,200,180,5,6,185,145,225,175,8,157,3,186,204,138,236,4,118,187,163,190,240,15,89,187,159,219,213,8,2,188,252,160,180,14,5,191,215,204,166,13,12,192,183,207,147,14,43,193,174,143,180,7,18,193,140,213,146,2,134,1,200,168,240,223,7,2,201,191,253,157,12,2,200,156,140,203,9,2,202,170,215,178,7,24,203,248,208,163,4,4,206,242,242,141,13,95,209,142,245,200,15,183,5,210,221,238,195,8,20,211,189,178,91,80,211,235,145,81,16,216,247,253,206,7,2,219,179,165,244,8,4,224,218,133,236,10,13,227,170,238,211,14,16,227,250,198,245,13,5,229,168,135,118,243,4,234,232,155,212,3,4,246,154,200,238,10,11,247,149,251,192,4,4,248,220,249,231,6,29,247,187,192,242,6,6,250,147,239,143,1,2,252,220,241,227,14,60,253,149,229,85,14,253,223,254,206,11,2,252,240,184,224,14,24],"doc_state":[65,79,187,163,190,240,15,0,39,0,137,227,133,241,2,3,36,101,52,49,48,55,52,55,98,45,53,102,50,102,45,52,53,97,48,45,98,50,102,55,45,56,57,48,97,100,51,48,48,49,51,53,53,1,40,0,187,163,190,240,15,0,2,105,100,1,119,36,101,52,49,48,55,52,55,98,45,53,102,50,102,45,52,53,97,48,45,98,50,102,55,45,56,57,48,97,100,51,48,48,49,51,53,53,40,0,187,163,190,240,15,0,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,52,99,54,53,56,56,49,55,45,50,48,100,98,45,52,102,53,54,45,98,55,102,57,45,48,54,51,55,97,50,50,100,102,101,98,54,40,0,187,163,190,240,15,0,4,110,97,109,101,1,119,5,66,111,97,114,100,40,0,187,163,190,240,15,0,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,0,0,0,0,33,0,187,163,190,240,15,0,11,109,111,100,105,102,105,101,100,95,97,116,1,39,0,187,163,190,240,15,0,15,108,97,121,111,117,116,95,115,101,116,116,105,110,103,115,1,39,0,187,163,190,240,15,6,1,49,1,40,0,187,163,190,240,15,7,21,104,105,100,101,95,117,110,103,114,111,117,112,101,100,95,99,111,108,117,109,110,1,121,40,0,187,163,190,240,15,7,22,99,111,108,108,97,112,115,101,95,104,105,100,100,101,110,95,103,114,111,117,112,115,1,121,40,0,187,163,190,240,15,0,6,108,97,121,111,117,116,1,122,0,0,0,0,0,0,0,1,39,0,187,163,190,240,15,0,14,102,105,101,108,100,95,115,101,116,116,105,110,103,115,1,39,0,187,163,190,240,15,0,7,102,105,108,116,101,114,115,0,39,0,187,163,190,240,15,0,6,103,114,111,117,112,115,0,39,0,187,163,190,240,15,0,5,115,111,114,116,115,0,39,0,187,163,190,240,15,0,12,102,105,101,108,100,95,111,114,100,101,114,115,0,8,0,187,163,190,240,15,15,4,118,1,2,105,100,119,6,89,53,52,81,73,115,118,1,2,105,100,119,6,70,114,115,115,74,100,118,1,2,105,100,119,6,89,80,102,105,50,109,118,1,2,105,100,119,6,84,102,117,121,104,84,39,0,187,163,190,240,15,0,10,114,111,119,95,111,114,100,101,114,115,0,8,0,187,163,190,240,15,20,3,118,2,2,105,100,119,36,50,102,57,52,52,50,50,48,45,57,102,52,53,45,52,48,100,57,45,57,54,98,53,45,101,56,99,48,56,56,56,100,97,102,55,99,6,104,101,105,103,104,116,125,60,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,49,48,52,55,102,50,100,48,45,51,55,53,55,45,52,55,57,57,45,98,99,102,50,45,101,56,102,57,55,52,54,52,100,50,98,53,118,2,2,105,100,119,36,100,100,54,99,56,100,49,51,45,52,56,54,55,45,52,49,99,54,45,56,53,57,57,45,98,56,56,56,51,53,48,102,53,50,101,101,6,104,101,105,103,104,116,125,60,161,187,163,190,240,15,5,1,7,0,187,163,190,240,15,13,1,33,0,187,163,190,240,15,25,8,102,105,101,108,100,95,105,100,1,33,0,187,163,190,240,15,25,2,116,121,1,33,0,187,163,190,240,15,25,7,99,111,110,116,101,110,116,1,33,0,187,163,190,240,15,25,2,105,100,1,33,0,187,163,190,240,15,25,6,103,114,111,117,112,115,1,161,187,163,190,240,15,24,1,161,187,163,190,240,15,30,1,0,1,161,187,163,190,240,15,26,1,161,187,163,190,240,15,29,1,161,187,163,190,240,15,27,1,161,187,163,190,240,15,28,1,39,0,137,227,133,241,2,3,36,50,49,52,51,101,57,53,100,45,53,100,99,98,45,52,101,48,102,45,98,98,50,99,45,53,48,57,52,52,101,54,101,48,49,57,102,1,40,0,187,163,190,240,15,38,2,105,100,1,119,36,50,49,52,51,101,57,53,100,45,53,100,99,98,45,52,101,48,102,45,98,98,50,99,45,53,48,57,52,52,101,54,101,48,49,57,102,40,0,187,163,190,240,15,38,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,52,99,54,53,56,56,49,55,45,50,48,100,98,45,52,102,53,54,45,98,55,102,57,45,48,54,51,55,97,50,50,100,102,101,98,54,40,0,187,163,190,240,15,38,4,110,97,109,101,1,119,8,67,97,108,101,110,100,97,114,40,0,187,163,190,240,15,38,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,0,0,0,0,33,0,187,163,190,240,15,38,11,109,111,100,105,102,105,101,100,95,97,116,1,39,0,187,163,190,240,15,38,15,108,97,121,111,117,116,95,115,101,116,116,105,110,103,115,1,39,0,187,163,190,240,15,44,1,50,1,40,0,187,163,190,240,15,45,8,102,105,101,108,100,95,105,100,1,119,6,106,87,101,95,116,54,40,0,187,163,190,240,15,45,13,115,104,111,119,95,119,101,101,107,101,110,100,115,1,120,40,0,187,163,190,240,15,45,17,115,104,111,119,95,119,101,101,107,95,110,117,109,98,101,114,115,1,120,40,0,187,163,190,240,15,45,17,102,105,114,115,116,95,100,97,121,95,111,102,95,119,101,101,107,1,122,0,0,0,0,0,0,0,0,40,0,187,163,190,240,15,45,9,108,97,121,111,117,116,95,116,121,1,122,0,0,0,0,0,0,0,0,40,0,187,163,190,240,15,38,6,108,97,121,111,117,116,1,122,0,0,0,0,0,0,0,2,39,0,187,163,190,240,15,38,14,102,105,101,108,100,95,115,101,116,116,105,110,103,115,1,39,0,187,163,190,240,15,38,7,102,105,108,116,101,114,115,0,39,0,187,163,190,240,15,38,6,103,114,111,117,112,115,0,39,0,187,163,190,240,15,38,5,115,111,114,116,115,0,39,0,187,163,190,240,15,38,12,102,105,101,108,100,95,111,114,100,101,114,115,0,8,0,187,163,190,240,15,56,4,118,1,2,105,100,119,6,89,53,52,81,73,115,118,1,2,105,100,119,6,70,114,115,115,74,100,118,1,2,105,100,119,6,89,80,102,105,50,109,118,1,2,105,100,119,6,84,102,117,121,104,84,39,0,187,163,190,240,15,38,10,114,111,119,95,111,114,100,101,114,115,0,8,0,187,163,190,240,15,61,3,118,2,2,105,100,119,36,50,102,57,52,52,50,50,48,45,57,102,52,53,45,52,48,100,57,45,57,54,98,53,45,101,56,99,48,56,56,56,100,97,102,55,99,6,104,101,105,103,104,116,125,60,118,2,2,105,100,119,36,49,48,52,55,102,50,100,48,45,51,55,53,55,45,52,55,57,57,45,98,99,102,50,45,101,56,102,57,55,52,54,52,100,50,98,53,6,104,101,105,103,104,116,125,60,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,100,100,54,99,56,100,49,51,45,52,56,54,55,45,52,49,99,54,45,56,53,57,57,45,98,56,56,56,51,53,48,102,53,50,101,101,161,187,163,190,240,15,31,1,136,187,163,190,240,15,19,1,118,1,2,105,100,119,6,106,87,101,95,116,54,39,0,187,163,190,240,15,11,6,106,87,101,95,116,54,1,40,0,187,163,190,240,15,67,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,161,137,227,133,241,2,67,1,136,137,227,133,241,2,68,1,118,1,2,105,100,119,6,106,87,101,95,116,54,39,0,137,227,133,241,2,43,6,106,87,101,95,116,54,1,40,0,187,163,190,240,15,71,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,161,187,163,190,240,15,43,1,136,187,163,190,240,15,60,1,118,1,2,105,100,119,6,106,87,101,95,116,54,39,0,187,163,190,240,15,52,6,106,87,101,95,116,54,1,40,0,187,163,190,240,15,75,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,39,0,137,227,133,241,2,2,6,106,87,101,95,116,54,1,40,0,187,163,190,240,15,77,2,105,100,1,119,6,106,87,101,95,116,54,40,0,187,163,190,240,15,77,4,110,97,109,101,1,119,4,68,97,116,101,40,0,187,163,190,240,15,77,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,48,178,115,33,0,187,163,190,240,15,77,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,40,0,187,163,190,240,15,77,10,105,115,95,112,114,105,109,97,114,121,1,121,33,0,187,163,190,240,15,77,2,116,121,1,39,0,187,163,190,240,15,77,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,187,163,190,240,15,84,1,50,1,40,0,187,163,190,240,15,85,11,116,105,109,101,95,102,111,114,109,97,116,1,122,0,0,0,0,0,0,0,1,40,0,187,163,190,240,15,85,11,116,105,109,101,122,111,110,101,95,105,100,1,119,0,40,0,187,163,190,240,15,85,11,100,97,116,101,95,102,111,114,109,97,116,1,122,0,0,0,0,0,0,0,3,1,162,129,240,225,15,0,161,219,179,165,244,8,3,19,1,135,173,169,205,15,0,161,175,150,167,163,14,3,4,155,5,209,142,245,200,15,0,161,229,168,135,118,214,4,1,161,229,168,135,118,202,4,1,161,209,142,245,200,15,0,1,161,229,168,135,118,204,4,1,161,229,168,135,118,216,4,1,161,229,168,135,118,218,4,1,161,229,168,135,118,217,4,1,161,229,168,135,118,215,4,1,161,209,142,245,200,15,2,1,161,209,142,245,200,15,4,1,161,209,142,245,200,15,7,1,161,209,142,245,200,15,5,1,161,209,142,245,200,15,6,1,161,209,142,245,200,15,8,1,161,209,142,245,200,15,9,1,161,209,142,245,200,15,11,1,161,209,142,245,200,15,12,1,161,209,142,245,200,15,10,1,161,209,142,245,200,15,13,1,161,209,142,245,200,15,1,1,161,209,142,245,200,15,18,1,161,209,142,245,200,15,3,1,161,209,142,245,200,15,15,1,161,209,142,245,200,15,17,1,161,209,142,245,200,15,14,1,161,209,142,245,200,15,16,1,161,209,142,245,200,15,20,1,161,209,142,245,200,15,24,1,161,209,142,245,200,15,25,1,161,209,142,245,200,15,22,1,161,209,142,245,200,15,23,1,161,209,142,245,200,15,26,1,161,209,142,245,200,15,29,1,161,209,142,245,200,15,28,1,161,209,142,245,200,15,27,1,161,209,142,245,200,15,30,1,161,209,142,245,200,15,31,1,161,209,142,245,200,15,19,1,161,209,142,245,200,15,36,1,161,209,142,245,200,15,21,1,161,209,142,245,200,15,32,1,161,209,142,245,200,15,33,1,161,209,142,245,200,15,34,1,161,209,142,245,200,15,35,1,161,209,142,245,200,15,38,1,161,209,142,245,200,15,43,1,161,209,142,245,200,15,42,1,161,209,142,245,200,15,41,1,161,209,142,245,200,15,40,1,161,209,142,245,200,15,44,1,161,209,142,245,200,15,47,1,161,209,142,245,200,15,48,1,161,209,142,245,200,15,45,1,161,209,142,245,200,15,46,1,161,209,142,245,200,15,49,1,168,209,142,245,200,15,37,1,119,4,84,101,120,116,161,209,142,245,200,15,54,1,168,209,142,245,200,15,39,1,122,0,0,0,0,0,0,0,1,161,209,142,245,200,15,53,1,161,209,142,245,200,15,52,1,161,209,142,245,200,15,50,1,161,209,142,245,200,15,51,1,161,209,142,245,200,15,56,1,161,209,142,245,200,15,60,1,161,209,142,245,200,15,58,1,161,209,142,245,200,15,61,1,161,209,142,245,200,15,59,1,168,209,142,245,200,15,62,1,122,0,0,0,0,102,65,132,91,168,209,142,245,200,15,65,1,122,0,0,0,0,0,0,0,36,168,209,142,245,200,15,64,1,122,0,0,0,0,0,0,0,0,168,209,142,245,200,15,63,1,119,0,168,209,142,245,200,15,66,1,119,0,161,168,211,203,155,8,0,1,161,137,227,133,241,2,18,1,161,209,142,245,200,15,72,1,161,137,227,133,241,2,22,1,161,168,211,203,155,8,1,1,161,209,142,245,200,15,74,1,161,209,142,245,200,15,76,1,161,209,142,245,200,15,77,1,161,209,142,245,200,15,78,1,161,182,201,218,189,1,4,1,161,177,178,255,174,1,2,1,0,3,161,177,178,255,174,1,7,1,161,177,178,255,174,1,6,1,161,177,178,255,174,1,1,1,161,177,178,255,174,1,5,1,161,209,142,245,200,15,79,1,161,209,142,245,200,15,73,1,161,209,142,245,200,15,90,1,161,209,142,245,200,15,75,1,161,209,142,245,200,15,80,1,161,209,142,245,200,15,92,1,161,209,142,245,200,15,94,1,161,209,142,245,200,15,95,1,161,209,142,245,200,15,96,1,161,209,142,245,200,15,97,1,161,209,142,245,200,15,91,1,161,209,142,245,200,15,99,1,161,209,142,245,200,15,93,1,161,209,142,245,200,15,98,1,161,209,142,245,200,15,101,1,161,209,142,245,200,15,103,1,161,209,142,245,200,15,104,1,161,209,142,245,200,15,105,1,161,209,142,245,200,15,106,1,161,209,142,245,200,15,100,1,161,209,142,245,200,15,108,1,161,209,142,245,200,15,102,1,161,209,142,245,200,15,107,1,161,209,142,245,200,15,110,1,161,209,142,245,200,15,112,1,161,209,142,245,200,15,113,1,161,209,142,245,200,15,114,1,161,209,142,245,200,15,115,1,161,209,142,245,200,15,109,1,161,209,142,245,200,15,117,1,161,209,142,245,200,15,111,1,161,209,142,245,200,15,116,1,161,209,142,245,200,15,119,1,161,209,142,245,200,15,121,1,161,209,142,245,200,15,122,1,161,209,142,245,200,15,123,1,161,209,142,245,200,15,81,1,168,209,142,245,200,15,89,1,119,6,70,114,115,115,74,100,168,209,142,245,200,15,87,1,119,0,168,209,142,245,200,15,88,1,119,8,103,58,95,51,55,82,110,115,168,209,142,245,200,15,86,1,122,0,0,0,0,0,0,0,3,167,209,142,245,200,15,82,0,8,0,209,142,245,200,15,131,1,4,118,2,7,118,105,115,105,98,108,101,120,2,105,100,119,6,70,114,115,115,74,100,118,2,2,105,100,119,4,120,90,48,51,7,118,105,115,105,98,108,101,120,118,2,2,105,100,119,36,48,52,102,52,55,48,51,55,45,49,56,54,97,45,52,56,55,102,45,98,54,56,101,45,102,49,98,102,97,48,102,101,54,54,53,101,7,118,105,115,105,98,108,101,120,118,2,7,118,105,115,105,98,108,101,120,2,105,100,119,36,54,49,50,100,50,99,51,98,45,56,50,98,99,45,52,55,51,98,45,98,49,52,53,45,55,102,53,55,49,56,54,101,51,102,55,101,161,209,142,245,200,15,124,1,161,209,142,245,200,15,118,1,161,209,142,245,200,15,136,1,1,161,209,142,245,200,15,120,1,161,209,142,245,200,15,125,1,161,209,142,245,200,15,138,1,1,161,209,142,245,200,15,140,1,1,161,209,142,245,200,15,141,1,1,161,209,142,245,200,15,142,1,1,161,209,142,245,200,15,143,1,1,161,209,142,245,200,15,137,1,1,161,209,142,245,200,15,145,1,1,161,209,142,245,200,15,139,1,1,161,209,142,245,200,15,144,1,1,161,209,142,245,200,15,147,1,1,161,209,142,245,200,15,149,1,1,161,209,142,245,200,15,150,1,1,161,209,142,245,200,15,151,1,1,161,209,142,245,200,15,152,1,1,161,209,142,245,200,15,146,1,1,161,209,142,245,200,15,154,1,1,161,209,142,245,200,15,148,1,1,161,209,142,245,200,15,153,1,1,161,209,142,245,200,15,156,1,1,161,209,142,245,200,15,158,1,1,161,209,142,245,200,15,159,1,1,161,209,142,245,200,15,160,1,1,161,209,142,245,200,15,161,1,1,168,209,142,245,200,15,155,1,1,119,4,84,121,112,101,161,209,142,245,200,15,163,1,1,168,209,142,245,200,15,157,1,1,122,0,0,0,0,0,0,0,3,161,209,142,245,200,15,162,1,1,161,209,142,245,200,15,165,1,1,161,209,142,245,200,15,167,1,1,168,209,142,245,200,15,168,1,1,122,0,0,0,0,102,65,140,43,168,209,142,245,200,15,169,1,1,119,227,1,123,34,111,112,116,105,111,110,115,34,58,91,123,34,105,100,34,58,34,120,90,48,51,34,44,34,110,97,109,101,34,58,34,55,55,34,44,34,99,111,108,111,114,34,58,34,80,117,114,112,108,101,34,125,44,123,34,105,100,34,58,34,48,52,102,52,55,48,51,55,45,49,56,54,97,45,52,56,55,102,45,98,54,56,101,45,102,49,98,102,97,48,102,101,54,54,53,101,34,44,34,110,97,109,101,34,58,34,57,57,57,34,44,34,99,111,108,111,114,34,58,34,80,105,110,107,34,125,44,123,34,105,100,34,58,34,54,49,50,100,50,99,51,98,45,56,50,98,99,45,52,55,51,98,45,98,49,52,53,45,55,102,53,55,49,56,54,101,51,102,55,101,34,44,34,110,97,109,101,34,58,34,49,48,48,48,34,44,34,99,111,108,111,114,34,58,34,80,105,110,107,34,125,93,44,34,100,105,115,97,98,108,101,95,99,111,108,111,114,34,58,102,97,108,115,101,125,161,137,227,133,241,2,162,1,1,161,137,227,133,241,2,160,1,1,161,209,142,245,200,15,172,1,1,161,137,227,133,241,2,164,1,1,39,0,137,227,133,241,2,165,1,1,52,1,33,0,209,142,245,200,15,176,1,7,99,111,110,116,101,110,116,1,161,209,142,245,200,15,174,1,1,40,0,137,227,133,241,2,166,1,7,99,111,110,116,101,110,116,1,119,36,123,34,111,112,116,105,111,110,115,34,58,91,93,44,34,100,105,115,97,98,108,101,95,99,111,108,111,114,34,58,102,97,108,115,101,125,161,182,201,218,189,1,6,1,161,209,142,245,200,15,126,1,161,209,142,245,200,15,178,1,1,161,209,142,245,200,15,177,1,1,161,209,142,245,200,15,182,1,1,161,209,142,245,200,15,173,1,1,161,209,142,245,200,15,184,1,1,161,209,142,245,200,15,175,1,1,161,209,142,245,200,15,183,1,1,161,209,142,245,200,15,186,1,1,161,209,142,245,200,15,188,1,1,161,209,142,245,200,15,189,1,1,161,209,142,245,200,15,190,1,1,161,209,142,245,200,15,191,1,1,161,209,142,245,200,15,185,1,1,161,209,142,245,200,15,193,1,1,161,209,142,245,200,15,187,1,1,161,209,142,245,200,15,192,1,1,161,209,142,245,200,15,195,1,1,161,209,142,245,200,15,197,1,1,161,209,142,245,200,15,198,1,1,161,209,142,245,200,15,199,1,1,161,209,142,245,200,15,200,1,1,161,209,142,245,200,15,194,1,1,161,209,142,245,200,15,202,1,1,161,209,142,245,200,15,196,1,1,161,209,142,245,200,15,201,1,1,161,209,142,245,200,15,204,1,1,161,209,142,245,200,15,206,1,1,161,209,142,245,200,15,207,1,1,161,209,142,245,200,15,208,1,1,161,209,142,245,200,15,209,1,1,161,209,142,245,200,15,203,1,1,161,209,142,245,200,15,211,1,1,161,209,142,245,200,15,205,1,1,161,209,142,245,200,15,210,1,1,161,209,142,245,200,15,213,1,1,161,209,142,245,200,15,215,1,1,161,209,142,245,200,15,216,1,1,161,209,142,245,200,15,217,1,1,161,209,142,245,200,15,218,1,1,161,209,142,245,200,15,212,1,1,161,209,142,245,200,15,220,1,1,161,209,142,245,200,15,214,1,1,161,209,142,245,200,15,219,1,1,161,209,142,245,200,15,222,1,1,161,209,142,245,200,15,224,1,1,161,209,142,245,200,15,225,1,1,161,209,142,245,200,15,226,1,1,161,209,142,245,200,15,227,1,1,161,209,142,245,200,15,221,1,1,161,209,142,245,200,15,229,1,1,161,209,142,245,200,15,223,1,1,161,209,142,245,200,15,228,1,1,161,209,142,245,200,15,231,1,1,161,209,142,245,200,15,233,1,1,161,209,142,245,200,15,234,1,1,161,209,142,245,200,15,235,1,1,161,209,142,245,200,15,236,1,1,161,209,142,245,200,15,230,1,1,161,209,142,245,200,15,238,1,1,161,209,142,245,200,15,232,1,1,161,209,142,245,200,15,237,1,1,161,209,142,245,200,15,240,1,1,161,209,142,245,200,15,242,1,1,161,209,142,245,200,15,243,1,1,161,209,142,245,200,15,244,1,1,161,209,142,245,200,15,245,1,1,161,209,142,245,200,15,239,1,1,161,209,142,245,200,15,247,1,1,161,209,142,245,200,15,241,1,1,161,209,142,245,200,15,246,1,1,161,209,142,245,200,15,249,1,1,161,209,142,245,200,15,251,1,1,161,209,142,245,200,15,252,1,1,161,209,142,245,200,15,253,1,1,161,209,142,245,200,15,254,1,1,161,209,142,245,200,15,248,1,1,161,209,142,245,200,15,128,2,1,161,209,142,245,200,15,250,1,1,161,209,142,245,200,15,255,1,1,161,209,142,245,200,15,130,2,1,161,209,142,245,200,15,132,2,1,161,209,142,245,200,15,133,2,1,161,209,142,245,200,15,134,2,1,161,209,142,245,200,15,135,2,1,161,209,142,245,200,15,129,2,1,161,209,142,245,200,15,137,2,1,161,209,142,245,200,15,131,2,1,161,209,142,245,200,15,136,2,1,161,209,142,245,200,15,139,2,1,161,209,142,245,200,15,141,2,1,161,209,142,245,200,15,142,2,1,161,209,142,245,200,15,143,2,1,161,209,142,245,200,15,144,2,1,161,209,142,245,200,15,138,2,1,161,209,142,245,200,15,146,2,1,161,209,142,245,200,15,140,2,1,161,209,142,245,200,15,145,2,1,161,209,142,245,200,15,148,2,1,161,209,142,245,200,15,150,2,1,161,209,142,245,200,15,151,2,1,161,209,142,245,200,15,152,2,1,161,209,142,245,200,15,153,2,1,161,209,142,245,200,15,147,2,1,161,209,142,245,200,15,155,2,1,161,209,142,245,200,15,149,2,1,161,209,142,245,200,15,154,2,1,161,209,142,245,200,15,157,2,1,161,209,142,245,200,15,159,2,1,161,209,142,245,200,15,160,2,1,161,209,142,245,200,15,161,2,1,161,209,142,245,200,15,162,2,1,161,209,142,245,200,15,156,2,1,161,209,142,245,200,15,164,2,1,161,209,142,245,200,15,158,2,1,161,209,142,245,200,15,163,2,1,161,209,142,245,200,15,166,2,1,161,209,142,245,200,15,168,2,1,161,209,142,245,200,15,169,2,1,161,209,142,245,200,15,170,2,1,161,209,142,245,200,15,171,2,1,161,209,142,245,200,15,165,2,1,161,209,142,245,200,15,173,2,1,161,209,142,245,200,15,167,2,1,161,209,142,245,200,15,172,2,1,161,209,142,245,200,15,175,2,1,161,209,142,245,200,15,177,2,1,161,209,142,245,200,15,178,2,1,161,209,142,245,200,15,179,2,1,161,209,142,245,200,15,180,2,1,161,209,142,245,200,15,174,2,1,161,209,142,245,200,15,182,2,1,161,209,142,245,200,15,176,2,1,161,209,142,245,200,15,181,2,1,161,209,142,245,200,15,184,2,1,161,209,142,245,200,15,186,2,1,161,209,142,245,200,15,187,2,1,161,209,142,245,200,15,188,2,1,161,209,142,245,200,15,189,2,1,161,209,142,245,200,15,183,2,1,161,209,142,245,200,15,191,2,1,161,209,142,245,200,15,185,2,1,161,209,142,245,200,15,190,2,1,161,209,142,245,200,15,193,2,1,161,209,142,245,200,15,195,2,1,161,209,142,245,200,15,196,2,1,161,209,142,245,200,15,197,2,1,161,209,142,245,200,15,198,2,1,161,209,142,245,200,15,192,2,1,161,209,142,245,200,15,200,2,1,161,209,142,245,200,15,194,2,1,161,209,142,245,200,15,199,2,1,161,209,142,245,200,15,202,2,1,161,209,142,245,200,15,204,2,1,161,209,142,245,200,15,205,2,1,161,209,142,245,200,15,206,2,1,161,209,142,245,200,15,207,2,1,161,209,142,245,200,15,201,2,1,161,209,142,245,200,15,209,2,1,161,209,142,245,200,15,203,2,1,161,209,142,245,200,15,208,2,1,161,209,142,245,200,15,211,2,1,161,209,142,245,200,15,213,2,1,161,209,142,245,200,15,214,2,1,161,209,142,245,200,15,215,2,1,161,209,142,245,200,15,216,2,1,161,209,142,245,200,15,210,2,1,161,209,142,245,200,15,218,2,1,161,209,142,245,200,15,212,2,1,161,209,142,245,200,15,217,2,1,161,209,142,245,200,15,220,2,1,161,209,142,245,200,15,222,2,1,161,209,142,245,200,15,223,2,1,161,209,142,245,200,15,224,2,1,161,209,142,245,200,15,225,2,1,161,209,142,245,200,15,219,2,1,161,209,142,245,200,15,227,2,1,161,209,142,245,200,15,221,2,1,161,209,142,245,200,15,226,2,1,161,209,142,245,200,15,229,2,1,161,209,142,245,200,15,231,2,1,161,209,142,245,200,15,232,2,1,161,209,142,245,200,15,233,2,1,161,209,142,245,200,15,234,2,1,161,209,142,245,200,15,228,2,1,161,209,142,245,200,15,236,2,1,161,209,142,245,200,15,230,2,1,161,209,142,245,200,15,235,2,1,161,209,142,245,200,15,238,2,1,161,209,142,245,200,15,240,2,1,161,209,142,245,200,15,241,2,1,161,209,142,245,200,15,242,2,1,161,209,142,245,200,15,243,2,1,161,209,142,245,200,15,237,2,1,161,209,142,245,200,15,245,2,1,161,209,142,245,200,15,239,2,1,161,209,142,245,200,15,244,2,1,161,209,142,245,200,15,247,2,1,161,209,142,245,200,15,249,2,1,161,209,142,245,200,15,250,2,1,161,209,142,245,200,15,251,2,1,161,209,142,245,200,15,252,2,1,161,209,142,245,200,15,246,2,1,161,209,142,245,200,15,254,2,1,161,209,142,245,200,15,248,2,1,161,209,142,245,200,15,253,2,1,161,209,142,245,200,15,128,3,1,161,209,142,245,200,15,130,3,1,161,209,142,245,200,15,131,3,1,161,209,142,245,200,15,132,3,1,161,209,142,245,200,15,133,3,1,161,209,142,245,200,15,255,2,1,161,209,142,245,200,15,135,3,1,161,209,142,245,200,15,129,3,1,161,209,142,245,200,15,134,3,1,161,209,142,245,200,15,137,3,1,161,209,142,245,200,15,139,3,1,161,209,142,245,200,15,140,3,1,161,209,142,245,200,15,141,3,1,161,209,142,245,200,15,142,3,1,161,209,142,245,200,15,136,3,1,161,209,142,245,200,15,144,3,1,161,209,142,245,200,15,138,3,1,161,209,142,245,200,15,143,3,1,161,209,142,245,200,15,146,3,1,161,209,142,245,200,15,148,3,1,161,209,142,245,200,15,149,3,1,161,209,142,245,200,15,150,3,1,161,209,142,245,200,15,151,3,1,161,209,142,245,200,15,145,3,1,161,209,142,245,200,15,153,3,1,168,209,142,245,200,15,147,3,1,122,0,0,0,0,0,0,0,4,161,209,142,245,200,15,152,3,1,161,209,142,245,200,15,155,3,1,161,209,142,245,200,15,157,3,1,161,209,142,245,200,15,158,3,1,168,209,142,245,200,15,159,3,1,119,205,5,123,34,111,112,116,105,111,110,115,34,58,91,123,34,105,100,34,58,34,50,100,54,48,51,48,99,51,45,57,55,49,101,45,52,100,52,53,45,98,53,55,48,45,100,101,57,50,102,100,101,97,100,97,101,54,34,44,34,110,97,109,101,34,58,34,103,104,106,116,117,105,107,34,44,34,99,111,108,111,114,34,58,34,71,114,101,101,110,34,125,44,123,34,105,100,34,58,34,102,99,100,54,101,102,56,99,45,56,99,100,54,45,52,49,98,51,45,57,50,52,53,45,57,57,56,57,51,49,100,52,57,97,49,54,34,44,34,110,97,109,101,34,58,34,103,104,106,34,44,34,99,111,108,111,114,34,58,34,80,105,110,107,34,125,44,123,34,105,100,34,58,34,49,99,52,102,53,52,54,57,45,54,101,49,49,45,52,55,48,51,45,57,48,56,54,45,101,98,98,50,51,57,49,53,100,53,100,56,34,44,34,110,97,109,101,34,58,34,111,111,111,34,44,34,99,111,108,111,114,34,58,34,76,105,109,101,34,125,44,123,34,105,100,34,58,34,57,100,48,48,56,50,51,97,45,100,57,101,50,45,52,102,98,55,45,98,100,98,54,45,99,97,102,54,101,98,99,54,99,49,50,51,34,44,34,110,97,109,101,34,58,34,104,106,107,34,44,34,99,111,108,111,114,34,58,34,76,105,103,104,116,80,105,110,107,34,125,44,123,34,105,100,34,58,34,48,52,48,102,98,48,98,102,45,50,101,100,97,45,52,99,97,51,45,56,54,99,97,45,53,98,57,49,98,55,48,50,102,101,49,54,34,44,34,110,97,109,101,34,58,34,110,106,107,34,44,34,99,111,108,111,114,34,58,34,80,117,114,112,108,101,34,125,44,123,34,105,100,34,58,34,52,49,57,50,51,51,57,51,45,102,55,99,51,45,52,50,51,53,45,98,54,49,51,45,102,57,97,101,56,52,102,102,53,56,56,57,34,44,34,110,97,109,101,34,58,34,107,107,107,34,44,34,99,111,108,111,114,34,58,34,66,108,117,101,34,125,44,123,34,105,100,34,58,34,56,51,51,50,99,52,56,51,45,102,56,57,99,45,52,48,53,55,45,57,101,99,57,45,101,50,53,53,56,54,53,48,52,52,51,56,34,44,34,110,97,109,101,34,58,34,98,110,109,34,44,34,99,111,108,111,114,34,58,34,89,101,108,108,111,119,34,125,44,123,34,105,100,34,58,34,52,53,53,98,100,49,56,51,45,54,54,57,102,45,52,98,49,55,45,56,99,56,57,45,56,102,56,53,48,102,102,50,48,51,54,52,34,44,34,110,97,109,101,34,58,34,118,110,109,34,44,34,99,111,108,111,114,34,58,34,79,114,97,110,103,101,34,125,44,123,34,105,100,34,58,34,57,97,102,51,49,102,100,53,45,98,54,53,52,45,52,54,54,54,45,98,101,101,57,45,101,50,52,55,49,51,55,50,53,49,102,53,34,44,34,110,97,109,101,34,58,34,106,106,109,34,44,34,99,111,108,111,114,34,58,34,65,113,117,97,34,125,93,44,34,100,105,115,97,98,108,101,95,99,111,108,111,114,34,58,102,97,108,115,101,125,161,209,142,245,200,15,160,3,1,161,209,142,245,200,15,154,3,1,161,209,142,245,200,15,162,3,1,161,209,142,245,200,15,163,3,1,161,209,142,245,200,15,164,3,1,161,209,142,245,200,15,165,3,1,161,209,142,245,200,15,166,3,1,161,209,142,245,200,15,167,3,1,161,209,142,245,200,15,168,3,1,161,209,142,245,200,15,169,3,1,161,209,142,245,200,15,170,3,1,161,209,142,245,200,15,171,3,1,161,209,142,245,200,15,172,3,1,161,209,142,245,200,15,173,3,1,161,209,142,245,200,15,174,3,1,161,209,142,245,200,15,175,3,1,161,209,142,245,200,15,176,3,1,161,209,142,245,200,15,177,3,1,168,209,142,245,200,15,178,3,1,122,0,0,0,0,102,65,147,48,168,209,142,245,200,15,179,3,1,119,12,109,117,108,116,105,32,115,101,108,101,99,116,161,209,142,245,200,15,181,1,1,136,187,163,190,240,15,66,1,118,1,2,105,100,119,6,55,75,88,95,99,120,39,0,187,163,190,240,15,11,6,55,75,88,95,99,120,1,40,0,209,142,245,200,15,184,3,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,161,182,201,218,189,1,10,1,136,168,211,203,155,8,63,1,118,1,2,105,100,119,6,55,75,88,95,99,120,39,0,137,227,133,241,2,92,6,55,75,88,95,99,120,1,40,0,209,142,245,200,15,188,3,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,161,182,201,218,189,1,8,1,136,168,211,203,155,8,71,1,118,1,2,105,100,119,6,55,75,88,95,99,120,39,0,168,211,203,155,8,18,6,55,75,88,95,99,120,1,40,0,209,142,245,200,15,192,3,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,161,209,142,245,200,15,180,1,1,136,187,163,190,240,15,70,1,118,1,2,105,100,119,6,55,75,88,95,99,120,39,0,137,227,133,241,2,43,6,55,75,88,95,99,120,1,40,0,209,142,245,200,15,196,3,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,161,182,201,218,189,1,2,1,136,168,211,203,155,8,67,1,118,1,2,105,100,119,6,55,75,88,95,99,120,39,0,137,227,133,241,2,133,1,6,55,75,88,95,99,120,1,40,0,209,142,245,200,15,200,3,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,161,182,201,218,189,1,0,1,136,187,163,190,240,15,74,1,118,1,2,105,100,119,6,55,75,88,95,99,120,39,0,187,163,190,240,15,52,6,55,75,88,95,99,120,1,40,0,209,142,245,200,15,204,3,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,39,0,137,227,133,241,2,2,6,55,75,88,95,99,120,1,40,0,209,142,245,200,15,206,3,2,105,100,1,119,6,55,75,88,95,99,120,33,0,209,142,245,200,15,206,3,4,110,97,109,101,1,40,0,209,142,245,200,15,206,3,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,67,23,60,33,0,209,142,245,200,15,206,3,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,40,0,209,142,245,200,15,206,3,10,105,115,95,112,114,105,109,97,114,121,1,121,33,0,209,142,245,200,15,206,3,2,116,121,1,39,0,209,142,245,200,15,206,3,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,209,142,245,200,15,213,3,1,57,1,33,0,209,142,245,200,15,214,3,11,100,97,116,101,95,102,111,114,109,97,116,1,33,0,209,142,245,200,15,214,3,12,105,110,99,108,117,100,101,95,116,105,109,101,1,33,0,209,142,245,200,15,214,3,10,102,105,101,108,100,95,116,121,112,101,1,33,0,209,142,245,200,15,214,3,11,116,105,109,101,95,102,111,114,109,97,116,1,161,209,142,245,200,15,182,3,1,136,209,142,245,200,15,183,3,1,118,1,2,105,100,119,6,76,99,121,68,75,106,39,0,187,163,190,240,15,11,6,76,99,121,68,75,106,1,40,0,209,142,245,200,15,221,3,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,161,209,142,245,200,15,186,3,1,136,209,142,245,200,15,187,3,1,118,1,2,105,100,119,6,76,99,121,68,75,106,39,0,137,227,133,241,2,92,6,76,99,121,68,75,106,1,40,0,209,142,245,200,15,225,3,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,161,209,142,245,200,15,190,3,1,136,209,142,245,200,15,191,3,1,118,1,2,105,100,119,6,76,99,121,68,75,106,39,0,168,211,203,155,8,18,6,76,99,121,68,75,106,1,40,0,209,142,245,200,15,229,3,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,161,209,142,245,200,15,194,3,1,136,209,142,245,200,15,195,3,1,118,1,2,105,100,119,6,76,99,121,68,75,106,39,0,137,227,133,241,2,43,6,76,99,121,68,75,106,1,40,0,209,142,245,200,15,233,3,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,161,209,142,245,200,15,198,3,1,136,209,142,245,200,15,199,3,1,118,1,2,105,100,119,6,76,99,121,68,75,106,39,0,137,227,133,241,2,133,1,6,76,99,121,68,75,106,1,40,0,209,142,245,200,15,237,3,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,161,209,142,245,200,15,202,3,1,136,209,142,245,200,15,203,3,1,118,1,2,105,100,119,6,76,99,121,68,75,106,39,0,187,163,190,240,15,52,6,76,99,121,68,75,106,1,40,0,209,142,245,200,15,241,3,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,39,0,137,227,133,241,2,2,6,76,99,121,68,75,106,1,40,0,209,142,245,200,15,243,3,2,105,100,1,119,6,76,99,121,68,75,106,40,0,209,142,245,200,15,243,3,4,110,97,109,101,1,119,13,76,97,115,116,32,109,111,100,105,102,105,101,100,40,0,209,142,245,200,15,243,3,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,67,23,66,40,0,209,142,245,200,15,243,3,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,67,23,66,40,0,209,142,245,200,15,243,3,10,105,115,95,112,114,105,109,97,114,121,1,121,40,0,209,142,245,200,15,243,3,2,116,121,1,122,0,0,0,0,0,0,0,8,39,0,209,142,245,200,15,243,3,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,209,142,245,200,15,250,3,1,56,1,40,0,209,142,245,200,15,251,3,11,116,105,109,101,95,102,111,114,109,97,116,1,122,0,0,0,0,0,0,0,0,40,0,209,142,245,200,15,251,3,11,100,97,116,101,95,102,111,114,109,97,116,1,122,0,0,0,0,0,0,0,0,40,0,209,142,245,200,15,251,3,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,0,40,0,209,142,245,200,15,251,3,12,105,110,99,108,117,100,101,95,116,105,109,101,1,121,161,209,142,245,200,15,210,3,1,161,209,142,245,200,15,208,3,1,161,209,142,245,200,15,128,4,1,161,209,142,245,200,15,212,3,1,161,209,142,245,200,15,217,3,1,161,209,142,245,200,15,215,3,1,161,209,142,245,200,15,216,3,1,161,209,142,245,200,15,218,3,1,161,209,142,245,200,15,130,4,1,161,209,142,245,200,15,132,4,1,161,209,142,245,200,15,134,4,1,161,209,142,245,200,15,135,4,1,161,209,142,245,200,15,133,4,1,161,209,142,245,200,15,136,4,1,161,209,142,245,200,15,140,4,1,161,209,142,245,200,15,138,4,1,161,209,142,245,200,15,139,4,1,161,209,142,245,200,15,137,4,1,161,209,142,245,200,15,141,4,1,161,209,142,245,200,15,129,4,1,161,209,142,245,200,15,146,4,1,161,209,142,245,200,15,131,4,1,161,209,142,245,200,15,143,4,1,161,209,142,245,200,15,145,4,1,161,209,142,245,200,15,144,4,1,161,209,142,245,200,15,142,4,1,161,209,142,245,200,15,148,4,1,161,209,142,245,200,15,153,4,1,161,209,142,245,200,15,152,4,1,161,209,142,245,200,15,150,4,1,161,209,142,245,200,15,151,4,1,161,209,142,245,200,15,154,4,1,161,209,142,245,200,15,155,4,1,161,209,142,245,200,15,157,4,1,161,209,142,245,200,15,156,4,1,161,209,142,245,200,15,158,4,1,161,209,142,245,200,15,159,4,1,161,209,142,245,200,15,147,4,1,161,209,142,245,200,15,164,4,1,161,209,142,245,200,15,149,4,1,161,209,142,245,200,15,163,4,1,161,209,142,245,200,15,162,4,1,161,209,142,245,200,15,160,4,1,161,209,142,245,200,15,161,4,1,161,209,142,245,200,15,166,4,1,161,209,142,245,200,15,171,4,1,161,209,142,245,200,15,170,4,1,161,209,142,245,200,15,168,4,1,161,209,142,245,200,15,169,4,1,161,209,142,245,200,15,172,4,1,161,209,142,245,200,15,176,4,1,161,209,142,245,200,15,174,4,1,161,209,142,245,200,15,175,4,1,161,209,142,245,200,15,173,4,1,161,209,142,245,200,15,177,4,1,168,209,142,245,200,15,165,4,1,119,10,67,114,101,97,116,101,100,32,97,116,161,209,142,245,200,15,182,4,1,168,209,142,245,200,15,167,4,1,122,0,0,0,0,0,0,0,9,161,209,142,245,200,15,181,4,1,161,209,142,245,200,15,180,4,1,161,209,142,245,200,15,178,4,1,161,209,142,245,200,15,179,4,1,161,209,142,245,200,15,184,4,1,161,209,142,245,200,15,186,4,1,161,209,142,245,200,15,189,4,1,161,209,142,245,200,15,188,4,1,161,209,142,245,200,15,187,4,1,168,209,142,245,200,15,190,4,1,122,0,0,0,0,102,67,41,222,168,209,142,245,200,15,192,4,1,122,0,0,0,0,0,0,0,4,168,209,142,245,200,15,191,4,1,121,168,209,142,245,200,15,194,4,1,122,0,0,0,0,0,0,0,0,168,209,142,245,200,15,193,4,1,122,0,0,0,0,0,0,0,0,161,209,142,245,200,15,219,3,1,136,209,142,245,200,15,220,3,1,118,1,2,105,100,119,6,120,69,81,65,111,75,39,0,187,163,190,240,15,11,6,120,69,81,65,111,75,1,40,0,209,142,245,200,15,202,4,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,161,209,142,245,200,15,223,3,1,136,209,142,245,200,15,224,3,1,118,1,2,105,100,119,6,120,69,81,65,111,75,39,0,137,227,133,241,2,92,6,120,69,81,65,111,75,1,40,0,209,142,245,200,15,206,4,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,161,209,142,245,200,15,227,3,1,136,209,142,245,200,15,228,3,1,118,1,2,105,100,119,6,120,69,81,65,111,75,39,0,168,211,203,155,8,18,6,120,69,81,65,111,75,1,40,0,209,142,245,200,15,210,4,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,161,209,142,245,200,15,231,3,1,136,209,142,245,200,15,232,3,1,118,1,2,105,100,119,6,120,69,81,65,111,75,39,0,137,227,133,241,2,43,6,120,69,81,65,111,75,1,40,0,209,142,245,200,15,214,4,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,161,209,142,245,200,15,235,3,1,136,209,142,245,200,15,236,3,1,118,1,2,105,100,119,6,120,69,81,65,111,75,39,0,137,227,133,241,2,133,1,6,120,69,81,65,111,75,1,40,0,209,142,245,200,15,218,4,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,161,209,142,245,200,15,239,3,1,136,209,142,245,200,15,240,3,1,118,1,2,105,100,119,6,120,69,81,65,111,75,39,0,187,163,190,240,15,52,6,120,69,81,65,111,75,1,40,0,209,142,245,200,15,222,4,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,39,0,137,227,133,241,2,2,6,120,69,81,65,111,75,1,40,0,209,142,245,200,15,224,4,2,105,100,1,119,6,120,69,81,65,111,75,40,0,209,142,245,200,15,224,4,4,110,97,109,101,1,119,9,67,104,101,99,107,108,105,115,116,40,0,209,142,245,200,15,224,4,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,67,49,249,40,0,209,142,245,200,15,224,4,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,67,49,249,40,0,209,142,245,200,15,224,4,10,105,115,95,112,114,105,109,97,114,121,1,121,40,0,209,142,245,200,15,224,4,2,116,121,1,122,0,0,0,0,0,0,0,7,39,0,209,142,245,200,15,224,4,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,209,142,245,200,15,231,4,1,55,1,161,227,250,198,245,13,0,1,1,0,137,227,133,241,2,58,1,0,3,161,209,142,245,200,15,233,4,1,129,209,142,245,200,15,234,4,1,0,3,161,209,142,245,200,15,238,4,1,0,3,161,209,142,245,200,15,243,4,1,0,3,161,209,142,245,200,15,247,4,3,129,209,142,245,200,15,239,4,1,0,3,161,209,142,245,200,15,253,4,1,129,209,142,245,200,15,254,4,1,0,3,161,209,142,245,200,15,130,5,1,129,209,142,245,200,15,131,5,1,0,3,161,209,142,245,200,15,135,5,4,129,209,142,245,200,15,136,5,1,0,3,161,146,198,138,224,6,15,1,136,182,201,218,189,1,5,1,118,2,2,105,100,119,36,52,98,53,54,48,99,50,100,45,51,102,51,57,45,52,48,56,54,45,97,97,51,100,45,99,50,53,57,48,100,49,50,57,56,53,48,6,104,101,105,103,104,116,125,60,161,209,142,245,200,15,204,4,1,136,182,201,218,189,1,11,1,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,52,98,53,54,48,99,50,100,45,51,102,51,57,45,52,48,56,54,45,97,97,51,100,45,99,50,53,57,48,100,49,50,57,56,53,48,161,209,142,245,200,15,208,4,1,136,182,201,218,189,1,9,1,118,2,2,105,100,119,36,52,98,53,54,48,99,50,100,45,51,102,51,57,45,52,48,56,54,45,97,97,51,100,45,99,50,53,57,48,100,49,50,57,56,53,48,6,104,101,105,103,104,116,125,60,161,209,142,245,200,15,143,5,1,136,182,201,218,189,1,7,1,118,2,2,105,100,119,36,52,98,53,54,48,99,50,100,45,51,102,51,57,45,52,48,56,54,45,97,97,51,100,45,99,50,53,57,48,100,49,50,57,56,53,48,6,104,101,105,103,104,116,125,60,161,209,142,245,200,15,216,4,1,136,182,201,218,189,1,3,1,118,2,2,105,100,119,36,52,98,53,54,48,99,50,100,45,51,102,51,57,45,52,48,56,54,45,97,97,51,100,45,99,50,53,57,48,100,49,50,57,56,53,48,6,104,101,105,103,104,116,125,60,161,209,142,245,200,15,220,4,1,136,182,201,218,189,1,1,1,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,52,98,53,54,48,99,50,100,45,51,102,51,57,45,52,48,56,54,45,97,97,51,100,45,99,50,53,57,48,100,49,50,57,56,53,48,161,209,142,245,200,15,154,5,1,161,177,178,255,174,1,12,1,161,177,178,255,174,1,11,1,161,177,178,255,174,1,14,1,161,177,178,255,174,1,13,1,161,209,142,245,200,15,160,5,1,161,177,178,255,174,1,24,1,161,177,178,255,174,1,23,1,161,177,178,255,174,1,25,1,161,177,178,255,174,1,22,1,161,209,142,245,200,15,165,5,1,168,227,250,198,245,13,2,1,119,6,89,80,102,105,50,109,168,227,250,198,245,13,3,1,119,1,56,168,227,250,198,245,13,4,1,119,6,80,78,49,51,122,82,168,227,250,198,245,13,1,1,122,0,0,0,0,0,0,0,5,161,209,142,245,200,15,170,5,1,168,177,178,255,174,1,34,1,119,6,117,106,117,122,75,103,168,177,178,255,174,1,37,1,119,1,54,168,177,178,255,174,1,35,1,122,0,0,0,0,0,0,0,7,168,177,178,255,174,1,36,1,119,6,115,111,118,85,116,69,161,209,142,245,200,15,175,5,3,25,252,220,241,227,14,0,161,227,250,198,245,13,0,1,1,0,137,227,133,241,2,58,1,0,3,161,252,220,241,227,14,0,1,129,252,220,241,227,14,1,1,0,3,161,252,220,241,227,14,5,3,129,252,220,241,227,14,6,1,0,3,161,252,220,241,227,14,12,1,129,252,220,241,227,14,13,1,0,3,161,252,220,241,227,14,17,1,1,0,137,227,133,241,2,56,1,0,6,161,252,220,241,227,14,22,1,129,252,220,241,227,14,23,1,0,6,129,252,220,241,227,14,31,1,0,6,161,252,220,241,227,14,30,1,129,252,220,241,227,14,38,1,0,6,129,252,220,241,227,14,46,1,0,6,1,252,240,184,224,14,0,161,246,154,200,238,10,10,24,1,227,170,238,211,14,0,161,135,173,169,205,15,3,16,1,141,132,223,206,14,0,161,132,238,182,192,14,4,5,1,132,238,182,192,14,0,161,203,248,208,163,4,3,5,5,188,252,160,180,14,0,161,185,145,225,175,8,235,2,1,135,209,142,245,200,15,144,5,1,40,0,188,252,160,180,14,1,8,102,105,101,108,100,95,105,100,1,119,6,89,53,52,81,73,115,40,0,188,252,160,180,14,1,9,99,111,110,100,105,116,105,111,110,1,122,0,0,0,0,0,0,0,0,40,0,188,252,160,180,14,1,2,105,100,1,119,8,115,58,104,97,52,74,106,113,1,175,150,167,163,14,0,161,247,187,192,242,6,5,4,2,142,215,187,158,14,0,161,149,154,146,112,15,6,161,142,215,187,158,14,5,4,1,192,183,207,147,14,0,161,182,139,168,140,5,35,43,5,227,250,198,245,13,0,161,146,198,138,224,6,14,1,161,177,178,255,174,1,29,1,161,177,178,255,174,1,31,1,161,177,178,255,174,1,30,1,161,177,178,255,174,1,28,1,49,178,161,242,226,13,0,161,171,216,132,162,10,92,1,129,185,145,225,175,8,150,3,1,0,6,129,178,161,242,226,13,1,1,0,6,129,178,161,242,226,13,8,1,0,6,129,178,161,242,226,13,15,1,0,6,129,178,161,242,226,13,22,1,0,6,129,178,161,242,226,13,29,1,0,6,161,178,161,242,226,13,0,1,129,178,161,242,226,13,36,1,0,6,129,178,161,242,226,13,44,1,0,6,129,178,161,242,226,13,51,1,0,6,129,178,161,242,226,13,58,1,0,6,129,178,161,242,226,13,65,1,0,6,161,178,161,242,226,13,43,1,129,178,161,242,226,13,72,1,0,6,129,178,161,242,226,13,80,1,0,6,129,178,161,242,226,13,87,1,0,6,129,178,161,242,226,13,94,1,0,6,161,178,161,242,226,13,79,1,129,178,161,242,226,13,101,1,0,6,129,178,161,242,226,13,109,1,0,6,129,178,161,242,226,13,116,1,0,6,161,178,161,242,226,13,108,1,129,178,161,242,226,13,123,1,0,6,129,178,161,242,226,13,131,1,1,0,6,161,178,161,242,226,13,130,1,1,129,178,161,242,226,13,138,1,1,0,6,168,178,161,242,226,13,145,1,1,122,0,0,0,0,102,77,81,51,1,154,253,168,186,13,0,161,162,129,240,225,15,18,6,1,191,215,204,166,13,0,161,210,221,238,195,8,19,12,2,180,149,168,150,13,0,161,165,237,195,173,1,7,1,161,142,215,187,158,14,9,9,1,206,242,242,141,13,0,161,180,132,165,192,8,0,95,1,201,191,253,157,12,0,161,187,159,219,213,8,1,2,1,253,223,254,206,11,0,161,174,158,229,225,9,1,2,1,174,182,200,164,11,0,161,210,221,238,195,8,19,2,1,246,154,200,238,10,0,161,192,183,207,147,14,42,11,1,160,159,229,236,10,0,161,193,174,143,180,7,17,34,1,224,218,133,236,10,0,161,247,149,251,192,4,3,13,76,171,216,132,162,10,0,39,0,137,227,133,241,2,3,36,97,53,53,54,54,101,52,57,45,102,49,53,54,45,52,49,54,56,45,57,98,50,100,45,49,55,57,50,54,99,53,100,97,51,50,57,1,40,0,171,216,132,162,10,0,2,105,100,1,119,36,97,53,53,54,54,101,52,57,45,102,49,53,54,45,52,49,54,56,45,57,98,50,100,45,49,55,57,50,54,99,53,100,97,51,50,57,40,0,171,216,132,162,10,0,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,52,99,54,53,56,56,49,55,45,50,48,100,98,45,52,102,53,54,45,98,55,102,57,45,48,54,51,55,97,50,50,100,102,101,98,54,40,0,171,216,132,162,10,0,4,110,97,109,101,1,119,14,66,111,97,114,100,32,99,104,101,99,107,98,111,120,40,0,171,216,132,162,10,0,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,0,0,0,0,33,0,171,216,132,162,10,0,11,109,111,100,105,102,105,101,100,95,97,116,1,39,0,171,216,132,162,10,0,15,108,97,121,111,117,116,95,115,101,116,116,105,110,103,115,1,39,0,171,216,132,162,10,6,1,49,1,40,0,171,216,132,162,10,7,22,99,111,108,108,97,112,115,101,95,104,105,100,100,101,110,95,103,114,111,117,112,115,1,121,40,0,171,216,132,162,10,7,21,104,105,100,101,95,117,110,103,114,111,117,112,101,100,95,99,111,108,117,109,110,1,121,40,0,171,216,132,162,10,0,6,108,97,121,111,117,116,1,122,0,0,0,0,0,0,0,1,39,0,171,216,132,162,10,0,14,102,105,101,108,100,95,115,101,116,116,105,110,103,115,1,39,0,171,216,132,162,10,0,7,102,105,108,116,101,114,115,0,39,0,171,216,132,162,10,0,6,103,114,111,117,112,115,0,39,0,171,216,132,162,10,0,5,115,111,114,116,115,0,39,0,171,216,132,162,10,0,12,102,105,101,108,100,95,111,114,100,101,114,115,0,8,0,171,216,132,162,10,15,12,118,1,2,105,100,119,6,89,53,52,81,73,115,118,1,2,105,100,119,6,70,114,115,115,74,100,118,1,2,105,100,119,6,89,80,102,105,50,109,118,1,2,105,100,119,6,84,102,117,121,104,84,118,1,2,105,100,119,6,115,111,118,85,116,69,118,1,2,105,100,119,6,54,76,70,72,66,54,118,1,2,105,100,119,6,86,89,52,50,103,49,118,1,2,105,100,119,6,106,87,101,95,116,54,118,1,2,105,100,119,6,55,75,88,95,99,120,118,1,2,105,100,119,6,76,99,121,68,75,106,118,1,2,105,100,119,6,120,69,81,65,111,75,118,1,2,105,100,119,6,52,57,85,69,86,53,39,0,171,216,132,162,10,0,10,114,111,119,95,111,114,100,101,114,115,0,8,0,171,216,132,162,10,28,8,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,50,102,57,52,52,50,50,48,45,57,102,52,53,45,52,48,100,57,45,57,54,98,53,45,101,56,99,48,56,56,56,100,97,102,55,99,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,49,48,52,55,102,50,100,48,45,51,55,53,55,45,52,55,57,57,45,98,99,102,50,45,101,56,102,57,55,52,54,52,100,50,98,53,118,2,2,105,100,119,36,100,100,54,99,56,100,49,51,45,52,56,54,55,45,52,49,99,54,45,56,53,57,57,45,98,56,56,56,51,53,48,102,53,50,101,101,6,104,101,105,103,104,116,125,60,118,2,2,105,100,119,36,49,99,98,57,49,102,97,50,45,54,51,56,100,45,52,48,100,54,45,97,55,99,52,45,51,57,52,102,48,100,56,98,49,57,49,51,6,104,101,105,103,104,116,125,60,118,2,2,105,100,119,36,51,49,56,97,97,52,49,53,45,57,50,97,101,45,52,56,57,97,45,97,49,52,102,45,97,50,52,54,57,50,97,50,101,102,97,54,6,104,101,105,103,104,116,125,60,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,51,99,99,100,49,55,101,48,45,100,55,56,98,45,52,52,101,50,45,97,102,100,49,45,49,98,102,55,99,99,52,57,99,98,53,54,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,48,49,54,48,101,53,56,55,45,52,49,102,52,45,52,51,57,49,45,97,98,98,51,45,100,51,50,50,98,53,50,51,101,100,98,50,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,52,98,53,54,48,99,50,100,45,51,102,51,57,45,52,48,56,54,45,97,97,51,100,45,99,50,53,57,48,100,49,50,57,56,53,48,161,171,216,132,162,10,5,1,7,0,171,216,132,162,10,13,1,33,0,171,216,132,162,10,38,6,103,114,111,117,112,115,1,33,0,171,216,132,162,10,38,2,116,121,1,33,0,171,216,132,162,10,38,7,99,111,110,116,101,110,116,1,33,0,171,216,132,162,10,38,8,102,105,101,108,100,95,105,100,1,33,0,171,216,132,162,10,38,2,105,100,1,161,171,216,132,162,10,37,1,168,171,216,132,162,10,41,1,119,0,167,171,216,132,162,10,39,0,8,0,171,216,132,162,10,46,4,118,2,2,105,100,119,6,70,114,115,115,74,100,7,118,105,115,105,98,108,101,120,118,2,2,105,100,119,4,120,90,48,51,7,118,105,115,105,98,108,101,120,118,2,2,105,100,119,36,48,52,102,52,55,48,51,55,45,49,56,54,97,45,52,56,55,102,45,98,54,56,101,45,102,49,98,102,97,48,102,101,54,54,53,101,7,118,105,115,105,98,108,101,120,118,2,7,118,105,115,105,98,108,101,120,2,105,100,119,36,54,49,50,100,50,99,51,98,45,56,50,98,99,45,52,55,51,98,45,98,49,52,53,45,55,102,53,55,49,56,54,101,51,102,55,101,168,171,216,132,162,10,40,1,122,0,0,0,0,0,0,0,3,168,171,216,132,162,10,42,1,119,6,70,114,115,115,74,100,168,171,216,132,162,10,43,1,119,8,103,58,102,104,55,54,48,95,161,185,145,225,175,8,189,2,1,136,209,142,245,200,15,151,5,1,118,2,2,105,100,119,36,51,97,97,100,99,99,52,49,45,52,98,52,100,45,52,53,55,48,45,97,53,100,101,45,48,54,101,98,101,51,102,52,54,48,101,99,6,104,101,105,103,104,116,125,60,161,185,145,225,175,8,233,2,1,136,209,142,245,200,15,149,5,1,118,2,2,105,100,119,36,51,97,97,100,99,99,52,49,45,52,98,52,100,45,52,53,55,48,45,97,53,100,101,45,48,54,101,98,101,51,102,52,54,48,101,99,6,104,101,105,103,104,116,125,60,161,171,216,132,162,10,44,1,136,171,216,132,162,10,36,1,118,2,2,105,100,119,36,51,97,97,100,99,99,52,49,45,52,98,52,100,45,52,53,55,48,45,97,53,100,101,45,48,54,101,98,101,51,102,52,54,48,101,99,6,104,101,105,103,104,116,125,60,161,188,252,160,180,14,0,1,136,209,142,245,200,15,155,5,1,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,51,97,97,100,99,99,52,49,45,52,98,52,100,45,52,53,55,48,45,97,53,100,101,45,48,54,101,98,101,51,102,52,54,48,101,99,161,185,145,225,175,8,234,2,1,136,209,142,245,200,15,159,5,1,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,51,97,97,100,99,99,52,49,45,52,98,52,100,45,52,53,55,48,45,97,53,100,101,45,48,54,101,98,101,51,102,52,54,48,101,99,161,185,145,225,175,8,197,2,1,136,209,142,245,200,15,157,5,1,118,2,2,105,100,119,36,51,97,97,100,99,99,52,49,45,52,98,52,100,45,52,53,55,48,45,97,53,100,101,45,48,54,101,98,101,51,102,52,54,48,101,99,6,104,101,105,103,104,116,125,60,161,185,145,225,175,8,181,2,1,136,209,142,245,200,15,153,5,1,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,51,97,97,100,99,99,52,49,45,52,98,52,100,45,52,53,55,48,45,97,53,100,101,45,48,54,101,98,101,51,102,52,54,48,101,99,161,171,216,132,162,10,60,1,161,209,142,245,200,15,167,5,1,161,209,142,245,200,15,169,5,1,161,209,142,245,200,15,166,5,1,161,209,142,245,200,15,168,5,1,168,171,216,132,162,10,54,1,122,0,0,0,0,102,75,60,209,136,171,216,132,162,10,55,1,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,56,56,102,97,51,54,98,50,45,54,100,55,50,45,52,52,100,101,45,98,48,100,102,45,100,51,98,50,101,54,100,55,52,52,100,54,168,171,216,132,162,10,56,1,122,0,0,0,0,102,75,60,209,136,171,216,132,162,10,57,1,118,2,2,105,100,119,36,56,56,102,97,51,54,98,50,45,54,100,55,50,45,52,52,100,101,45,98,48,100,102,45,100,51,98,50,101,54,100,55,52,52,100,54,6,104,101,105,103,104,116,125,60,168,171,216,132,162,10,58,1,122,0,0,0,0,102,75,60,209,136,171,216,132,162,10,59,1,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,56,56,102,97,51,54,98,50,45,54,100,55,50,45,52,52,100,101,45,98,48,100,102,45,100,51,98,50,101,54,100,55,52,52,100,54,161,171,216,132,162,10,68,1,136,171,216,132,162,10,61,1,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,56,56,102,97,51,54,98,50,45,54,100,55,50,45,52,52,100,101,45,98,48,100,102,45,100,51,98,50,101,54,100,55,52,52,100,54,168,171,216,132,162,10,62,1,122,0,0,0,0,102,75,60,209,136,171,216,132,162,10,63,1,118,2,2,105,100,119,36,56,56,102,97,51,54,98,50,45,54,100,55,50,45,52,52,100,101,45,98,48,100,102,45,100,51,98,50,101,54,100,55,52,52,100,54,6,104,101,105,103,104,116,125,60,168,171,216,132,162,10,64,1,122,0,0,0,0,102,75,60,209,136,171,216,132,162,10,65,1,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,56,56,102,97,51,54,98,50,45,54,100,55,50,45,52,52,100,101,45,98,48,100,102,45,100,51,98,50,101,54,100,55,52,52,100,54,168,171,216,132,162,10,66,1,122,0,0,0,0,102,75,60,209,136,171,216,132,162,10,67,1,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,56,56,102,97,51,54,98,50,45,54,100,55,50,45,52,52,100,101,45,98,48,100,102,45,100,51,98,50,101,54,100,55,52,52,100,54,161,171,216,132,162,10,79,1,168,171,216,132,162,10,70,1,119,1,57,168,171,216,132,162,10,72,1,119,6,70,114,115,115,74,100,168,171,216,132,162,10,71,1,122,0,0,0,0,0,0,0,7,168,171,216,132,162,10,69,1,119,6,67,101,97,68,98,122,161,171,216,132,162,10,87,1,168,209,142,245,200,15,161,5,1,119,6,121,81,77,51,67,56,168,209,142,245,200,15,164,5,1,119,6,89,53,52,81,73,115,168,209,142,245,200,15,163,5,1,119,2,49,48,168,209,142,245,200,15,162,5,1,122,0,0,0,0,0,0,0,5,1,174,158,229,225,9,0,161,227,170,238,211,14,15,2,1,200,156,140,203,9,0,161,250,147,239,143,1,1,2,1,219,179,165,244,8,0,161,140,242,215,248,4,34,4,1,187,159,219,213,8,0,161,200,168,240,223,7,1,2,1,210,221,238,195,8,0,161,211,235,145,81,15,20,1,180,132,165,192,8,0,161,211,189,178,91,79,1,163,1,185,145,225,175,8,0,161,252,220,241,227,14,45,1,129,252,220,241,227,14,53,1,0,6,129,185,145,225,175,8,1,1,0,6,129,185,145,225,175,8,8,1,0,6,161,185,145,225,175,8,0,1,129,185,145,225,175,8,15,1,0,6,129,185,145,225,175,8,23,1,0,6,129,185,145,225,175,8,30,1,0,6,129,185,145,225,175,8,37,1,0,6,161,185,145,225,175,8,22,1,129,185,145,225,175,8,44,1,0,6,129,185,145,225,175,8,52,1,0,6,129,185,145,225,175,8,59,1,0,6,129,185,145,225,175,8,66,1,0,6,129,185,145,225,175,8,73,1,0,6,161,185,145,225,175,8,51,1,129,185,145,225,175,8,80,1,0,6,129,185,145,225,175,8,88,1,0,6,129,185,145,225,175,8,95,1,0,6,129,185,145,225,175,8,102,1,0,6,129,185,145,225,175,8,109,1,0,6,129,185,145,225,175,8,116,1,0,6,161,209,142,245,200,15,182,5,1,129,185,145,225,175,8,123,1,0,6,129,185,145,225,175,8,131,1,1,0,6,129,185,145,225,175,8,138,1,1,0,6,129,185,145,225,175,8,145,1,1,0,6,129,185,145,225,175,8,152,1,1,0,6,129,185,145,225,175,8,159,1,1,0,6,161,185,145,225,175,8,130,1,1,129,185,145,225,175,8,166,1,1,0,6,129,185,145,225,175,8,174,1,1,0,6,129,185,145,225,175,8,181,1,1,0,6,129,185,145,225,175,8,188,1,1,0,6,129,185,145,225,175,8,195,1,1,0,6,129,185,145,225,175,8,202,1,1,0,6,161,185,145,225,175,8,173,1,1,129,185,145,225,175,8,209,1,1,0,6,129,185,145,225,175,8,217,1,1,0,6,129,185,145,225,175,8,224,1,1,0,6,129,185,145,225,175,8,231,1,1,0,6,129,185,145,225,175,8,238,1,1,0,6,129,185,145,225,175,8,245,1,1,0,6,161,185,145,225,175,8,216,1,1,129,185,145,225,175,8,252,1,1,0,6,129,185,145,225,175,8,132,2,1,0,6,129,185,145,225,175,8,139,2,1,0,6,129,185,145,225,175,8,146,2,1,0,6,129,185,145,225,175,8,153,2,1,0,6,129,185,145,225,175,8,160,2,1,0,6,129,185,145,225,175,8,167,2,1,0,6,161,209,142,245,200,15,152,5,1,136,209,142,245,200,15,209,4,1,118,1,2,105,100,119,6,52,57,85,69,86,53,39,0,168,211,203,155,8,18,6,52,57,85,69,86,53,1,40,0,185,145,225,175,8,183,2,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,161,185,145,225,175,8,131,2,1,136,209,142,245,200,15,213,4,1,118,1,2,105,100,119,6,52,57,85,69,86,53,39,0,137,227,133,241,2,43,6,52,57,85,69,86,53,1,40,0,185,145,225,175,8,187,2,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,161,209,142,245,200,15,150,5,1,136,209,142,245,200,15,205,4,1,118,1,2,105,100,119,6,52,57,85,69,86,53,39,0,137,227,133,241,2,92,6,52,57,85,69,86,53,1,40,0,185,145,225,175,8,191,2,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,161,209,142,245,200,15,148,5,1,136,209,142,245,200,15,201,4,1,118,1,2,105,100,119,6,52,57,85,69,86,53,39,0,187,163,190,240,15,11,6,52,57,85,69,86,53,1,40,0,185,145,225,175,8,195,2,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,161,209,142,245,200,15,156,5,1,136,209,142,245,200,15,217,4,1,118,1,2,105,100,119,6,52,57,85,69,86,53,39,0,137,227,133,241,2,133,1,6,52,57,85,69,86,53,1,40,0,185,145,225,175,8,199,2,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,161,209,142,245,200,15,158,5,1,136,209,142,245,200,15,221,4,1,118,1,2,105,100,119,6,52,57,85,69,86,53,39,0,187,163,190,240,15,52,6,52,57,85,69,86,53,1,40,0,185,145,225,175,8,203,2,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,39,0,137,227,133,241,2,2,6,52,57,85,69,86,53,1,40,0,185,145,225,175,8,205,2,2,105,100,1,119,6,52,57,85,69,86,53,33,0,185,145,225,175,8,205,2,4,110,97,109,101,1,40,0,185,145,225,175,8,205,2,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,69,129,177,33,0,185,145,225,175,8,205,2,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,40,0,185,145,225,175,8,205,2,10,105,115,95,112,114,105,109,97,114,121,1,121,33,0,185,145,225,175,8,205,2,2,116,121,1,39,0,185,145,225,175,8,205,2,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,185,145,225,175,8,212,2,1,48,1,40,0,185,145,225,175,8,213,2,4,100,97,116,97,1,119,0,161,185,145,225,175,8,209,2,1,161,185,145,225,175,8,207,2,1,161,185,145,225,175,8,215,2,1,161,185,145,225,175,8,216,2,1,161,185,145,225,175,8,217,2,1,161,185,145,225,175,8,218,2,1,161,185,145,225,175,8,219,2,1,168,185,145,225,175,8,220,2,1,119,4,116,105,109,101,161,185,145,225,175,8,221,2,1,168,185,145,225,175,8,211,2,1,122,0,0,0,0,0,0,0,2,39,0,185,145,225,175,8,212,2,1,50,1,40,0,185,145,225,175,8,225,2,11,116,105,109,101,95,102,111,114,109,97,116,1,122,0,0,0,0,0,0,0,1,40,0,185,145,225,175,8,225,2,11,100,97,116,101,95,102,111,114,109,97,116,1,122,0,0,0,0,0,0,0,3,40,0,185,145,225,175,8,225,2,11,116,105,109,101,122,111,110,101,95,105,100,1,119,0,168,185,145,225,175,8,223,2,1,122,0,0,0,0,102,69,129,187,40,0,185,145,225,175,8,213,2,11,116,105,109,101,122,111,110,101,95,105,100,1,119,0,40,0,185,145,225,175,8,213,2,11,100,97,116,101,95,102,111,114,109,97,116,1,122,0,0,0,0,0,0,0,3,40,0,185,145,225,175,8,213,2,11,116,105,109,101,95,102,111,114,109,97,116,1,122,0,0,0,0,0,0,0,1,161,185,145,225,175,8,193,2,1,161,185,145,225,175,8,201,2,1,161,185,145,225,175,8,185,2,1,129,185,145,225,175,8,174,2,1,0,6,129,185,145,225,175,8,236,2,1,0,6,129,185,145,225,175,8,243,2,1,0,6,129,185,145,225,175,8,250,2,1,0,6,129,185,145,225,175,8,129,3,1,0,6,129,185,145,225,175,8,136,3,1,0,6,129,185,145,225,175,8,143,3,1,0,6,81,168,211,203,155,8,0,161,137,227,133,241,2,20,1,161,137,227,133,241,2,25,1,161,137,227,133,241,2,150,1,1,168,137,227,133,241,2,115,1,119,6,70,114,115,115,74,100,168,137,227,133,241,2,113,1,119,8,103,58,107,56,113,69,117,118,168,137,227,133,241,2,116,1,122,0,0,0,0,0,0,0,3,168,137,227,133,241,2,114,1,119,0,167,137,227,133,241,2,117,0,8,0,168,211,203,155,8,7,2,118,2,2,105,100,119,6,70,114,115,115,74,100,7,118,105,115,105,98,108,101,120,118,2,7,118,105,115,105,98,108,101,120,2,105,100,119,4,120,90,48,51,39,0,137,227,133,241,2,3,36,55,101,98,54,57,55,99,100,45,54,97,53,53,45,52,48,98,98,45,57,54,97,99,45,48,100,52,97,51,98,99,57,50,52,98,50,1,40,0,168,211,203,155,8,10,2,105,100,1,119,36,55,101,98,54,57,55,99,100,45,54,97,53,53,45,52,48,98,98,45,57,54,97,99,45,48,100,52,97,51,98,99,57,50,52,98,50,40,0,168,211,203,155,8,10,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,52,99,54,53,56,56,49,55,45,50,48,100,98,45,52,102,53,54,45,98,55,102,57,45,48,54,51,55,97,50,50,100,102,101,98,54,40,0,168,211,203,155,8,10,4,110,97,109,101,1,119,4,71,114,105,100,40,0,168,211,203,155,8,10,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,48,178,5,33,0,168,211,203,155,8,10,11,109,111,100,105,102,105,101,100,95,97,116,1,39,0,168,211,203,155,8,10,15,108,97,121,111,117,116,95,115,101,116,116,105,110,103,115,1,40,0,168,211,203,155,8,10,6,108,97,121,111,117,116,1,122,0,0,0,0,0,0,0,0,39,0,168,211,203,155,8,10,14,102,105,101,108,100,95,115,101,116,116,105,110,103,115,1,39,0,168,211,203,155,8,10,7,102,105,108,116,101,114,115,0,39,0,168,211,203,155,8,10,6,103,114,111,117,112,115,0,39,0,168,211,203,155,8,10,5,115,111,114,116,115,0,39,0,168,211,203,155,8,10,12,102,105,101,108,100,95,111,114,100,101,114,115,0,8,0,168,211,203,155,8,22,5,118,1,2,105,100,119,6,89,53,52,81,73,115,118,1,2,105,100,119,6,70,114,115,115,74,100,118,1,2,105,100,119,6,89,80,102,105,50,109,118,1,2,105,100,119,6,84,102,117,121,104,84,118,1,2,105,100,119,6,115,111,118,85,116,69,39,0,168,211,203,155,8,10,10,114,111,119,95,111,114,100,101,114,115,0,8,0,168,211,203,155,8,28,3,118,2,2,105,100,119,36,50,102,57,52,52,50,50,48,45,57,102,52,53,45,52,48,100,57,45,57,54,98,53,45,101,56,99,48,56,56,56,100,97,102,55,99,6,104,101,105,103,104,116,125,60,118,2,2,105,100,119,36,49,48,52,55,102,50,100,48,45,51,55,53,55,45,52,55,57,57,45,98,99,102,50,45,101,56,102,57,55,52,54,52,100,50,98,53,6,104,101,105,103,104,116,125,60,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,100,100,54,99,56,100,49,51,45,52,56,54,55,45,52,49,99,54,45,56,53,57,57,45,98,56,56,56,51,53,48,102,53,50,101,101,161,137,227,133,241,2,154,1,1,40,0,137,227,133,241,2,69,5,119,105,100,116,104,1,122,0,0,0,0,0,0,0,150,40,0,137,227,133,241,2,69,4,119,114,97,112,1,121,168,137,227,133,241,2,70,1,122,0,0,0,0,0,0,0,2,161,168,211,203,155,8,2,1,136,137,227,133,241,2,151,1,1,118,1,2,105,100,119,6,54,76,70,72,66,54,39,0,137,227,133,241,2,92,6,54,76,70,72,66,54,1,40,0,168,211,203,155,8,38,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,161,137,227,133,241,2,146,1,1,136,137,227,133,241,2,147,1,1,118,1,2,105,100,119,6,54,76,70,72,66,54,39,0,137,227,133,241,2,133,1,6,54,76,70,72,66,54,1,40,0,168,211,203,155,8,42,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,161,168,211,203,155,8,15,1,136,168,211,203,155,8,27,1,118,1,2,105,100,119,6,54,76,70,72,66,54,39,0,168,211,203,155,8,18,6,54,76,70,72,66,54,1,40,0,168,211,203,155,8,46,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,161,168,211,203,155,8,32,1,136,137,227,133,241,2,155,1,1,118,1,2,105,100,119,6,54,76,70,72,66,54,39,0,137,227,133,241,2,43,6,54,76,70,72,66,54,1,40,0,168,211,203,155,8,50,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,39,0,137,227,133,241,2,2,6,54,76,70,72,66,54,1,40,0,168,211,203,155,8,52,2,105,100,1,119,6,54,76,70,72,66,54,33,0,168,211,203,155,8,52,4,110,97,109,101,1,40,0,168,211,203,155,8,52,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,48,230,211,33,0,168,211,203,155,8,52,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,40,0,168,211,203,155,8,52,10,105,115,95,112,114,105,109,97,114,121,1,121,33,0,168,211,203,155,8,52,2,116,121,1,39,0,168,211,203,155,8,52,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,168,211,203,155,8,59,1,48,1,40,0,168,211,203,155,8,60,4,100,97,116,97,1,119,0,161,168,211,203,155,8,36,1,136,168,211,203,155,8,37,1,118,1,2,105,100,119,6,86,89,52,50,103,49,39,0,137,227,133,241,2,92,6,86,89,52,50,103,49,1,40,0,168,211,203,155,8,64,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,161,168,211,203,155,8,40,1,136,168,211,203,155,8,41,1,118,1,2,105,100,119,6,86,89,52,50,103,49,39,0,137,227,133,241,2,133,1,6,86,89,52,50,103,49,1,40,0,168,211,203,155,8,68,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,161,168,211,203,155,8,44,1,136,168,211,203,155,8,45,1,118,1,2,105,100,119,6,86,89,52,50,103,49,39,0,168,211,203,155,8,18,6,86,89,52,50,103,49,1,40,0,168,211,203,155,8,72,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,161,168,211,203,155,8,48,1,136,168,211,203,155,8,49,1,118,1,2,105,100,119,6,86,89,52,50,103,49,39,0,137,227,133,241,2,43,6,86,89,52,50,103,49,1,40,0,168,211,203,155,8,76,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,39,0,137,227,133,241,2,2,6,86,89,52,50,103,49,1,40,0,168,211,203,155,8,78,2,105,100,1,119,6,86,89,52,50,103,49,33,0,168,211,203,155,8,78,4,110,97,109,101,1,40,0,168,211,203,155,8,78,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,48,230,213,33,0,168,211,203,155,8,78,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,40,0,168,211,203,155,8,78,10,105,115,95,112,114,105,109,97,114,121,1,121,33,0,168,211,203,155,8,78,2,116,121,1,39,0,168,211,203,155,8,78,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,168,211,203,155,8,85,1,48,1,40,0,168,211,203,155,8,86,4,100,97,116,97,1,119,0,1,150,194,135,131,8,0,161,206,242,242,141,13,94,12,1,200,168,240,223,7,0,161,180,149,168,150,13,9,2,1,216,247,253,206,7,0,161,141,132,223,206,14,4,2,1,193,174,143,180,7,0,161,150,194,135,131,8,11,18,1,202,170,215,178,7,0,161,191,215,204,166,13,11,24,1,157,197,217,249,6,0,161,224,218,133,236,10,12,3,1,247,187,192,242,6,0,161,134,200,133,143,5,1,6,1,248,220,249,231,6,0,161,200,156,140,203,9,1,29,18,146,198,138,224,6,0,161,137,227,133,241,2,162,1,1,161,137,227,133,241,2,169,1,1,161,137,227,133,241,2,168,1,1,161,137,227,133,241,2,167,1,1,161,146,198,138,224,6,0,1,168,146,198,138,224,6,2,1,122,0,0,0,0,0,0,0,1,168,146,198,138,224,6,1,1,122,0,0,0,0,0,0,0,3,168,146,198,138,224,6,3,1,119,0,161,187,163,190,240,15,81,1,168,187,163,190,240,15,83,1,122,0,0,0,0,0,0,0,10,39,0,187,163,190,240,15,84,2,49,48,1,33,0,146,198,138,224,6,10,11,100,97,116,97,98,97,115,101,95,105,100,1,161,146,198,138,224,6,8,1,40,0,187,163,190,240,15,85,11,100,97,116,97,98,97,115,101,95,105,100,1,119,0,161,234,232,155,212,3,0,1,161,177,178,255,174,1,0,1,168,146,198,138,224,6,12,1,122,0,0,0,0,102,67,52,219,168,146,198,138,224,6,11,1,119,36,52,99,54,53,56,56,49,55,45,50,48,100,98,45,52,102,53,54,45,98,55,102,57,45,48,54,51,55,97,50,50,100,102,101,98,54,1,158,173,179,170,6,0,161,183,238,200,180,5,5,81,1,183,238,200,180,5,0,161,160,159,229,236,10,33,6,2,174,250,146,158,5,0,161,216,247,253,206,7,1,1,168,174,250,146,158,5,0,1,122,0,0,0,0,102,88,107,140,1,134,200,133,143,5,0,161,201,191,253,157,12,1,2,1,182,139,168,140,5,0,161,253,223,254,206,11,1,36,1,140,242,215,248,4,0,161,157,197,217,249,6,2,35,2,186,204,138,236,4,0,33,1,4,109,101,116,97,12,108,97,115,116,95,115,121,110,99,95,97,116,112,161,186,204,138,236,4,111,6,1,247,149,251,192,4,0,161,248,220,249,231,6,28,4,1,203,248,208,163,4,0,161,202,170,215,178,7,23,4,1,128,137,148,150,4,0,161,154,253,168,186,13,5,39,4,234,232,155,212,3,0,161,177,178,255,174,1,32,1,168,137,227,133,241,2,54,1,122,0,0,0,0,0,0,0,150,168,137,227,133,241,2,55,1,120,168,137,227,133,241,2,53,1,122,0,0,0,0,0,0,0,0,156,1,137,227,133,241,2,0,39,1,4,100,97,116,97,8,100,97,116,97,98,97,115,101,1,40,0,137,227,133,241,2,0,2,105,100,1,119,36,52,99,54,53,56,56,49,55,45,50,48,100,98,45,52,102,53,54,45,98,55,102,57,45,48,54,51,55,97,50,50,100,102,101,98,54,39,0,137,227,133,241,2,0,6,102,105,101,108,100,115,1,39,0,137,227,133,241,2,0,5,118,105,101,119,115,1,39,0,137,227,133,241,2,0,5,109,101,116,97,115,1,40,0,137,227,133,241,2,4,3,105,105,100,1,119,36,55,100,50,49,52,56,102,99,45,99,97,99,101,45,52,52,53,50,45,57,99,53,99,45,57,54,101,53,50,101,54,98,102,56,98,53,39,0,137,227,133,241,2,2,6,89,53,52,81,73,115,1,40,0,137,227,133,241,2,6,2,105,100,1,119,6,89,53,52,81,73,115,40,0,137,227,133,241,2,6,4,110,97,109,101,1,119,4,78,97,109,101,40,0,137,227,133,241,2,6,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,48,108,138,40,0,137,227,133,241,2,6,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,48,108,138,40,0,137,227,133,241,2,6,10,105,115,95,112,114,105,109,97,114,121,1,120,40,0,137,227,133,241,2,6,2,116,121,1,122,0,0,0,0,0,0,0,0,39,0,137,227,133,241,2,6,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,137,227,133,241,2,13,1,48,1,40,0,137,227,133,241,2,14,4,100,97,116,97,1,119,0,39,0,137,227,133,241,2,2,6,70,114,115,115,74,100,1,40,0,137,227,133,241,2,16,2,105,100,1,119,6,70,114,115,115,74,100,33,0,137,227,133,241,2,16,4,110,97,109,101,1,40,0,137,227,133,241,2,16,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,48,108,138,33,0,137,227,133,241,2,16,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,40,0,137,227,133,241,2,16,10,105,115,95,112,114,105,109,97,114,121,1,121,33,0,137,227,133,241,2,16,2,116,121,1,39,0,137,227,133,241,2,16,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,137,227,133,241,2,23,1,51,1,33,0,137,227,133,241,2,24,7,99,111,110,116,101,110,116,1,39,0,137,227,133,241,2,2,6,89,80,102,105,50,109,1,40,0,137,227,133,241,2,26,2,105,100,1,119,6,89,80,102,105,50,109,40,0,137,227,133,241,2,26,4,110,97,109,101,1,119,4,68,111,110,101,40,0,137,227,133,241,2,26,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,48,108,138,40,0,137,227,133,241,2,26,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,48,108,138,40,0,137,227,133,241,2,26,10,105,115,95,112,114,105,109,97,114,121,1,121,40,0,137,227,133,241,2,26,2,116,121,1,122,0,0,0,0,0,0,0,5,39,0,137,227,133,241,2,26,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,137,227,133,241,2,33,1,53,1,39,0,137,227,133,241,2,3,36,55,100,50,49,52,56,102,99,45,99,97,99,101,45,52,52,53,50,45,57,99,53,99,45,57,54,101,53,50,101,54,98,102,56,98,53,1,40,0,137,227,133,241,2,35,2,105,100,1,119,36,55,100,50,49,52,56,102,99,45,99,97,99,101,45,52,52,53,50,45,57,99,53,99,45,57,54,101,53,50,101,54,98,102,56,98,53,40,0,137,227,133,241,2,35,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,52,99,54,53,56,56,49,55,45,50,48,100,98,45,52,102,53,54,45,98,55,102,57,45,48,54,51,55,97,50,50,100,102,101,98,54,40,0,137,227,133,241,2,35,4,110,97,109,101,1,119,8,85,110,116,105,116,108,101,100,40,0,137,227,133,241,2,35,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,48,108,138,33,0,137,227,133,241,2,35,11,109,111,100,105,102,105,101,100,95,97,116,1,39,0,137,227,133,241,2,35,15,108,97,121,111,117,116,95,115,101,116,116,105,110,103,115,1,40,0,137,227,133,241,2,35,6,108,97,121,111,117,116,1,122,0,0,0,0,0,0,0,0,39,0,137,227,133,241,2,35,14,102,105,101,108,100,95,115,101,116,116,105,110,103,115,1,39,0,137,227,133,241,2,43,6,70,114,115,115,74,100,1,40,0,137,227,133,241,2,44,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,40,0,137,227,133,241,2,44,4,119,114,97,112,1,121,40,0,137,227,133,241,2,44,5,119,105,100,116,104,1,122,0,0,0,0,0,0,0,150,39,0,137,227,133,241,2,43,6,89,80,102,105,50,109,1,40,0,137,227,133,241,2,48,5,119,105,100,116,104,1,122,0,0,0,0,0,0,0,150,40,0,137,227,133,241,2,48,4,119,114,97,112,1,121,40,0,137,227,133,241,2,48,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,39,0,137,227,133,241,2,43,6,89,53,52,81,73,115,1,33,0,137,227,133,241,2,52,10,118,105,115,105,98,105,108,105,116,121,1,33,0,137,227,133,241,2,52,5,119,105,100,116,104,1,33,0,137,227,133,241,2,52,4,119,114,97,112,1,39,0,137,227,133,241,2,35,7,102,105,108,116,101,114,115,0,39,0,137,227,133,241,2,35,6,103,114,111,117,112,115,0,39,0,137,227,133,241,2,35,5,115,111,114,116,115,0,39,0,137,227,133,241,2,35,12,102,105,101,108,100,95,111,114,100,101,114,115,0,8,0,137,227,133,241,2,59,3,118,1,2,105,100,119,6,89,53,52,81,73,115,118,1,2,105,100,119,6,70,114,115,115,74,100,118,1,2,105,100,119,6,89,80,102,105,50,109,39,0,137,227,133,241,2,35,10,114,111,119,95,111,114,100,101,114,115,0,8,0,137,227,133,241,2,63,3,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,50,102,57,52,52,50,50,48,45,57,102,52,53,45,52,48,100,57,45,57,54,98,53,45,101,56,99,48,56,56,56,100,97,102,55,99,118,2,2,105,100,119,36,49,48,52,55,102,50,100,48,45,51,55,53,55,45,52,55,57,57,45,98,99,102,50,45,101,56,102,57,55,52,54,52,100,50,98,53,6,104,101,105,103,104,116,125,60,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,100,100,54,99,56,100,49,51,45,52,56,54,55,45,52,49,99,54,45,56,53,57,57,45,98,56,56,56,51,53,48,102,53,50,101,101,161,137,227,133,241,2,40,1,136,137,227,133,241,2,62,1,118,1,2,105,100,119,6,84,102,117,121,104,84,39,0,137,227,133,241,2,43,6,84,102,117,121,104,84,1,33,0,137,227,133,241,2,69,10,118,105,115,105,98,105,108,105,116,121,1,39,0,137,227,133,241,2,2,6,84,102,117,121,104,84,1,40,0,137,227,133,241,2,71,2,105,100,1,119,6,84,102,117,121,104,84,40,0,137,227,133,241,2,71,4,110,97,109,101,1,119,4,84,101,120,116,40,0,137,227,133,241,2,71,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,48,111,178,40,0,137,227,133,241,2,71,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,48,111,178,40,0,137,227,133,241,2,71,10,105,115,95,112,114,105,109,97,114,121,1,121,40,0,137,227,133,241,2,71,2,116,121,1,122,0,0,0,0,0,0,0,0,39,0,137,227,133,241,2,71,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,137,227,133,241,2,78,1,48,1,40,0,137,227,133,241,2,79,4,100,97,116,97,1,119,0,39,0,137,227,133,241,2,3,36,101,57,55,56,55,55,102,53,45,99,51,54,53,45,52,48,50,53,45,57,101,54,97,45,101,53,57,48,99,52,98,49,57,100,98,98,1,40,0,137,227,133,241,2,81,2,105,100,1,119,36,101,57,55,56,55,55,102,53,45,99,51,54,53,45,52,48,50,53,45,57,101,54,97,45,101,53,57,48,99,52,98,49,57,100,98,98,40,0,137,227,133,241,2,81,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,52,99,54,53,56,56,49,55,45,50,48,100,98,45,52,102,53,54,45,98,55,102,57,45,48,54,51,55,97,50,50,100,102,101,98,54,40,0,137,227,133,241,2,81,4,110,97,109,101,1,119,5,66,111,97,114,100,40,0,137,227,133,241,2,81,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,48,177,159,33,0,137,227,133,241,2,81,11,109,111,100,105,102,105,101,100,95,97,116,1,39,0,137,227,133,241,2,81,15,108,97,121,111,117,116,95,115,101,116,116,105,110,103,115,1,39,0,137,227,133,241,2,87,1,49,1,40,0,137,227,133,241,2,88,21,104,105,100,101,95,117,110,103,114,111,117,112,101,100,95,99,111,108,117,109,110,1,121,40,0,137,227,133,241,2,88,22,99,111,108,108,97,112,115,101,95,104,105,100,100,101,110,95,103,114,111,117,112,115,1,121,40,0,137,227,133,241,2,81,6,108,97,121,111,117,116,1,122,0,0,0,0,0,0,0,1,39,0,137,227,133,241,2,81,14,102,105,101,108,100,95,115,101,116,116,105,110,103,115,1,39,0,137,227,133,241,2,81,7,102,105,108,116,101,114,115,0,39,0,137,227,133,241,2,81,6,103,114,111,117,112,115,0,39,0,137,227,133,241,2,81,5,115,111,114,116,115,0,39,0,137,227,133,241,2,81,12,102,105,101,108,100,95,111,114,100,101,114,115,0,8,0,137,227,133,241,2,96,4,118,1,2,105,100,119,6,89,53,52,81,73,115,118,1,2,105,100,119,6,70,114,115,115,74,100,118,1,2,105,100,119,6,89,80,102,105,50,109,118,1,2,105,100,119,6,84,102,117,121,104,84,39,0,137,227,133,241,2,81,10,114,111,119,95,111,114,100,101,114,115,0,8,0,137,227,133,241,2,101,3,118,2,2,105,100,119,36,50,102,57,52,52,50,50,48,45,57,102,52,53,45,52,48,100,57,45,57,54,98,53,45,101,56,99,48,56,56,56,100,97,102,55,99,6,104,101,105,103,104,116,125,60,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,49,48,52,55,102,50,100,48,45,51,55,53,55,45,52,55,57,57,45,98,99,102,50,45,101,56,102,57,55,52,54,52,100,50,98,53,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,100,100,54,99,56,100,49,51,45,52,56,54,55,45,52,49,99,54,45,56,53,57,57,45,98,56,56,56,51,53,48,102,53,50,101,101,161,137,227,133,241,2,86,1,7,0,137,227,133,241,2,94,1,33,0,137,227,133,241,2,106,2,105,100,1,33,0,137,227,133,241,2,106,6,103,114,111,117,112,115,1,33,0,137,227,133,241,2,106,7,99,111,110,116,101,110,116,1,33,0,137,227,133,241,2,106,8,102,105,101,108,100,95,105,100,1,33,0,137,227,133,241,2,106,2,116,121,1,161,137,227,133,241,2,105,1,161,137,227,133,241,2,107,1,161,137,227,133,241,2,109,1,161,137,227,133,241,2,110,1,161,137,227,133,241,2,111,1,161,137,227,133,241,2,108,1,0,1,39,0,137,227,133,241,2,3,36,102,48,99,53,57,57,50,49,45,48,52,101,101,45,52,57,55,49,45,57,57,53,99,45,55,57,98,55,102,100,56,99,48,48,101,50,1,40,0,137,227,133,241,2,119,2,105,100,1,119,36,102,48,99,53,57,57,50,49,45,48,52,101,101,45,52,57,55,49,45,57,57,53,99,45,55,57,98,55,102,100,56,99,48,48,101,50,40,0,137,227,133,241,2,119,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,52,99,54,53,56,56,49,55,45,50,48,100,98,45,52,102,53,54,45,98,55,102,57,45,48,54,51,55,97,50,50,100,102,101,98,54,40,0,137,227,133,241,2,119,4,110,97,109,101,1,119,8,67,97,108,101,110,100,97,114,40,0,137,227,133,241,2,119,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,48,177,162,33,0,137,227,133,241,2,119,11,109,111,100,105,102,105,101,100,95,97,116,1,39,0,137,227,133,241,2,119,15,108,97,121,111,117,116,95,115,101,116,116,105,110,103,115,1,39,0,137,227,133,241,2,125,1,50,1,40,0,137,227,133,241,2,126,9,108,97,121,111,117,116,95,116,121,1,122,0,0,0,0,0,0,0,0,40,0,137,227,133,241,2,126,17,102,105,114,115,116,95,100,97,121,95,111,102,95,119,101,101,107,1,122,0,0,0,0,0,0,0,0,40,0,137,227,133,241,2,126,8,102,105,101,108,100,95,105,100,1,119,6,115,111,118,85,116,69,40,0,137,227,133,241,2,126,13,115,104,111,119,95,119,101,101,107,101,110,100,115,1,120,40,0,137,227,133,241,2,126,17,115,104,111,119,95,119,101,101,107,95,110,117,109,98,101,114,115,1,120,40,0,137,227,133,241,2,119,6,108,97,121,111,117,116,1,122,0,0,0,0,0,0,0,2,39,0,137,227,133,241,2,119,14,102,105,101,108,100,95,115,101,116,116,105,110,103,115,1,39,0,137,227,133,241,2,119,7,102,105,108,116,101,114,115,0,39,0,137,227,133,241,2,119,6,103,114,111,117,112,115,0,39,0,137,227,133,241,2,119,5,115,111,114,116,115,0,39,0,137,227,133,241,2,119,12,102,105,101,108,100,95,111,114,100,101,114,115,0,8,0,137,227,133,241,2,137,1,4,118,1,2,105,100,119,6,89,53,52,81,73,115,118,1,2,105,100,119,6,70,114,115,115,74,100,118,1,2,105,100,119,6,89,80,102,105,50,109,118,1,2,105,100,119,6,84,102,117,121,104,84,39,0,137,227,133,241,2,119,10,114,111,119,95,111,114,100,101,114,115,0,8,0,137,227,133,241,2,142,1,3,118,2,2,105,100,119,36,50,102,57,52,52,50,50,48,45,57,102,52,53,45,52,48,100,57,45,57,54,98,53,45,101,56,99,48,56,56,56,100,97,102,55,99,6,104,101,105,103,104,116,125,60,118,2,2,105,100,119,36,49,48,52,55,102,50,100,48,45,51,55,53,55,45,52,55,57,57,45,98,99,102,50,45,101,56,102,57,55,52,54,52,100,50,98,53,6,104,101,105,103,104,116,125,60,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,100,100,54,99,56,100,49,51,45,52,56,54,55,45,52,49,99,54,45,56,53,57,57,45,98,56,56,56,51,53,48,102,53,50,101,101,161,137,227,133,241,2,124,1,136,137,227,133,241,2,141,1,1,118,1,2,105,100,119,6,115,111,118,85,116,69,39,0,137,227,133,241,2,133,1,6,115,111,118,85,116,69,1,40,0,137,227,133,241,2,148,1,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,161,137,227,133,241,2,112,1,136,137,227,133,241,2,100,1,118,1,2,105,100,119,6,115,111,118,85,116,69,39,0,137,227,133,241,2,92,6,115,111,118,85,116,69,1,40,0,137,227,133,241,2,152,1,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,161,137,227,133,241,2,67,1,136,137,227,133,241,2,68,1,118,1,2,105,100,119,6,115,111,118,85,116,69,39,0,137,227,133,241,2,43,6,115,111,118,85,116,69,1,40,0,137,227,133,241,2,156,1,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,39,0,137,227,133,241,2,2,6,115,111,118,85,116,69,1,40,0,137,227,133,241,2,158,1,2,105,100,1,119,6,115,111,118,85,116,69,33,0,137,227,133,241,2,158,1,4,110,97,109,101,1,40,0,137,227,133,241,2,158,1,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,48,177,162,33,0,137,227,133,241,2,158,1,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,40,0,137,227,133,241,2,158,1,10,105,115,95,112,114,105,109,97,114,121,1,121,33,0,137,227,133,241,2,158,1,2,116,121,1,39,0,137,227,133,241,2,158,1,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,137,227,133,241,2,165,1,1,50,1,33,0,137,227,133,241,2,166,1,11,116,105,109,101,122,111,110,101,95,105,100,1,33,0,137,227,133,241,2,166,1,11,116,105,109,101,95,102,111,114,109,97,116,1,33,0,137,227,133,241,2,166,1,11,100,97,116,101,95,102,111,114,109,97,116,1,71,193,140,213,146,2,0,39,0,137,227,133,241,2,3,36,49,51,53,54,49,53,102,97,45,54,54,102,55,45,52,52,53,49,45,57,98,53,52,45,100,55,101,57,57,52,52,53,102,99,97,52,1,40,0,193,140,213,146,2,0,2,105,100,1,119,36,49,51,53,54,49,53,102,97,45,54,54,102,55,45,52,52,53,49,45,57,98,53,52,45,100,55,101,57,57,52,52,53,102,99,97,52,40,0,193,140,213,146,2,0,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,52,99,54,53,56,56,49,55,45,50,48,100,98,45,52,102,53,54,45,98,55,102,57,45,48,54,51,55,97,50,50,100,102,101,98,54,40,0,193,140,213,146,2,0,4,110,97,109,101,1,119,12,86,105,101,119,32,111,102,32,71,114,105,100,40,0,193,140,213,146,2,0,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,0,0,0,0,40,0,193,140,213,146,2,0,11,109,111,100,105,102,105,101,100,95,97,116,1,122,0,0,0,0,0,0,0,0,39,0,193,140,213,146,2,0,15,108,97,121,111,117,116,95,115,101,116,116,105,110,103,115,1,40,0,193,140,213,146,2,0,6,108,97,121,111,117,116,1,122,0,0,0,0,0,0,0,0,39,0,193,140,213,146,2,0,14,102,105,101,108,100,95,115,101,116,116,105,110,103,115,1,39,0,193,140,213,146,2,0,7,102,105,108,116,101,114,115,0,39,0,193,140,213,146,2,0,6,103,114,111,117,112,115,0,39,0,193,140,213,146,2,0,5,115,111,114,116,115,0,39,0,193,140,213,146,2,0,12,102,105,101,108,100,95,111,114,100,101,114,115,0,8,0,193,140,213,146,2,12,12,118,1,2,105,100,119,6,89,53,52,81,73,115,118,1,2,105,100,119,6,70,114,115,115,74,100,118,1,2,105,100,119,6,89,80,102,105,50,109,118,1,2,105,100,119,6,84,102,117,121,104,84,118,1,2,105,100,119,6,115,111,118,85,116,69,118,1,2,105,100,119,6,54,76,70,72,66,54,118,1,2,105,100,119,6,86,89,52,50,103,49,118,1,2,105,100,119,6,106,87,101,95,116,54,118,1,2,105,100,119,6,55,75,88,95,99,120,118,1,2,105,100,119,6,76,99,121,68,75,106,118,1,2,105,100,119,6,120,69,81,65,111,75,118,1,2,105,100,119,6,52,57,85,69,86,53,39,0,193,140,213,146,2,0,10,114,111,119,95,111,114,100,101,114,115,0,8,0,193,140,213,146,2,25,10,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,50,102,57,52,52,50,50,48,45,57,102,52,53,45,52,48,100,57,45,57,54,98,53,45,101,56,99,48,56,56,56,100,97,102,55,99,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,49,48,52,55,102,50,100,48,45,51,55,53,55,45,52,55,57,57,45,98,99,102,50,45,101,56,102,57,55,52,54,52,100,50,98,53,118,2,2,105,100,119,36,100,100,54,99,56,100,49,51,45,52,56,54,55,45,52,49,99,54,45,56,53,57,57,45,98,56,56,56,51,53,48,102,53,50,101,101,6,104,101,105,103,104,116,125,60,118,2,2,105,100,119,36,49,99,98,57,49,102,97,50,45,54,51,56,100,45,52,48,100,54,45,97,55,99,52,45,51,57,52,102,48,100,56,98,49,57,49,51,6,104,101,105,103,104,116,125,60,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,51,49,56,97,97,52,49,53,45,57,50,97,101,45,52,56,57,97,45,97,49,52,102,45,97,50,52,54,57,50,97,50,101,102,97,54,118,2,2,105,100,119,36,51,99,99,100,49,55,101,48,45,100,55,56,98,45,52,52,101,50,45,97,102,100,49,45,49,98,102,55,99,99,52,57,99,98,53,54,6,104,101,105,103,104,116,125,60,118,2,2,105,100,119,36,48,49,54,48,101,53,56,55,45,52,49,102,52,45,52,51,57,49,45,97,98,98,51,45,100,51,50,50,98,53,50,51,101,100,98,50,6,104,101,105,103,104,116,125,60,118,2,2,105,100,119,36,52,98,53,54,48,99,50,100,45,51,102,51,57,45,52,48,56,54,45,97,97,51,100,45,99,50,53,57,48,100,49,50,57,56,53,48,6,104,101,105,103,104,116,125,60,118,2,2,105,100,119,36,51,97,97,100,99,99,52,49,45,52,98,52,100,45,52,53,55,48,45,97,53,100,101,45,48,54,101,98,101,51,102,52,54,48,101,99,6,104,101,105,103,104,116,125,60,118,2,2,105,100,119,36,56,56,102,97,51,54,98,50,45,54,100,55,50,45,52,52,100,101,45,98,48,100,102,45,100,51,98,50,101,54,100,55,52,52,100,54,6,104,101,105,103,104,116,125,60,39,0,137,227,133,241,2,3,36,98,52,101,55,55,50,48,51,45,53,99,56,98,45,52,56,100,102,45,98,98,99,53,45,50,101,49,49,52,51,101,98,48,101,54,49,1,40,0,193,140,213,146,2,36,2,105,100,1,119,36,98,52,101,55,55,50,48,51,45,53,99,56,98,45,52,56,100,102,45,98,98,99,53,45,50,101,49,49,52,51,101,98,48,101,54,49,40,0,193,140,213,146,2,36,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,52,99,54,53,56,56,49,55,45,50,48,100,98,45,52,102,53,54,45,98,55,102,57,45,48,54,51,55,97,50,50,100,102,101,98,54,40,0,193,140,213,146,2,36,4,110,97,109,101,1,119,22,86,105,101,119,32,111,102,32,66,111,97,114,100,32,99,104,101,99,107,98,111,120,40,0,193,140,213,146,2,36,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,0,0,0,0,33,0,193,140,213,146,2,36,11,109,111,100,105,102,105,101,100,95,97,116,1,39,0,193,140,213,146,2,36,15,108,97,121,111,117,116,95,115,101,116,116,105,110,103,115,1,39,0,193,140,213,146,2,42,1,49,1,40,0,193,140,213,146,2,43,21,104,105,100,101,95,117,110,103,114,111,117,112,101,100,95,99,111,108,117,109,110,1,121,40,0,193,140,213,146,2,43,22,99,111,108,108,97,112,115,101,95,104,105,100,100,101,110,95,103,114,111,117,112,115,1,121,40,0,193,140,213,146,2,36,6,108,97,121,111,117,116,1,122,0,0,0,0,0,0,0,1,39,0,193,140,213,146,2,36,14,102,105,101,108,100,95,115,101,116,116,105,110,103,115,1,39,0,193,140,213,146,2,36,7,102,105,108,116,101,114,115,0,39,0,193,140,213,146,2,36,6,103,114,111,117,112,115,0,39,0,193,140,213,146,2,36,5,115,111,114,116,115,0,39,0,193,140,213,146,2,36,12,102,105,101,108,100,95,111,114,100,101,114,115,0,8,0,193,140,213,146,2,51,12,118,1,2,105,100,119,6,89,53,52,81,73,115,118,1,2,105,100,119,6,70,114,115,115,74,100,118,1,2,105,100,119,6,89,80,102,105,50,109,118,1,2,105,100,119,6,84,102,117,121,104,84,118,1,2,105,100,119,6,115,111,118,85,116,69,118,1,2,105,100,119,6,54,76,70,72,66,54,118,1,2,105,100,119,6,86,89,52,50,103,49,118,1,2,105,100,119,6,106,87,101,95,116,54,118,1,2,105,100,119,6,55,75,88,95,99,120,118,1,2,105,100,119,6,76,99,121,68,75,106,118,1,2,105,100,119,6,120,69,81,65,111,75,118,1,2,105,100,119,6,52,57,85,69,86,53,39,0,193,140,213,146,2,36,10,114,111,119,95,111,114,100,101,114,115,0,8,0,193,140,213,146,2,64,10,118,2,2,105,100,119,36,50,102,57,52,52,50,50,48,45,57,102,52,53,45,52,48,100,57,45,57,54,98,53,45,101,56,99,48,56,56,56,100,97,102,55,99,6,104,101,105,103,104,116,125,60,118,2,2,105,100,119,36,49,48,52,55,102,50,100,48,45,51,55,53,55,45,52,55,57,57,45,98,99,102,50,45,101,56,102,57,55,52,54,52,100,50,98,53,6,104,101,105,103,104,116,125,60,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,100,100,54,99,56,100,49,51,45,52,56,54,55,45,52,49,99,54,45,56,53,57,57,45,98,56,56,56,51,53,48,102,53,50,101,101,118,2,2,105,100,119,36,49,99,98,57,49,102,97,50,45,54,51,56,100,45,52,48,100,54,45,97,55,99,52,45,51,57,52,102,48,100,56,98,49,57,49,51,6,104,101,105,103,104,116,125,60,118,2,2,105,100,119,36,51,49,56,97,97,52,49,53,45,57,50,97,101,45,52,56,57,97,45,97,49,52,102,45,97,50,52,54,57,50,97,50,101,102,97,54,6,104,101,105,103,104,116,125,60,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,51,99,99,100,49,55,101,48,45,100,55,56,98,45,52,52,101,50,45,97,102,100,49,45,49,98,102,55,99,99,52,57,99,98,53,54,118,2,2,105,100,119,36,48,49,54,48,101,53,56,55,45,52,49,102,52,45,52,51,57,49,45,97,98,98,51,45,100,51,50,50,98,53,50,51,101,100,98,50,6,104,101,105,103,104,116,125,60,118,2,2,105,100,119,36,52,98,53,54,48,99,50,100,45,51,102,51,57,45,52,48,56,54,45,97,97,51,100,45,99,50,53,57,48,100,49,50,57,56,53,48,6,104,101,105,103,104,116,125,60,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,51,97,97,100,99,99,52,49,45,52,98,52,100,45,52,53,55,48,45,97,53,100,101,45,48,54,101,98,101,51,102,52,54,48,101,99,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,56,56,102,97,51,54,98,50,45,54,100,55,50,45,52,52,100,101,45,98,48,100,102,45,100,51,98,50,101,54,100,55,52,52,100,54,161,193,140,213,146,2,41,1,7,0,193,140,213,146,2,49,1,33,0,193,140,213,146,2,76,6,103,114,111,117,112,115,1,33,0,193,140,213,146,2,76,2,116,121,1,33,0,193,140,213,146,2,76,8,102,105,101,108,100,95,105,100,1,33,0,193,140,213,146,2,76,2,105,100,1,33,0,193,140,213,146,2,76,7,99,111,110,116,101,110,116,1,168,193,140,213,146,2,75,1,122,0,0,0,0,102,79,7,25,168,193,140,213,146,2,79,1,119,6,70,114,115,115,74,100,168,193,140,213,146,2,78,1,122,0,0,0,0,0,0,0,3,168,193,140,213,146,2,80,1,119,8,103,58,105,88,95,87,48,73,167,193,140,213,146,2,77,0,8,0,193,140,213,146,2,86,4,118,2,2,105,100,119,6,70,114,115,115,74,100,7,118,105,115,105,98,108,101,120,118,2,7,118,105,115,105,98,108,101,120,2,105,100,119,4,120,90,48,51,118,2,7,118,105,115,105,98,108,101,120,2,105,100,119,36,48,52,102,52,55,48,51,55,45,49,56,54,97,45,52,56,55,102,45,98,54,56,101,45,102,49,98,102,97,48,102,101,54,54,53,101,118,2,7,118,105,115,105,98,108,101,120,2,105,100,119,36,54,49,50,100,50,99,51,98,45,56,50,98,99,45,52,55,51,98,45,98,49,52,53,45,55,102,53,55,49,56,54,101,51,102,55,101,168,193,140,213,146,2,81,1,119,0,39,0,137,227,133,241,2,3,36,97,54,97,102,51,49,49,102,45,99,98,99,56,45,52,50,99,50,45,98,56,48,49,45,55,49,49,53,54,49,57,99,51,55,55,54,1,40,0,193,140,213,146,2,92,2,105,100,1,119,36,97,54,97,102,51,49,49,102,45,99,98,99,56,45,52,50,99,50,45,98,56,48,49,45,55,49,49,53,54,49,57,99,51,55,55,54,40,0,193,140,213,146,2,92,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,52,99,54,53,56,56,49,55,45,50,48,100,98,45,52,102,53,54,45,98,55,102,57,45,48,54,51,55,97,50,50,100,102,101,98,54,40,0,193,140,213,146,2,92,4,110,97,109,101,1,119,16,86,105,101,119,32,111,102,32,67,97,108,101,110,100,97,114,40,0,193,140,213,146,2,92,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,0,0,0,0,40,0,193,140,213,146,2,92,11,109,111,100,105,102,105,101,100,95,97,116,1,122,0,0,0,0,0,0,0,0,39,0,193,140,213,146,2,92,15,108,97,121,111,117,116,95,115,101,116,116,105,110,103,115,1,39,0,193,140,213,146,2,98,1,50,1,40,0,193,140,213,146,2,99,17,102,105,114,115,116,95,100,97,121,95,111,102,95,119,101,101,107,1,122,0,0,0,0,0,0,0,0,40,0,193,140,213,146,2,99,8,102,105,101,108,100,95,105,100,1,119,6,52,57,85,69,86,53,40,0,193,140,213,146,2,99,9,108,97,121,111,117,116,95,116,121,1,122,0,0,0,0,0,0,0,0,40,0,193,140,213,146,2,99,13,115,104,111,119,95,119,101,101,107,101,110,100,115,1,120,40,0,193,140,213,146,2,99,17,115,104,111,119,95,119,101,101,107,95,110,117,109,98,101,114,115,1,120,40,0,193,140,213,146,2,92,6,108,97,121,111,117,116,1,122,0,0,0,0,0,0,0,2,39,0,193,140,213,146,2,92,14,102,105,101,108,100,95,115,101,116,116,105,110,103,115,1,39,0,193,140,213,146,2,92,7,102,105,108,116,101,114,115,0,39,0,193,140,213,146,2,92,6,103,114,111,117,112,115,0,39,0,193,140,213,146,2,92,5,115,111,114,116,115,0,39,0,193,140,213,146,2,92,12,102,105,101,108,100,95,111,114,100,101,114,115,0,8,0,193,140,213,146,2,110,12,118,1,2,105,100,119,6,89,53,52,81,73,115,118,1,2,105,100,119,6,70,114,115,115,74,100,118,1,2,105,100,119,6,89,80,102,105,50,109,118,1,2,105,100,119,6,84,102,117,121,104,84,118,1,2,105,100,119,6,115,111,118,85,116,69,118,1,2,105,100,119,6,54,76,70,72,66,54,118,1,2,105,100,119,6,86,89,52,50,103,49,118,1,2,105,100,119,6,106,87,101,95,116,54,118,1,2,105,100,119,6,55,75,88,95,99,120,118,1,2,105,100,119,6,76,99,121,68,75,106,118,1,2,105,100,119,6,120,69,81,65,111,75,118,1,2,105,100,119,6,52,57,85,69,86,53,39,0,193,140,213,146,2,92,10,114,111,119,95,111,114,100,101,114,115,0,8,0,193,140,213,146,2,123,10,118,2,2,105,100,119,36,50,102,57,52,52,50,50,48,45,57,102,52,53,45,52,48,100,57,45,57,54,98,53,45,101,56,99,48,56,56,56,100,97,102,55,99,6,104,101,105,103,104,116,125,60,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,49,48,52,55,102,50,100,48,45,51,55,53,55,45,52,55,57,57,45,98,99,102,50,45,101,56,102,57,55,52,54,52,100,50,98,53,118,2,2,105,100,119,36,100,100,54,99,56,100,49,51,45,52,56,54,55,45,52,49,99,54,45,56,53,57,57,45,98,56,56,56,51,53,48,102,53,50,101,101,6,104,101,105,103,104,116,125,60,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,49,99,98,57,49,102,97,50,45,54,51,56,100,45,52,48,100,54,45,97,55,99,52,45,51,57,52,102,48,100,56,98,49,57,49,51,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,51,49,56,97,97,52,49,53,45,57,50,97,101,45,52,56,57,97,45,97,49,52,102,45,97,50,52,54,57,50,97,50,101,102,97,54,118,2,2,105,100,119,36,51,99,99,100,49,55,101,48,45,100,55,56,98,45,52,52,101,50,45,97,102,100,49,45,49,98,102,55,99,99,52,57,99,98,53,54,6,104,101,105,103,104,116,125,60,118,2,2,105,100,119,36,48,49,54,48,101,53,56,55,45,52,49,102,52,45,52,51,57,49,45,97,98,98,51,45,100,51,50,50,98,53,50,51,101,100,98,50,6,104,101,105,103,104,116,125,60,118,2,2,105,100,119,36,52,98,53,54,48,99,50,100,45,51,102,51,57,45,52,48,56,54,45,97,97,51,100,45,99,50,53,57,48,100,49,50,57,56,53,48,6,104,101,105,103,104,116,125,60,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,51,97,97,100,99,99,52,49,45,52,98,52,100,45,52,53,55,48,45,97,53,100,101,45,48,54,101,98,101,51,102,52,54,48,101,99,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,56,56,102,97,51,54,98,50,45,54,100,55,50,45,52,52,100,101,45,98,48,100,102,45,100,51,98,50,101,54,100,55,52,52,100,54,12,182,201,218,189,1,0,161,229,168,135,118,231,4,1,136,229,168,135,118,232,4,1,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,48,49,54,48,101,53,56,55,45,52,49,102,52,45,52,51,57,49,45,97,98,98,51,45,100,51,50,50,98,53,50,51,101,100,98,50,161,229,168,135,118,237,4,1,136,229,168,135,118,238,4,1,118,2,2,105,100,119,36,48,49,54,48,101,53,56,55,45,52,49,102,52,45,52,51,57,49,45,97,98,98,51,45,100,51,50,50,98,53,50,51,101,100,98,50,6,104,101,105,103,104,116,125,60,161,229,168,135,118,235,4,1,136,229,168,135,118,236,4,1,118,2,2,105,100,119,36,48,49,54,48,101,53,56,55,45,52,49,102,52,45,52,51,57,49,45,97,98,98,51,45,100,51,50,50,98,53,50,51,101,100,98,50,6,104,101,105,103,104,116,125,60,161,229,168,135,118,233,4,1,136,229,168,135,118,234,4,1,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,48,49,54,48,101,53,56,55,45,52,49,102,52,45,52,51,57,49,45,97,98,98,51,45,100,51,50,50,98,53,50,51,101,100,98,50,161,229,168,135,118,241,4,1,136,229,168,135,118,242,4,1,118,2,2,105,100,119,36,48,49,54,48,101,53,56,55,45,52,49,102,52,45,52,51,57,49,45,97,98,98,51,45,100,51,50,50,98,53,50,51,101,100,98,50,6,104,101,105,103,104,116,125,60,161,229,168,135,118,239,4,1,136,229,168,135,118,240,4,1,118,2,2,105,100,119,36,48,49,54,48,101,53,56,55,45,52,49,102,52,45,52,51,57,49,45,97,98,98,51,45,100,51,50,50,98,53,50,51,101,100,98,50,6,104,101,105,103,104,116,125,60,37,177,178,255,174,1,0,161,187,163,190,240,15,65,1,161,187,163,190,240,15,35,1,161,187,163,190,240,15,32,1,0,2,161,187,163,190,240,15,34,1,161,187,163,190,240,15,37,1,161,187,163,190,240,15,36,1,161,187,163,190,240,15,69,1,39,0,137,227,133,241,2,35,12,99,97,108,99,117,108,97,116,105,111,110,115,0,7,0,177,178,255,174,1,9,1,33,0,177,178,255,174,1,10,2,116,121,1,33,0,177,178,255,174,1,10,2,105,100,1,33,0,177,178,255,174,1,10,8,102,105,101,108,100,95,105,100,1,33,0,177,178,255,174,1,10,17,99,97,108,99,117,108,97,116,105,111,110,95,118,97,108,117,101,1,161,177,178,255,174,1,8,1,135,177,178,255,174,1,10,1,33,0,177,178,255,174,1,16,8,102,105,101,108,100,95,105,100,1,33,0,177,178,255,174,1,16,2,105,100,1,33,0,177,178,255,174,1,16,2,116,121,1,33,0,177,178,255,174,1,16,17,99,97,108,99,117,108,97,116,105,111,110,95,118,97,108,117,101,1,161,177,178,255,174,1,15,1,161,177,178,255,174,1,20,1,161,177,178,255,174,1,18,1,161,177,178,255,174,1,19,1,161,177,178,255,174,1,17,1,161,177,178,255,174,1,21,1,135,177,178,255,174,1,16,1,33,0,177,178,255,174,1,27,2,105,100,1,33,0,177,178,255,174,1,27,2,116,121,1,33,0,177,178,255,174,1,27,17,99,97,108,99,117,108,97,116,105,111,110,95,118,97,108,117,101,1,33,0,177,178,255,174,1,27,8,102,105,101,108,100,95,105,100,1,161,177,178,255,174,1,26,1,135,177,178,255,174,1,27,1,33,0,177,178,255,174,1,33,2,105,100,1,33,0,177,178,255,174,1,33,2,116,121,1,33,0,177,178,255,174,1,33,8,102,105,101,108,100,95,105,100,1,33,0,177,178,255,174,1,33,17,99,97,108,99,117,108,97,116,105,111,110,95,118,97,108,117,101,1,1,165,237,195,173,1,0,161,253,149,229,85,13,8,1,250,147,239,143,1,0,161,158,173,179,170,6,80,2,243,4,229,168,135,118,0,161,168,211,203,155,8,56,1,168,168,211,203,155,8,54,1,119,4,84,101,120,116,161,229,168,135,118,0,1,168,168,211,203,155,8,58,1,122,0,0,0,0,0,0,0,6,39,0,168,211,203,155,8,59,1,54,1,40,0,229,168,135,118,4,3,117,114,108,1,119,0,40,0,229,168,135,118,4,7,99,111,110,116,101,110,116,1,119,0,168,229,168,135,118,2,1,122,0,0,0,0,102,60,204,0,40,0,168,211,203,155,8,60,7,99,111,110,116,101,110,116,1,119,0,40,0,168,211,203,155,8,60,3,117,114,108,1,119,0,161,177,178,255,174,1,0,1,161,187,163,190,240,15,69,1,161,168,211,203,155,8,82,1,161,168,211,203,155,8,80,1,161,229,168,135,118,12,1,161,168,211,203,155,8,84,1,39,0,168,211,203,155,8,85,1,49,1,33,0,229,168,135,118,16,5,115,99,97,108,101,1,33,0,229,168,135,118,16,4,110,97,109,101,1,33,0,229,168,135,118,16,6,102,111,114,109,97,116,1,33,0,229,168,135,118,16,6,115,121,109,98,111,108,1,161,229,168,135,118,14,1,40,0,168,211,203,155,8,86,4,110,97,109,101,1,119,6,78,117,109,98,101,114,40,0,168,211,203,155,8,86,6,115,121,109,98,111,108,1,119,3,82,85,66,40,0,168,211,203,155,8,86,6,102,111,114,109,97,116,1,122,0,0,0,0,0,0,0,0,40,0,168,211,203,155,8,86,5,115,99,97,108,101,1,122,0,0,0,0,0,0,0,0,161,229,168,135,118,10,1,161,229,168,135,118,11,1,161,229,168,135,118,21,1,161,229,168,135,118,17,1,161,229,168,135,118,19,1,161,229,168,135,118,20,1,161,229,168,135,118,18,1,161,229,168,135,118,28,1,161,229,168,135,118,13,1,161,229,168,135,118,33,1,161,229,168,135,118,15,1,161,229,168,135,118,32,1,161,229,168,135,118,30,1,161,229,168,135,118,31,1,161,229,168,135,118,29,1,161,229,168,135,118,35,1,161,229,168,135,118,37,1,161,229,168,135,118,39,1,161,229,168,135,118,38,1,161,229,168,135,118,40,1,161,229,168,135,118,41,1,161,229,168,135,118,44,1,161,229,168,135,118,45,1,161,229,168,135,118,42,1,161,229,168,135,118,43,1,161,187,163,190,240,15,73,1,136,187,163,190,240,15,64,1,118,2,2,105,100,119,36,49,99,98,57,49,102,97,50,45,54,51,56,100,45,52,48,100,54,45,97,55,99,52,45,51,57,52,102,48,100,56,98,49,57,49,51,6,104,101,105,103,104,116,125,60,161,229,168,135,118,27,1,136,137,227,133,241,2,66,1,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,49,99,98,57,49,102,97,50,45,54,51,56,100,45,52,48,100,54,45,97,55,99,52,45,51,57,52,102,48,100,56,98,49,57,49,51,161,229,168,135,118,26,1,136,187,163,190,240,15,23,1,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,49,99,98,57,49,102,97,50,45,54,51,56,100,45,52,48,100,54,45,97,55,99,52,45,51,57,52,102,48,100,56,98,49,57,49,51,161,168,211,203,155,8,66,1,136,137,227,133,241,2,145,1,1,118,2,2,105,100,119,36,49,99,98,57,49,102,97,50,45,54,51,56,100,45,52,48,100,54,45,97,55,99,52,45,51,57,52,102,48,100,56,98,49,57,49,51,6,104,101,105,103,104,116,125,60,161,168,211,203,155,8,62,1,136,137,227,133,241,2,104,1,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,49,99,98,57,49,102,97,50,45,54,51,56,100,45,52,48,100,54,45,97,55,99,52,45,51,57,52,102,48,100,56,98,49,57,49,51,161,168,211,203,155,8,70,1,136,168,211,203,155,8,31,1,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,49,99,98,57,49,102,97,50,45,54,51,56,100,45,52,48,100,54,45,97,55,99,52,45,51,57,52,102,48,100,56,98,49,57,49,51,161,229,168,135,118,46,1,161,229,168,135,118,34,1,161,229,168,135,118,63,1,161,229,168,135,118,36,1,161,229,168,135,118,50,1,161,229,168,135,118,47,1,161,229,168,135,118,49,1,161,229,168,135,118,48,1,161,229,168,135,118,65,1,161,229,168,135,118,69,1,161,229,168,135,118,68,1,161,229,168,135,118,67,1,161,229,168,135,118,70,1,161,229,168,135,118,71,1,161,229,168,135,118,74,1,161,229,168,135,118,73,1,161,229,168,135,118,72,1,161,229,168,135,118,75,1,161,229,168,135,118,76,1,161,229,168,135,118,64,1,161,229,168,135,118,81,1,161,229,168,135,118,66,1,161,229,168,135,118,79,1,161,229,168,135,118,80,1,161,229,168,135,118,78,1,161,229,168,135,118,77,1,161,229,168,135,118,83,1,161,229,168,135,118,88,1,161,229,168,135,118,86,1,161,229,168,135,118,87,1,161,229,168,135,118,85,1,161,229,168,135,118,89,1,161,229,168,135,118,91,1,161,229,168,135,118,93,1,161,229,168,135,118,92,1,161,229,168,135,118,90,1,161,229,168,135,118,94,1,161,229,168,135,118,82,1,161,229,168,135,118,99,1,161,229,168,135,118,84,1,161,229,168,135,118,97,1,161,229,168,135,118,95,1,161,229,168,135,118,96,1,161,229,168,135,118,98,1,161,229,168,135,118,101,1,161,229,168,135,118,104,1,161,229,168,135,118,106,1,161,229,168,135,118,105,1,161,229,168,135,118,103,1,161,229,168,135,118,107,1,161,229,168,135,118,111,1,161,229,168,135,118,109,1,161,229,168,135,118,110,1,161,229,168,135,118,108,1,161,229,168,135,118,112,1,161,229,168,135,118,100,1,161,229,168,135,118,117,1,161,229,168,135,118,102,1,161,229,168,135,118,113,1,161,229,168,135,118,115,1,161,229,168,135,118,116,1,161,229,168,135,118,114,1,161,229,168,135,118,119,1,161,229,168,135,118,122,1,161,229,168,135,118,123,1,161,229,168,135,118,121,1,161,229,168,135,118,124,1,161,229,168,135,118,125,1,161,229,168,135,118,128,1,1,161,229,168,135,118,126,1,161,229,168,135,118,129,1,1,161,229,168,135,118,127,1,161,229,168,135,118,130,1,1,161,229,168,135,118,118,1,161,229,168,135,118,135,1,1,161,229,168,135,118,120,1,161,229,168,135,118,131,1,1,161,229,168,135,118,133,1,1,161,229,168,135,118,132,1,1,161,229,168,135,118,134,1,1,161,229,168,135,118,137,1,1,161,229,168,135,118,140,1,1,161,229,168,135,118,139,1,1,161,229,168,135,118,142,1,1,161,229,168,135,118,141,1,1,161,229,168,135,118,143,1,1,161,229,168,135,118,145,1,1,161,229,168,135,118,146,1,1,161,229,168,135,118,147,1,1,161,229,168,135,118,144,1,1,161,229,168,135,118,148,1,1,161,229,168,135,118,136,1,1,161,229,168,135,118,153,1,1,161,229,168,135,118,138,1,1,161,229,168,135,118,149,1,1,161,229,168,135,118,151,1,1,161,229,168,135,118,150,1,1,161,229,168,135,118,152,1,1,161,229,168,135,118,155,1,1,161,229,168,135,118,160,1,1,161,229,168,135,118,159,1,1,161,229,168,135,118,158,1,1,161,229,168,135,118,157,1,1,161,229,168,135,118,161,1,1,161,229,168,135,118,165,1,1,161,229,168,135,118,162,1,1,161,229,168,135,118,164,1,1,161,229,168,135,118,163,1,1,161,229,168,135,118,166,1,1,161,229,168,135,118,154,1,1,161,229,168,135,118,171,1,1,161,229,168,135,118,156,1,1,161,229,168,135,118,170,1,1,161,229,168,135,118,168,1,1,161,229,168,135,118,167,1,1,161,229,168,135,118,169,1,1,161,229,168,135,118,173,1,1,161,229,168,135,118,175,1,1,161,229,168,135,118,176,1,1,161,229,168,135,118,177,1,1,161,229,168,135,118,178,1,1,161,229,168,135,118,179,1,1,161,229,168,135,118,182,1,1,161,229,168,135,118,180,1,1,161,229,168,135,118,183,1,1,161,229,168,135,118,181,1,1,161,229,168,135,118,184,1,1,161,229,168,135,118,172,1,1,161,229,168,135,118,189,1,1,161,229,168,135,118,174,1,1,161,229,168,135,118,185,1,1,161,229,168,135,118,186,1,1,161,229,168,135,118,188,1,1,161,229,168,135,118,187,1,1,161,229,168,135,118,191,1,1,161,229,168,135,118,194,1,1,161,229,168,135,118,195,1,1,161,229,168,135,118,196,1,1,161,229,168,135,118,193,1,1,161,229,168,135,118,197,1,1,161,229,168,135,118,198,1,1,161,229,168,135,118,200,1,1,161,229,168,135,118,201,1,1,161,229,168,135,118,199,1,1,161,229,168,135,118,202,1,1,161,229,168,135,118,190,1,1,161,229,168,135,118,207,1,1,161,229,168,135,118,192,1,1,161,229,168,135,118,204,1,1,161,229,168,135,118,203,1,1,161,229,168,135,118,205,1,1,161,229,168,135,118,206,1,1,161,229,168,135,118,209,1,1,161,229,168,135,118,212,1,1,161,229,168,135,118,213,1,1,161,229,168,135,118,214,1,1,161,229,168,135,118,211,1,1,161,229,168,135,118,215,1,1,161,229,168,135,118,217,1,1,161,229,168,135,118,218,1,1,161,229,168,135,118,219,1,1,161,229,168,135,118,216,1,1,161,229,168,135,118,220,1,1,161,229,168,135,118,208,1,1,161,229,168,135,118,225,1,1,161,229,168,135,118,210,1,1,161,229,168,135,118,223,1,1,161,229,168,135,118,222,1,1,161,229,168,135,118,221,1,1,161,229,168,135,118,224,1,1,161,229,168,135,118,227,1,1,161,229,168,135,118,229,1,1,161,229,168,135,118,231,1,1,161,229,168,135,118,230,1,1,161,229,168,135,118,232,1,1,161,229,168,135,118,233,1,1,161,229,168,135,118,234,1,1,161,229,168,135,118,237,1,1,161,229,168,135,118,235,1,1,161,229,168,135,118,236,1,1,161,229,168,135,118,238,1,1,161,229,168,135,118,226,1,1,161,229,168,135,118,243,1,1,161,229,168,135,118,228,1,1,161,229,168,135,118,242,1,1,161,229,168,135,118,241,1,1,161,229,168,135,118,239,1,1,161,229,168,135,118,240,1,1,161,229,168,135,118,245,1,1,161,229,168,135,118,249,1,1,161,229,168,135,118,247,1,1,161,229,168,135,118,248,1,1,161,229,168,135,118,250,1,1,161,229,168,135,118,251,1,1,161,229,168,135,118,252,1,1,161,229,168,135,118,253,1,1,161,229,168,135,118,254,1,1,161,229,168,135,118,255,1,1,161,229,168,135,118,128,2,1,161,229,168,135,118,244,1,1,161,229,168,135,118,133,2,1,161,229,168,135,118,246,1,1,161,229,168,135,118,129,2,1,161,229,168,135,118,130,2,1,161,229,168,135,118,132,2,1,161,229,168,135,118,131,2,1,161,229,168,135,118,135,2,1,161,229,168,135,118,138,2,1,161,229,168,135,118,137,2,1,161,229,168,135,118,140,2,1,161,229,168,135,118,139,2,1,161,229,168,135,118,141,2,1,161,229,168,135,118,142,2,1,161,229,168,135,118,143,2,1,161,229,168,135,118,145,2,1,161,229,168,135,118,144,2,1,161,229,168,135,118,146,2,1,161,229,168,135,118,134,2,1,161,229,168,135,118,151,2,1,161,229,168,135,118,136,2,1,161,229,168,135,118,150,2,1,161,229,168,135,118,147,2,1,161,229,168,135,118,148,2,1,161,229,168,135,118,149,2,1,161,229,168,135,118,153,2,1,161,229,168,135,118,157,2,1,161,229,168,135,118,156,2,1,161,229,168,135,118,158,2,1,161,229,168,135,118,155,2,1,161,229,168,135,118,159,2,1,161,229,168,135,118,161,2,1,161,229,168,135,118,163,2,1,161,229,168,135,118,160,2,1,161,229,168,135,118,162,2,1,161,229,168,135,118,164,2,1,161,229,168,135,118,152,2,1,161,229,168,135,118,169,2,1,161,229,168,135,118,154,2,1,161,229,168,135,118,166,2,1,161,229,168,135,118,165,2,1,161,229,168,135,118,168,2,1,161,229,168,135,118,167,2,1,161,229,168,135,118,171,2,1,161,229,168,135,118,173,2,1,161,229,168,135,118,174,2,1,161,229,168,135,118,176,2,1,161,229,168,135,118,175,2,1,161,229,168,135,118,177,2,1,161,229,168,135,118,179,2,1,161,229,168,135,118,178,2,1,161,229,168,135,118,181,2,1,161,229,168,135,118,180,2,1,161,229,168,135,118,182,2,1,161,229,168,135,118,170,2,1,161,229,168,135,118,187,2,1,161,229,168,135,118,172,2,1,161,229,168,135,118,186,2,1,161,229,168,135,118,185,2,1,161,229,168,135,118,184,2,1,161,229,168,135,118,183,2,1,161,229,168,135,118,189,2,1,161,229,168,135,118,194,2,1,161,229,168,135,118,193,2,1,161,229,168,135,118,192,2,1,161,229,168,135,118,191,2,1,161,229,168,135,118,195,2,1,161,229,168,135,118,197,2,1,161,229,168,135,118,196,2,1,161,229,168,135,118,198,2,1,161,229,168,135,118,199,2,1,161,229,168,135,118,200,2,1,161,229,168,135,118,188,2,1,161,229,168,135,118,205,2,1,161,229,168,135,118,190,2,1,161,229,168,135,118,203,2,1,161,229,168,135,118,204,2,1,161,229,168,135,118,202,2,1,161,229,168,135,118,201,2,1,161,229,168,135,118,207,2,1,161,229,168,135,118,210,2,1,161,229,168,135,118,209,2,1,161,229,168,135,118,211,2,1,161,229,168,135,118,212,2,1,161,229,168,135,118,213,2,1,161,229,168,135,118,216,2,1,161,229,168,135,118,217,2,1,161,229,168,135,118,215,2,1,161,229,168,135,118,214,2,1,161,229,168,135,118,218,2,1,161,229,168,135,118,206,2,1,161,229,168,135,118,223,2,1,161,229,168,135,118,208,2,1,161,229,168,135,118,219,2,1,161,229,168,135,118,221,2,1,161,229,168,135,118,220,2,1,161,229,168,135,118,222,2,1,161,229,168,135,118,225,2,1,161,229,168,135,118,229,2,1,161,229,168,135,118,230,2,1,161,229,168,135,118,227,2,1,161,229,168,135,118,228,2,1,161,229,168,135,118,231,2,1,161,229,168,135,118,232,2,1,161,229,168,135,118,235,2,1,161,229,168,135,118,233,2,1,161,229,168,135,118,234,2,1,161,229,168,135,118,236,2,1,161,229,168,135,118,224,2,1,161,229,168,135,118,241,2,1,161,229,168,135,118,226,2,1,161,229,168,135,118,239,2,1,161,229,168,135,118,240,2,1,161,229,168,135,118,238,2,1,161,229,168,135,118,237,2,1,161,229,168,135,118,243,2,1,161,229,168,135,118,246,2,1,161,229,168,135,118,247,2,1,161,229,168,135,118,245,2,1,161,229,168,135,118,248,2,1,161,229,168,135,118,249,2,1,161,229,168,135,118,251,2,1,161,229,168,135,118,252,2,1,161,229,168,135,118,253,2,1,161,229,168,135,118,250,2,1,161,229,168,135,118,254,2,1,161,229,168,135,118,242,2,1,161,229,168,135,118,131,3,1,161,229,168,135,118,244,2,1,161,229,168,135,118,255,2,1,161,229,168,135,118,129,3,1,161,229,168,135,118,130,3,1,161,229,168,135,118,128,3,1,161,229,168,135,118,133,3,1,161,229,168,135,118,135,3,1,161,229,168,135,118,138,3,1,161,229,168,135,118,137,3,1,161,229,168,135,118,136,3,1,161,229,168,135,118,139,3,1,161,229,168,135,118,143,3,1,161,229,168,135,118,140,3,1,161,229,168,135,118,141,3,1,161,229,168,135,118,142,3,1,161,229,168,135,118,144,3,1,161,229,168,135,118,132,3,1,161,229,168,135,118,149,3,1,161,229,168,135,118,134,3,1,161,229,168,135,118,147,3,1,161,229,168,135,118,145,3,1,161,229,168,135,118,148,3,1,161,229,168,135,118,146,3,1,161,229,168,135,118,151,3,1,161,229,168,135,118,155,3,1,161,229,168,135,118,153,3,1,161,229,168,135,118,154,3,1,161,229,168,135,118,156,3,1,161,229,168,135,118,157,3,1,161,229,168,135,118,159,3,1,161,229,168,135,118,160,3,1,161,229,168,135,118,158,3,1,161,229,168,135,118,161,3,1,161,229,168,135,118,162,3,1,161,229,168,135,118,150,3,1,161,229,168,135,118,167,3,1,161,229,168,135,118,152,3,1,161,229,168,135,118,163,3,1,161,229,168,135,118,164,3,1,161,229,168,135,118,166,3,1,161,229,168,135,118,165,3,1,161,229,168,135,118,169,3,1,161,229,168,135,118,173,3,1,161,229,168,135,118,171,3,1,161,229,168,135,118,174,3,1,161,229,168,135,118,172,3,1,161,229,168,135,118,175,3,1,161,229,168,135,118,179,3,1,161,229,168,135,118,177,3,1,161,229,168,135,118,176,3,1,161,229,168,135,118,178,3,1,161,229,168,135,118,180,3,1,161,229,168,135,118,168,3,1,161,229,168,135,118,185,3,1,161,229,168,135,118,170,3,1,161,229,168,135,118,181,3,1,161,229,168,135,118,183,3,1,161,229,168,135,118,184,3,1,161,229,168,135,118,182,3,1,161,229,168,135,118,187,3,1,161,229,168,135,118,191,3,1,161,229,168,135,118,189,3,1,161,229,168,135,118,190,3,1,161,229,168,135,118,192,3,1,161,229,168,135,118,193,3,1,161,229,168,135,118,194,3,1,161,229,168,135,118,197,3,1,161,229,168,135,118,196,3,1,161,229,168,135,118,195,3,1,161,229,168,135,118,198,3,1,161,229,168,135,118,186,3,1,161,229,168,135,118,203,3,1,161,229,168,135,118,188,3,1,161,229,168,135,118,202,3,1,161,229,168,135,118,199,3,1,161,229,168,135,118,200,3,1,161,229,168,135,118,201,3,1,161,229,168,135,118,205,3,1,161,229,168,135,118,208,3,1,161,229,168,135,118,209,3,1,161,229,168,135,118,210,3,1,161,229,168,135,118,207,3,1,161,229,168,135,118,211,3,1,161,229,168,135,118,212,3,1,161,229,168,135,118,215,3,1,161,229,168,135,118,213,3,1,161,229,168,135,118,214,3,1,161,229,168,135,118,216,3,1,161,229,168,135,118,204,3,1,161,229,168,135,118,221,3,1,161,229,168,135,118,206,3,1,161,229,168,135,118,218,3,1,161,229,168,135,118,219,3,1,161,229,168,135,118,217,3,1,161,229,168,135,118,220,3,1,161,229,168,135,118,223,3,1,161,229,168,135,118,225,3,1,161,229,168,135,118,227,3,1,161,229,168,135,118,228,3,1,161,229,168,135,118,226,3,1,161,229,168,135,118,229,3,1,161,229,168,135,118,230,3,1,161,229,168,135,118,233,3,1,161,229,168,135,118,231,3,1,161,229,168,135,118,232,3,1,161,229,168,135,118,234,3,1,161,229,168,135,118,222,3,1,161,229,168,135,118,239,3,1,161,229,168,135,118,224,3,1,161,229,168,135,118,238,3,1,161,229,168,135,118,236,3,1,161,229,168,135,118,235,3,1,161,229,168,135,118,237,3,1,161,229,168,135,118,241,3,1,161,229,168,135,118,244,3,1,161,229,168,135,118,243,3,1,161,229,168,135,118,245,3,1,161,229,168,135,118,246,3,1,161,229,168,135,118,247,3,1,161,229,168,135,118,250,3,1,161,229,168,135,118,249,3,1,161,229,168,135,118,248,3,1,161,229,168,135,118,251,3,1,161,229,168,135,118,252,3,1,161,229,168,135,118,240,3,1,161,229,168,135,118,129,4,1,161,229,168,135,118,242,3,1,161,229,168,135,118,254,3,1,161,229,168,135,118,255,3,1,161,229,168,135,118,128,4,1,161,229,168,135,118,253,3,1,161,229,168,135,118,131,4,1,161,229,168,135,118,134,4,1,161,229,168,135,118,136,4,1,161,229,168,135,118,135,4,1,161,229,168,135,118,133,4,1,161,229,168,135,118,137,4,1,161,229,168,135,118,139,4,1,161,229,168,135,118,140,4,1,161,229,168,135,118,141,4,1,161,229,168,135,118,138,4,1,161,229,168,135,118,142,4,1,161,229,168,135,118,130,4,1,161,229,168,135,118,147,4,1,161,229,168,135,118,132,4,1,161,229,168,135,118,143,4,1,161,229,168,135,118,145,4,1,161,229,168,135,118,146,4,1,161,229,168,135,118,144,4,1,161,229,168,135,118,149,4,1,161,229,168,135,118,151,4,1,161,229,168,135,118,152,4,1,161,229,168,135,118,153,4,1,161,229,168,135,118,154,4,1,161,229,168,135,118,155,4,1,161,229,168,135,118,158,4,1,161,229,168,135,118,157,4,1,161,229,168,135,118,156,4,1,161,229,168,135,118,159,4,1,161,229,168,135,118,160,4,1,161,229,168,135,118,148,4,1,161,229,168,135,118,165,4,1,161,229,168,135,118,150,4,1,161,229,168,135,118,162,4,1,161,229,168,135,118,164,4,1,161,229,168,135,118,163,4,1,161,229,168,135,118,161,4,1,161,229,168,135,118,167,4,1,161,229,168,135,118,169,4,1,161,229,168,135,118,171,4,1,161,229,168,135,118,170,4,1,161,229,168,135,118,172,4,1,161,229,168,135,118,173,4,1,161,229,168,135,118,176,4,1,161,229,168,135,118,177,4,1,161,229,168,135,118,174,4,1,161,229,168,135,118,175,4,1,161,229,168,135,118,178,4,1,161,229,168,135,118,166,4,1,161,229,168,135,118,183,4,1,161,229,168,135,118,168,4,1,161,229,168,135,118,181,4,1,161,229,168,135,118,180,4,1,161,229,168,135,118,182,4,1,161,229,168,135,118,179,4,1,161,229,168,135,118,185,4,1,161,229,168,135,118,188,4,1,161,229,168,135,118,189,4,1,161,229,168,135,118,187,4,1,161,229,168,135,118,190,4,1,161,229,168,135,118,191,4,1,161,229,168,135,118,194,4,1,161,229,168,135,118,192,4,1,161,229,168,135,118,193,4,1,161,229,168,135,118,195,4,1,161,229,168,135,118,196,4,1,161,229,168,135,118,184,4,1,161,229,168,135,118,201,4,1,161,229,168,135,118,186,4,1,161,229,168,135,118,199,4,1,161,229,168,135,118,200,4,1,161,229,168,135,118,198,4,1,161,229,168,135,118,197,4,1,161,229,168,135,118,203,4,1,161,229,168,135,118,207,4,1,161,229,168,135,118,205,4,1,161,229,168,135,118,208,4,1,161,229,168,135,118,206,4,1,161,229,168,135,118,209,4,1,161,229,168,135,118,213,4,1,161,229,168,135,118,212,4,1,161,229,168,135,118,210,4,1,161,229,168,135,118,211,4,1,161,229,168,135,118,51,1,136,229,168,135,118,52,1,118,2,2,105,100,119,36,51,49,56,97,97,52,49,53,45,57,50,97,101,45,52,56,57,97,45,97,49,52,102,45,97,50,52,54,57,50,97,50,101,102,97,54,6,104,101,105,103,104,116,125,60,161,229,168,135,118,53,1,136,229,168,135,118,54,1,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,51,49,56,97,97,52,49,53,45,57,50,97,101,45,52,56,57,97,45,97,49,52,102,45,97,50,52,54,57,50,97,50,101,102,97,54,161,229,168,135,118,55,1,136,229,168,135,118,56,1,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,51,49,56,97,97,52,49,53,45,57,50,97,101,45,52,56,57,97,45,97,49,52,102,45,97,50,52,54,57,50,97,50,101,102,97,54,161,229,168,135,118,57,1,136,229,168,135,118,58,1,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,51,49,56,97,97,52,49,53,45,57,50,97,101,45,52,56,57,97,45,97,49,52,102,45,97,50,52,54,57,50,97,50,101,102,97,54,161,229,168,135,118,59,1,136,229,168,135,118,60,1,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,51,49,56,97,97,52,49,53,45,57,50,97,101,45,52,56,57,97,45,97,49,52,102,45,97,50,52,54,57,50,97,50,101,102,97,54,161,229,168,135,118,61,1,136,229,168,135,118,62,1,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,51,49,56,97,97,52,49,53,45,57,50,97,101,45,52,56,57,97,45,97,49,52,102,45,97,50,52,54,57,50,97,50,101,102,97,54,161,229,168,135,118,219,4,1,136,229,168,135,118,220,4,1,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,51,99,99,100,49,55,101,48,45,100,55,56,98,45,52,52,101,50,45,97,102,100,49,45,49,98,102,55,99,99,52,57,99,98,53,54,161,229,168,135,118,221,4,1,136,229,168,135,118,222,4,1,118,2,2,105,100,119,36,51,99,99,100,49,55,101,48,45,100,55,56,98,45,52,52,101,50,45,97,102,100,49,45,49,98,102,55,99,99,52,57,99,98,53,54,6,104,101,105,103,104,116,125,60,161,229,168,135,118,223,4,1,136,229,168,135,118,224,4,1,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,51,99,99,100,49,55,101,48,45,100,55,56,98,45,52,52,101,50,45,97,102,100,49,45,49,98,102,55,99,99,52,57,99,98,53,54,161,229,168,135,118,225,4,1,136,229,168,135,118,226,4,1,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,51,99,99,100,49,55,101,48,45,100,55,56,98,45,52,52,101,50,45,97,102,100,49,45,49,98,102,55,99,99,52,57,99,98,53,54,161,229,168,135,118,227,4,1,136,229,168,135,118,228,4,1,118,2,2,105,100,119,36,51,99,99,100,49,55,101,48,45,100,55,56,98,45,52,52,101,50,45,97,102,100,49,45,49,98,102,55,99,99,52,57,99,98,53,54,6,104,101,105,103,104,116,125,60,161,229,168,135,118,229,4,1,136,229,168,135,118,230,4,1,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,51,99,99,100,49,55,101,48,45,100,55,56,98,45,52,52,101,50,45,97,102,100,49,45,49,98,102,55,99,99,52,57,99,98,53,54,2,149,154,146,112,0,161,186,204,138,236,4,111,1,161,186,204,138,236,4,117,19,1,211,189,178,91,0,161,252,240,184,224,14,23,80,1,253,149,229,85,0,161,142,215,187,158,14,5,14,1,211,235,145,81,0,161,128,137,148,150,4,38,16,65,128,137,148,150,4,1,0,39,132,238,182,192,14,1,0,5,134,200,133,143,5,1,0,2,135,173,169,205,15,1,0,4,137,227,133,241,2,19,18,1,20,1,22,1,25,1,40,1,53,3,67,1,70,1,86,1,105,1,107,12,124,1,146,1,1,150,1,1,154,1,1,160,1,1,162,1,1,164,1,1,167,1,3,140,242,215,248,4,1,0,35,141,132,223,206,14,1,0,5,142,215,187,158,14,1,0,10,146,198,138,224,6,4,0,5,8,1,11,2,14,2,149,154,146,112,1,0,20,150,194,135,131,8,1,0,12,154,253,168,186,13,1,0,6,157,197,217,249,6,1,0,3,158,173,179,170,6,1,0,81,160,159,229,236,10,1,0,34,162,129,240,225,15,1,0,19,165,237,195,173,1,1,0,8,168,211,203,155,8,17,0,3,15,1,32,1,36,1,40,1,44,1,48,1,54,1,56,1,58,1,62,1,66,1,70,1,74,1,80,1,82,1,84,1,171,216,132,162,10,14,5,1,37,1,39,6,54,1,56,1,58,1,60,1,62,1,64,1,66,1,68,5,79,1,87,1,92,1,174,158,229,225,9,1,0,2,175,150,167,163,14,1,0,4,174,182,200,164,11,1,0,2,177,178,255,174,1,5,0,9,11,5,17,10,28,5,34,4,178,161,242,226,13,1,0,153,1,174,250,146,158,5,1,0,1,180,149,168,150,13,1,0,10,180,132,165,192,8,1,0,1,182,201,218,189,1,6,0,1,2,1,4,1,6,1,8,1,10,1,182,139,168,140,5,1,0,36,183,238,200,180,5,1,0,6,185,145,225,175,8,12,0,182,2,185,2,1,189,2,1,193,2,1,197,2,1,201,2,1,207,2,1,209,2,1,211,2,1,215,2,7,223,2,1,233,2,52,186,204,138,236,4,1,0,118,187,163,190,240,15,9,5,1,24,1,26,12,43,1,65,1,69,1,73,1,81,1,83,1,187,159,219,213,8,1,0,2,188,252,160,180,14,1,0,1,191,215,204,166,13,1,0,12,192,183,207,147,14,1,0,43,193,174,143,180,7,1,0,18,193,140,213,146,2,3,41,1,75,1,77,5,200,168,240,223,7,1,0,2,201,191,253,157,12,1,0,2,200,156,140,203,9,1,0,2,202,170,215,178,7,1,0,24,203,248,208,163,4,1,0,4,206,242,242,141,13,1,0,95,209,142,245,200,15,45,0,55,56,1,58,9,72,55,136,1,28,165,1,1,167,1,3,172,1,4,177,1,2,180,1,232,1,157,3,4,162,3,18,182,3,1,186,3,1,190,3,1,194,3,1,198,3,1,202,3,1,208,3,1,210,3,1,212,3,1,215,3,5,223,3,1,227,3,1,231,3,1,235,3,1,239,3,1,128,4,55,184,4,1,186,4,9,200,4,1,204,4,1,208,4,1,212,4,1,216,4,1,220,4,1,233,4,44,150,5,1,152,5,1,154,5,1,156,5,1,158,5,1,160,5,11,175,5,1,180,5,3,210,221,238,195,8,1,0,20,211,189,178,91,1,0,80,211,235,145,81,1,0,16,216,247,253,206,7,1,0,2,219,179,165,244,8,1,0,4,224,218,133,236,10,1,0,13,227,170,238,211,14,1,0,16,227,250,198,245,13,1,0,5,229,168,135,118,22,0,1,2,1,10,6,17,5,26,26,53,1,55,1,57,1,59,1,61,1,63,157,4,221,4,1,223,4,1,225,4,1,227,4,1,229,4,1,231,4,1,233,4,1,235,4,1,237,4,1,239,4,1,241,4,1,234,232,155,212,3,1,0,1,246,154,200,238,10,1,0,11,247,149,251,192,4,1,0,4,248,220,249,231,6,1,0,29,247,187,192,242,6,1,0,6,250,147,239,143,1,1,0,2,252,220,241,227,14,1,0,60,253,149,229,85,1,0,14,253,223,254,206,11,1,0,2,252,240,184,224,14,1,0,24],"version":0,"object_id":"4c658817-20db-4f56-b7f9-0637a22dfeb6"},"code":0,"message":"Operation completed successfully."} \ No newline at end of file diff --git a/frontend/appflowy_web_app/cypress/fixtures/database/87bc006e-c1eb-47fd-9ac6-e39b17956369.json b/frontend/appflowy_web_app/cypress/fixtures/database/87bc006e-c1eb-47fd-9ac6-e39b17956369.json deleted file mode 100644 index 474a10765c..0000000000 --- a/frontend/appflowy_web_app/cypress/fixtures/database/87bc006e-c1eb-47fd-9ac6-e39b17956369.json +++ /dev/null @@ -1 +0,0 @@ -{"data":{"state_vector":[2,144,224,143,199,14,16,201,175,140,129,8,161,6],"doc_state":[2,2,144,224,143,199,14,0,33,1,4,109,101,116,97,12,108,97,115,116,95,115,121,110,99,95,97,116,15,168,144,224,143,199,14,14,1,122,0,0,0,0,102,97,139,106,230,3,201,175,140,129,8,0,39,1,4,100,97,116,97,8,100,97,116,97,98,97,115,101,1,40,0,201,175,140,129,8,0,2,105,100,1,119,36,56,55,98,99,48,48,54,101,45,99,49,101,98,45,52,55,102,100,45,57,97,99,54,45,101,51,57,98,49,55,57,53,54,51,54,57,39,0,201,175,140,129,8,0,6,102,105,101,108,100,115,1,39,0,201,175,140,129,8,0,5,118,105,101,119,115,1,39,0,201,175,140,129,8,0,5,109,101,116,97,115,1,40,0,201,175,140,129,8,4,3,105,105,100,1,119,36,55,102,50,51,51,98,101,52,45,49,98,52,100,45,52,54,98,50,45,98,99,102,99,45,102,51,52,49,98,56,100,55,53,50,54,55,39,0,201,175,140,129,8,2,6,77,67,57,90,97,69,1,40,0,201,175,140,129,8,6,2,105,100,1,119,6,77,67,57,90,97,69,40,0,201,175,140,129,8,6,4,110,97,109,101,1,119,4,78,97,109,101,40,0,201,175,140,129,8,6,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,97,115,63,40,0,201,175,140,129,8,6,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,97,115,63,40,0,201,175,140,129,8,6,10,105,115,95,112,114,105,109,97,114,121,1,120,40,0,201,175,140,129,8,6,2,116,121,1,122,0,0,0,0,0,0,0,0,39,0,201,175,140,129,8,6,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,201,175,140,129,8,13,1,48,1,40,0,201,175,140,129,8,14,4,100,97,116,97,1,119,0,39,0,201,175,140,129,8,2,6,53,69,90,81,65,87,1,40,0,201,175,140,129,8,16,2,105,100,1,119,6,53,69,90,81,65,87,40,0,201,175,140,129,8,16,4,110,97,109,101,1,119,4,84,121,112,101,40,0,201,175,140,129,8,16,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,97,115,63,33,0,201,175,140,129,8,16,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,40,0,201,175,140,129,8,16,10,105,115,95,112,114,105,109,97,114,121,1,121,40,0,201,175,140,129,8,16,2,116,121,1,122,0,0,0,0,0,0,0,3,39,0,201,175,140,129,8,16,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,201,175,140,129,8,23,1,51,1,33,0,201,175,140,129,8,24,7,99,111,110,116,101,110,116,1,39,0,201,175,140,129,8,2,6,108,73,72,113,101,57,1,40,0,201,175,140,129,8,26,2,105,100,1,119,6,108,73,72,113,101,57,40,0,201,175,140,129,8,26,4,110,97,109,101,1,119,4,68,111,110,101,40,0,201,175,140,129,8,26,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,97,115,63,40,0,201,175,140,129,8,26,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,97,115,63,40,0,201,175,140,129,8,26,10,105,115,95,112,114,105,109,97,114,121,1,121,40,0,201,175,140,129,8,26,2,116,121,1,122,0,0,0,0,0,0,0,5,39,0,201,175,140,129,8,26,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,201,175,140,129,8,33,1,53,1,39,0,201,175,140,129,8,3,36,55,102,50,51,51,98,101,52,45,49,98,52,100,45,52,54,98,50,45,98,99,102,99,45,102,51,52,49,98,56,100,55,53,50,54,55,1,40,0,201,175,140,129,8,35,2,105,100,1,119,36,55,102,50,51,51,98,101,52,45,49,98,52,100,45,52,54,98,50,45,98,99,102,99,45,102,51,52,49,98,56,100,55,53,50,54,55,40,0,201,175,140,129,8,35,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,56,55,98,99,48,48,54,101,45,99,49,101,98,45,52,55,102,100,45,57,97,99,54,45,101,51,57,98,49,55,57,53,54,51,54,57,40,0,201,175,140,129,8,35,4,110,97,109,101,1,119,8,85,110,116,105,116,108,101,100,40,0,201,175,140,129,8,35,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,97,115,63,33,0,201,175,140,129,8,35,11,109,111,100,105,102,105,101,100,95,97,116,1,39,0,201,175,140,129,8,35,15,108,97,121,111,117,116,95,115,101,116,116,105,110,103,115,1,40,0,201,175,140,129,8,35,6,108,97,121,111,117,116,1,122,0,0,0,0,0,0,0,0,39,0,201,175,140,129,8,35,14,102,105,101,108,100,95,115,101,116,116,105,110,103,115,1,39,0,201,175,140,129,8,43,6,108,73,72,113,101,57,1,40,0,201,175,140,129,8,44,4,119,114,97,112,1,120,40,0,201,175,140,129,8,44,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,40,0,201,175,140,129,8,44,5,119,105,100,116,104,1,122,0,0,0,0,0,0,0,150,39,0,201,175,140,129,8,43,6,77,67,57,90,97,69,1,40,0,201,175,140,129,8,48,5,119,105,100,116,104,1,122,0,0,0,0,0,0,0,150,40,0,201,175,140,129,8,48,4,119,114,97,112,1,120,40,0,201,175,140,129,8,48,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,39,0,201,175,140,129,8,43,6,53,69,90,81,65,87,1,40,0,201,175,140,129,8,52,4,119,114,97,112,1,120,40,0,201,175,140,129,8,52,5,119,105,100,116,104,1,122,0,0,0,0,0,0,0,150,40,0,201,175,140,129,8,52,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,39,0,201,175,140,129,8,35,7,102,105,108,116,101,114,115,0,39,0,201,175,140,129,8,35,6,103,114,111,117,112,115,0,39,0,201,175,140,129,8,35,5,115,111,114,116,115,0,39,0,201,175,140,129,8,35,12,102,105,101,108,100,95,111,114,100,101,114,115,0,8,0,201,175,140,129,8,59,3,118,1,2,105,100,119,6,77,67,57,90,97,69,118,1,2,105,100,119,6,53,69,90,81,65,87,118,1,2,105,100,119,6,108,73,72,113,101,57,39,0,201,175,140,129,8,35,10,114,111,119,95,111,114,100,101,114,115,0,8,0,201,175,140,129,8,63,3,118,2,2,105,100,119,36,49,49,49,49,98,49,52,54,45,52,99,54,99,45,52,102,99,54,45,57,53,101,49,45,55,48,99,50,52,54,49,52,55,102,56,102,6,104,101,105,103,104,116,125,60,118,2,2,105,100,119,36,51,101,99,55,98,55,54,99,45,54,56,99,57,45,52,50,55,57,45,57,98,51,51,45,50,51,54,53,51,50,49,101,97,102,52,49,6,104,101,105,103,104,116,125,60,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,57,99,100,101,55,99,49,53,45,51,52,55,99,45,52,52,55,97,45,57,101,97,49,45,55,54,98,99,51,97,56,100,52,101,57,54,161,201,175,140,129,8,40,1,136,201,175,140,129,8,62,1,118,1,2,105,100,119,6,111,121,80,121,97,117,39,0,201,175,140,129,8,43,6,111,121,80,121,97,117,1,40,0,201,175,140,129,8,69,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,39,0,201,175,140,129,8,2,6,111,121,80,121,97,117,1,40,0,201,175,140,129,8,71,2,105,100,1,119,6,111,121,80,121,97,117,33,0,201,175,140,129,8,71,4,110,97,109,101,1,40,0,201,175,140,129,8,71,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,97,115,77,33,0,201,175,140,129,8,71,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,40,0,201,175,140,129,8,71,10,105,115,95,112,114,105,109,97,114,121,1,121,33,0,201,175,140,129,8,71,2,116,121,1,39,0,201,175,140,129,8,71,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,201,175,140,129,8,78,1,48,1,40,0,201,175,140,129,8,79,4,100,97,116,97,1,119,0,161,201,175,140,129,8,75,1,168,201,175,140,129,8,77,1,122,0,0,0,0,0,0,0,1,39,0,201,175,140,129,8,78,1,49,1,40,0,201,175,140,129,8,83,6,115,121,109,98,111,108,1,119,3,82,85,66,40,0,201,175,140,129,8,83,6,102,111,114,109,97,116,1,122,0,0,0,0,0,0,0,0,40,0,201,175,140,129,8,83,5,115,99,97,108,101,1,122,0,0,0,0,0,0,0,0,40,0,201,175,140,129,8,83,4,110,97,109,101,1,119,6,78,117,109,98,101,114,161,201,175,140,129,8,81,1,40,0,201,175,140,129,8,79,6,102,111,114,109,97,116,1,122,0,0,0,0,0,0,0,0,40,0,201,175,140,129,8,79,5,115,99,97,108,101,1,122,0,0,0,0,0,0,0,0,40,0,201,175,140,129,8,79,6,115,121,109,98,111,108,1,119,3,82,85,66,40,0,201,175,140,129,8,79,4,110,97,109,101,1,119,6,78,117,109,98,101,114,161,201,175,140,129,8,67,2,136,201,175,140,129,8,68,1,118,1,2,105,100,119,6,102,116,73,53,52,121,39,0,201,175,140,129,8,43,6,102,116,73,53,52,121,1,40,0,201,175,140,129,8,96,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,39,0,201,175,140,129,8,2,6,102,116,73,53,52,121,1,40,0,201,175,140,129,8,98,2,105,100,1,119,6,102,116,73,53,52,121,33,0,201,175,140,129,8,98,4,110,97,109,101,1,40,0,201,175,140,129,8,98,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,97,115,82,33,0,201,175,140,129,8,98,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,40,0,201,175,140,129,8,98,10,105,115,95,112,114,105,109,97,114,121,1,121,33,0,201,175,140,129,8,98,2,116,121,1,39,0,201,175,140,129,8,98,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,201,175,140,129,8,105,1,48,1,40,0,201,175,140,129,8,106,4,100,97,116,97,1,119,0,161,201,175,140,129,8,102,1,168,201,175,140,129,8,104,1,122,0,0,0,0,0,0,0,4,39,0,201,175,140,129,8,105,1,52,1,33,0,201,175,140,129,8,110,7,99,111,110,116,101,110,116,1,161,201,175,140,129,8,108,1,40,0,201,175,140,129,8,106,7,99,111,110,116,101,110,116,1,119,36,123,34,111,112,116,105,111,110,115,34,58,91,93,44,34,100,105,115,97,98,108,101,95,99,111,108,111,114,34,58,102,97,108,115,101,125,161,201,175,140,129,8,94,1,161,201,175,140,129,8,112,1,161,201,175,140,129,8,111,1,161,201,175,140,129,8,115,1,168,201,175,140,129,8,116,1,119,131,1,123,34,111,112,116,105,111,110,115,34,58,91,123,34,105,100,34,58,34,104,57,106,100,34,44,34,110,97,109,101,34,58,34,111,112,116,105,111,110,45,50,34,44,34,99,111,108,111,114,34,58,34,80,105,110,107,34,125,44,123,34,105,100,34,58,34,111,95,66,104,34,44,34,110,97,109,101,34,58,34,111,112,116,105,111,110,45,49,34,44,34,99,111,108,111,114,34,58,34,80,117,114,112,108,101,34,125,93,44,34,100,105,115,97,98,108,101,95,99,111,108,111,114,34,58,102,97,108,115,101,125,161,201,175,140,129,8,20,1,161,201,175,140,129,8,25,1,168,201,175,140,129,8,119,1,122,0,0,0,0,102,97,115,111,168,201,175,140,129,8,120,1,119,145,1,123,34,111,112,116,105,111,110,115,34,58,91,123,34,105,100,34,58,34,71,102,87,50,34,44,34,110,97,109,101,34,58,34,115,105,110,103,108,101,45,111,112,116,105,111,110,45,50,34,44,34,99,111,108,111,114,34,58,34,80,105,110,107,34,125,44,123,34,105,100,34,58,34,111,102,70,102,34,44,34,110,97,109,101,34,58,34,115,105,110,103,108,101,45,111,112,116,105,111,110,45,49,34,44,34,99,111,108,111,114,34,58,34,80,117,114,112,108,101,34,125,93,44,34,100,105,115,97,98,108,101,95,99,111,108,111,114,34,58,102,97,108,115,101,125,161,201,175,140,129,8,88,1,161,201,175,140,129,8,73,1,161,201,175,140,129,8,123,1,161,201,175,140,129,8,124,1,161,201,175,140,129,8,125,1,161,201,175,140,129,8,126,1,161,201,175,140,129,8,127,1,161,201,175,140,129,8,128,1,1,161,201,175,140,129,8,129,1,1,161,201,175,140,129,8,130,1,1,168,201,175,140,129,8,131,1,1,122,0,0,0,0,102,97,115,117,168,201,175,140,129,8,132,1,1,119,6,110,117,109,98,101,114,161,201,175,140,129,8,117,1,161,201,175,140,129,8,100,1,161,201,175,140,129,8,135,1,1,161,201,175,140,129,8,136,1,1,161,201,175,140,129,8,137,1,1,161,201,175,140,129,8,138,1,1,161,201,175,140,129,8,139,1,1,161,201,175,140,129,8,140,1,1,161,201,175,140,129,8,141,1,1,161,201,175,140,129,8,142,1,1,161,201,175,140,129,8,143,1,1,161,201,175,140,129,8,144,1,1,161,201,175,140,129,8,145,1,1,161,201,175,140,129,8,146,1,1,161,201,175,140,129,8,147,1,1,161,201,175,140,129,8,148,1,1,161,201,175,140,129,8,149,1,1,161,201,175,140,129,8,150,1,1,168,201,175,140,129,8,151,1,1,122,0,0,0,0,102,97,115,124,168,201,175,140,129,8,152,1,1,119,10,109,117,108,116,105,32,116,121,112,101,161,201,175,140,129,8,114,1,136,201,175,140,129,8,95,1,118,1,2,105,100,119,6,87,120,110,102,109,110,39,0,201,175,140,129,8,43,6,87,120,110,102,109,110,1,40,0,201,175,140,129,8,157,1,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,39,0,201,175,140,129,8,2,6,87,120,110,102,109,110,1,40,0,201,175,140,129,8,159,1,2,105,100,1,119,6,87,120,110,102,109,110,33,0,201,175,140,129,8,159,1,4,110,97,109,101,1,40,0,201,175,140,129,8,159,1,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,97,115,126,33,0,201,175,140,129,8,159,1,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,40,0,201,175,140,129,8,159,1,10,105,115,95,112,114,105,109,97,114,121,1,121,33,0,201,175,140,129,8,159,1,2,116,121,1,39,0,201,175,140,129,8,159,1,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,201,175,140,129,8,166,1,1,48,1,40,0,201,175,140,129,8,167,1,4,100,97,116,97,1,119,0,161,201,175,140,129,8,163,1,1,168,201,175,140,129,8,165,1,1,122,0,0,0,0,0,0,0,2,39,0,201,175,140,129,8,166,1,1,50,1,40,0,201,175,140,129,8,171,1,11,116,105,109,101,122,111,110,101,95,105,100,1,119,0,40,0,201,175,140,129,8,171,1,11,100,97,116,101,95,102,111,114,109,97,116,1,122,0,0,0,0,0,0,0,3,40,0,201,175,140,129,8,171,1,11,116,105,109,101,95,102,111,114,109,97,116,1,122,0,0,0,0,0,0,0,1,161,201,175,140,129,8,169,1,1,40,0,201,175,140,129,8,167,1,11,116,105,109,101,95,102,111,114,109,97,116,1,122,0,0,0,0,0,0,0,1,40,0,201,175,140,129,8,167,1,11,100,97,116,101,95,102,111,114,109,97,116,1,122,0,0,0,0,0,0,0,3,40,0,201,175,140,129,8,167,1,11,116,105,109,101,122,111,110,101,95,105,100,1,119,0,161,201,175,140,129,8,155,1,1,161,201,175,140,129,8,175,1,1,161,201,175,140,129,8,161,1,1,161,201,175,140,129,8,180,1,1,161,201,175,140,129,8,181,1,1,161,201,175,140,129,8,182,1,1,161,201,175,140,129,8,183,1,1,161,201,175,140,129,8,184,1,1,161,201,175,140,129,8,185,1,1,161,201,175,140,129,8,186,1,1,161,201,175,140,129,8,187,1,1,161,201,175,140,129,8,188,1,1,161,201,175,140,129,8,189,1,1,161,201,175,140,129,8,190,1,1,161,201,175,140,129,8,191,1,1,168,201,175,140,129,8,192,1,1,122,0,0,0,0,102,97,115,132,168,201,175,140,129,8,193,1,1,119,4,68,97,116,101,161,201,175,140,129,8,179,1,1,129,201,175,140,129,8,156,1,1,33,0,201,175,140,129,8,43,6,67,108,105,104,117,89,1,0,1,33,0,201,175,140,129,8,2,6,67,108,105,104,117,89,1,0,36,161,201,175,140,129,8,196,1,1,136,201,175,140,129,8,197,1,1,118,1,2,105,100,119,6,84,79,87,83,70,104,39,0,201,175,140,129,8,43,6,84,79,87,83,70,104,1,40,0,201,175,140,129,8,239,1,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,39,0,201,175,140,129,8,2,6,84,79,87,83,70,104,1,40,0,201,175,140,129,8,241,1,2,105,100,1,119,6,84,79,87,83,70,104,33,0,201,175,140,129,8,241,1,4,110,97,109,101,1,40,0,201,175,140,129,8,241,1,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,97,115,147,33,0,201,175,140,129,8,241,1,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,40,0,201,175,140,129,8,241,1,10,105,115,95,112,114,105,109,97,114,121,1,121,33,0,201,175,140,129,8,241,1,2,116,121,1,39,0,201,175,140,129,8,241,1,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,201,175,140,129,8,248,1,1,48,1,40,0,201,175,140,129,8,249,1,4,100,97,116,97,1,119,0,161,201,175,140,129,8,245,1,1,168,201,175,140,129,8,247,1,1,122,0,0,0,0,0,0,0,7,39,0,201,175,140,129,8,248,1,1,55,1,161,201,175,140,129,8,237,1,1,136,201,175,140,129,8,238,1,1,118,1,2,105,100,119,6,45,81,77,51,70,50,39,0,201,175,140,129,8,43,6,45,81,77,51,70,50,1,40,0,201,175,140,129,8,128,2,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,39,0,201,175,140,129,8,2,6,45,81,77,51,70,50,1,40,0,201,175,140,129,8,130,2,2,105,100,1,119,6,45,81,77,51,70,50,33,0,201,175,140,129,8,130,2,4,110,97,109,101,1,40,0,201,175,140,129,8,130,2,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,97,115,154,33,0,201,175,140,129,8,130,2,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,40,0,201,175,140,129,8,130,2,10,105,115,95,112,114,105,109,97,114,121,1,121,33,0,201,175,140,129,8,130,2,2,116,121,1,39,0,201,175,140,129,8,130,2,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,201,175,140,129,8,137,2,1,48,1,40,0,201,175,140,129,8,138,2,4,100,97,116,97,1,119,0,161,201,175,140,129,8,134,2,1,168,201,175,140,129,8,136,2,1,122,0,0,0,0,0,0,0,6,39,0,201,175,140,129,8,137,2,1,54,1,40,0,201,175,140,129,8,142,2,7,99,111,110,116,101,110,116,1,119,0,40,0,201,175,140,129,8,142,2,3,117,114,108,1,119,0,161,201,175,140,129,8,140,2,1,40,0,201,175,140,129,8,138,2,3,117,114,108,1,119,0,40,0,201,175,140,129,8,138,2,7,99,111,110,116,101,110,116,1,119,0,161,201,175,140,129,8,254,1,1,161,201,175,140,129,8,145,2,1,161,201,175,140,129,8,132,2,1,161,201,175,140,129,8,149,2,1,161,201,175,140,129,8,150,2,1,161,201,175,140,129,8,151,2,1,161,201,175,140,129,8,152,2,1,161,201,175,140,129,8,153,2,1,161,201,175,140,129,8,154,2,1,161,201,175,140,129,8,155,2,1,161,201,175,140,129,8,156,2,1,161,201,175,140,129,8,157,2,1,161,201,175,140,129,8,158,2,1,168,201,175,140,129,8,159,2,1,122,0,0,0,0,102,97,115,160,168,201,175,140,129,8,160,2,1,119,3,117,114,108,161,201,175,140,129,8,251,1,1,161,201,175,140,129,8,243,1,1,161,201,175,140,129,8,163,2,1,161,201,175,140,129,8,164,2,1,161,201,175,140,129,8,165,2,1,161,201,175,140,129,8,166,2,1,161,201,175,140,129,8,167,2,1,161,201,175,140,129,8,168,2,1,161,201,175,140,129,8,169,2,1,161,201,175,140,129,8,170,2,1,161,201,175,140,129,8,171,2,1,161,201,175,140,129,8,172,2,1,161,201,175,140,129,8,173,2,1,161,201,175,140,129,8,174,2,1,161,201,175,140,129,8,175,2,1,161,201,175,140,129,8,176,2,1,168,201,175,140,129,8,177,2,1,122,0,0,0,0,102,97,115,164,168,201,175,140,129,8,178,2,1,119,9,67,104,101,99,107,108,105,115,116,161,201,175,140,129,8,148,2,1,129,201,175,140,129,8,255,1,1,33,0,201,175,140,129,8,43,6,121,53,95,75,84,100,1,0,1,33,0,201,175,140,129,8,2,6,121,53,95,75,84,100,1,0,9,161,201,175,140,129,8,181,2,3,1,0,201,175,140,129,8,56,1,0,6,161,201,175,140,129,8,197,2,1,129,201,175,140,129,8,198,2,1,0,6,161,201,175,140,129,8,205,2,1,129,201,175,140,129,8,206,2,1,0,6,161,201,175,140,129,8,213,2,1,129,201,175,140,129,8,214,2,1,0,6,129,201,175,140,129,8,222,2,1,0,6,161,201,175,140,129,8,221,2,1,129,201,175,140,129,8,229,2,1,0,6,129,201,175,140,129,8,237,2,1,0,6,161,201,175,140,129,8,236,2,1,129,201,175,140,129,8,244,2,1,0,6,129,201,175,140,129,8,252,2,1,0,6,129,201,175,140,129,8,131,3,1,0,6,161,201,175,140,129,8,251,2,2,129,201,175,140,129,8,138,3,1,0,6,129,201,175,140,129,8,147,3,1,0,6,129,201,175,140,129,8,154,3,1,0,6,161,201,175,140,129,8,146,3,1,129,201,175,140,129,8,161,3,1,0,6,129,201,175,140,129,8,169,3,1,0,6,129,201,175,140,129,8,176,3,1,0,6,129,201,175,140,129,8,183,3,1,0,6,161,201,175,140,129,8,168,3,1,129,201,175,140,129,8,190,3,1,0,6,129,201,175,140,129,8,198,3,1,0,6,129,201,175,140,129,8,205,3,1,0,6,129,201,175,140,129,8,212,3,1,0,6,161,201,175,140,129,8,197,3,1,129,201,175,140,129,8,219,3,1,0,6,129,201,175,140,129,8,227,3,1,0,6,129,201,175,140,129,8,234,3,1,0,6,129,201,175,140,129,8,241,3,1,0,6,161,201,175,140,129,8,226,3,1,129,201,175,140,129,8,248,3,1,0,6,129,201,175,140,129,8,128,4,1,0,6,129,201,175,140,129,8,135,4,1,0,6,129,201,175,140,129,8,142,4,1,0,6,161,201,175,140,129,8,255,3,1,129,201,175,140,129,8,149,4,1,0,6,129,201,175,140,129,8,157,4,1,0,6,129,201,175,140,129,8,164,4,1,0,6,129,201,175,140,129,8,171,4,1,0,6,129,201,175,140,129,8,178,4,1,0,6,161,201,175,140,129,8,156,4,1,129,201,175,140,129,8,185,4,1,0,6,129,201,175,140,129,8,193,4,1,0,6,129,201,175,140,129,8,200,4,1,0,6,129,201,175,140,129,8,207,4,1,0,6,129,201,175,140,129,8,214,4,1,0,6,161,201,175,140,129,8,192,4,1,129,201,175,140,129,8,221,4,1,0,6,129,201,175,140,129,8,229,4,1,0,6,129,201,175,140,129,8,236,4,1,0,6,129,201,175,140,129,8,243,4,1,0,6,129,201,175,140,129,8,250,4,1,0,6,161,201,175,140,129,8,228,4,1,129,201,175,140,129,8,129,5,1,0,6,129,201,175,140,129,8,137,5,1,0,6,129,201,175,140,129,8,144,5,1,0,6,129,201,175,140,129,8,151,5,1,0,6,129,201,175,140,129,8,158,5,1,0,6,129,201,175,140,129,8,165,5,1,0,6,161,201,175,140,129,8,136,5,1,135,201,175,140,129,8,172,5,1,40,0,201,175,140,129,8,180,5,2,105,100,1,119,6,112,85,95,77,67,70,40,0,201,175,140,129,8,180,5,7,99,111,110,116,101,110,116,1,119,3,49,50,51,40,0,201,175,140,129,8,180,5,8,102,105,101,108,100,95,105,100,1,119,6,77,67,57,90,97,69,40,0,201,175,140,129,8,180,5,2,116,121,1,122,0,0,0,0,0,0,0,0,40,0,201,175,140,129,8,180,5,11,102,105,108,116,101,114,95,116,121,112,101,1,122,0,0,0,0,0,0,0,2,40,0,201,175,140,129,8,180,5,9,99,111,110,100,105,116,105,111,110,1,122,0,0,0,0,0,0,0,2,135,201,175,140,129,8,180,5,1,40,0,201,175,140,129,8,187,5,2,105,100,1,119,6,115,120,80,56,104,79,40,0,201,175,140,129,8,187,5,11,102,105,108,116,101,114,95,116,121,112,101,1,122,0,0,0,0,0,0,0,2,40,0,201,175,140,129,8,187,5,2,116,121,1,122,0,0,0,0,0,0,0,3,40,0,201,175,140,129,8,187,5,9,99,111,110,100,105,116,105,111,110,1,122,0,0,0,0,0,0,0,5,40,0,201,175,140,129,8,187,5,8,102,105,101,108,100,95,105,100,1,119,6,53,69,90,81,65,87,40,0,201,175,140,129,8,187,5,7,99,111,110,116,101,110,116,1,119,0,135,201,175,140,129,8,187,5,1,40,0,201,175,140,129,8,194,5,2,105,100,1,119,6,90,76,109,68,81,87,40,0,201,175,140,129,8,194,5,8,102,105,101,108,100,95,105,100,1,119,6,108,73,72,113,101,57,40,0,201,175,140,129,8,194,5,11,102,105,108,116,101,114,95,116,121,112,101,1,122,0,0,0,0,0,0,0,2,40,0,201,175,140,129,8,194,5,2,116,121,1,122,0,0,0,0,0,0,0,5,40,0,201,175,140,129,8,194,5,9,99,111,110,100,105,116,105,111,110,1,122,0,0,0,0,0,0,0,0,40,0,201,175,140,129,8,194,5,7,99,111,110,116,101,110,116,1,119,0,135,201,175,140,129,8,194,5,1,40,0,201,175,140,129,8,201,5,7,99,111,110,116,101,110,116,1,119,3,54,48,48,40,0,201,175,140,129,8,201,5,2,105,100,1,119,6,108,52,83,54,119,71,40,0,201,175,140,129,8,201,5,11,102,105,108,116,101,114,95,116,121,112,101,1,122,0,0,0,0,0,0,0,2,40,0,201,175,140,129,8,201,5,8,102,105,101,108,100,95,105,100,1,119,6,111,121,80,121,97,117,40,0,201,175,140,129,8,201,5,9,99,111,110,100,105,116,105,111,110,1,122,0,0,0,0,0,0,0,2,40,0,201,175,140,129,8,201,5,2,116,121,1,122,0,0,0,0,0,0,0,1,135,201,175,140,129,8,201,5,1,40,0,201,175,140,129,8,208,5,11,102,105,108,116,101,114,95,116,121,112,101,1,122,0,0,0,0,0,0,0,2,40,0,201,175,140,129,8,208,5,9,99,111,110,100,105,116,105,111,110,1,122,0,0,0,0,0,0,0,3,40,0,201,175,140,129,8,208,5,2,116,121,1,122,0,0,0,0,0,0,0,4,40,0,201,175,140,129,8,208,5,7,99,111,110,116,101,110,116,1,119,4,111,95,66,104,40,0,201,175,140,129,8,208,5,2,105,100,1,119,6,103,108,89,79,49,55,40,0,201,175,140,129,8,208,5,8,102,105,101,108,100,95,105,100,1,119,6,102,116,73,53,52,121,135,201,175,140,129,8,208,5,1,40,0,201,175,140,129,8,215,5,11,102,105,108,116,101,114,95,116,121,112,101,1,122,0,0,0,0,0,0,0,2,40,0,201,175,140,129,8,215,5,8,102,105,101,108,100,95,105,100,1,119,6,84,79,87,83,70,104,40,0,201,175,140,129,8,215,5,2,116,121,1,122,0,0,0,0,0,0,0,7,40,0,201,175,140,129,8,215,5,9,99,111,110,100,105,116,105,111,110,1,122,0,0,0,0,0,0,0,0,40,0,201,175,140,129,8,215,5,7,99,111,110,116,101,110,116,1,119,0,40,0,201,175,140,129,8,215,5,2,105,100,1,119,6,122,109,79,103,122,80,161,201,175,140,129,8,179,5,1,136,201,175,140,129,8,66,1,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,49,54,100,97,48,102,54,56,45,102,52,49,52,45,52,99,53,57,45,57,53,101,98,45,51,98,52,53,98,52,98,54,49,100,99,51,161,201,175,140,129,8,222,5,1,136,201,175,140,129,8,223,5,1,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,51,98,53,101,102,56,50,52,45,52,55,53,99,45,52,56,52,56,45,97,99,102,102,45,52,49,56,101,50,53,57,97,51,100,53,51,161,201,175,140,129,8,224,5,1,136,201,175,140,129,8,225,5,1,118,2,2,105,100,119,36,57,101,53,101,102,101,100,48,45,54,50,50,48,45,52,56,98,101,45,56,55,48,52,45,100,56,101,99,48,49,54,54,55,57,54,99,6,104,101,105,103,104,116,125,60,161,201,175,140,129,8,226,5,1,129,201,175,140,129,8,227,5,1,161,201,175,140,129,8,228,5,1,129,201,175,140,129,8,229,5,1,161,201,175,140,129,8,230,5,1,129,201,175,140,129,8,231,5,1,39,0,201,175,140,129,8,3,36,97,55,51,52,97,48,54,56,45,101,55,51,100,45,52,98,52,98,45,56,53,51,99,45,52,100,97,102,102,101,97,51,56,57,99,48,1,40,0,201,175,140,129,8,234,5,2,105,100,1,119,36,97,55,51,52,97,48,54,56,45,101,55,51,100,45,52,98,52,98,45,56,53,51,99,45,52,100,97,102,102,101,97,51,56,57,99,48,40,0,201,175,140,129,8,234,5,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,56,55,98,99,48,48,54,101,45,99,49,101,98,45,52,55,102,100,45,57,97,99,54,45,101,51,57,98,49,55,57,53,54,51,54,57,40,0,201,175,140,129,8,234,5,4,110,97,109,101,1,119,4,71,114,105,100,40,0,201,175,140,129,8,234,5,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,0,0,0,0,33,0,201,175,140,129,8,234,5,11,109,111,100,105,102,105,101,100,95,97,116,1,39,0,201,175,140,129,8,234,5,15,108,97,121,111,117,116,95,115,101,116,116,105,110,103,115,1,40,0,201,175,140,129,8,234,5,6,108,97,121,111,117,116,1,122,0,0,0,0,0,0,0,0,39,0,201,175,140,129,8,234,5,14,102,105,101,108,100,95,115,101,116,116,105,110,103,115,1,39,0,201,175,140,129,8,234,5,7,102,105,108,116,101,114,115,0,39,0,201,175,140,129,8,234,5,6,103,114,111,117,112,115,0,39,0,201,175,140,129,8,234,5,5,115,111,114,116,115,0,39,0,201,175,140,129,8,234,5,12,102,105,101,108,100,95,111,114,100,101,114,115,0,8,0,201,175,140,129,8,246,5,8,118,1,2,105,100,119,6,77,67,57,90,97,69,118,1,2,105,100,119,6,53,69,90,81,65,87,118,1,2,105,100,119,6,108,73,72,113,101,57,118,1,2,105,100,119,6,111,121,80,121,97,117,118,1,2,105,100,119,6,102,116,73,53,52,121,118,1,2,105,100,119,6,87,120,110,102,109,110,118,1,2,105,100,119,6,84,79,87,83,70,104,118,1,2,105,100,119,6,45,81,77,51,70,50,39,0,201,175,140,129,8,234,5,10,114,111,119,95,111,114,100,101,114,115,0,8,0,201,175,140,129,8,255,5,6,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,49,49,49,49,98,49,52,54,45,52,99,54,99,45,52,102,99,54,45,57,53,101,49,45,55,48,99,50,52,54,49,52,55,102,56,102,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,51,101,99,55,98,55,54,99,45,54,56,99,57,45,52,50,55,57,45,57,98,51,51,45,50,51,54,53,51,50,49,101,97,102,52,49,118,2,2,105,100,119,36,57,99,100,101,55,99,49,53,45,51,52,55,99,45,52,52,55,97,45,57,101,97,49,45,55,54,98,99,51,97,56,100,52,101,57,54,6,104,101,105,103,104,116,125,60,118,2,2,105,100,119,36,49,54,100,97,48,102,54,56,45,102,52,49,52,45,52,99,53,57,45,57,53,101,98,45,51,98,52,53,98,52,98,54,49,100,99,51,6,104,101,105,103,104,116,125,60,118,2,2,105,100,119,36,51,98,53,101,102,56,50,52,45,52,55,53,99,45,52,56,52,56,45,97,99,102,102,45,52,49,56,101,50,53,57,97,51,100,53,51,6,104,101,105,103,104,116,125,60,118,2,2,105,100,119,36,57,101,53,101,102,101,100,48,45,54,50,50,48,45,52,56,98,101,45,56,55,48,52,45,100,56,101,99,48,49,54,54,55,57,54,99,6,104,101,105,103,104,116,125,60,129,201,175,140,129,8,133,6,3,161,201,175,140,129,8,232,5,1,161,201,175,140,129,8,239,5,1,161,201,175,140,129,8,137,6,1,161,201,175,140,129,8,138,6,1,161,201,175,140,129,8,139,6,1,161,201,175,140,129,8,140,6,1,161,201,175,140,129,8,141,6,1,7,0,201,175,140,129,8,58,1,40,0,201,175,140,129,8,144,6,2,105,100,1,119,8,115,58,57,78,84,103,95,117,40,0,201,175,140,129,8,144,6,9,99,111,110,100,105,116,105,111,110,1,122,0,0,0,0,0,0,0,0,40,0,201,175,140,129,8,144,6,8,102,105,101,108,100,95,105,100,1,119,6,111,121,80,121,97,117,161,201,175,140,129,8,143,6,1,136,201,175,140,129,8,233,5,1,118,2,2,105,100,119,36,50,52,50,52,57,54,56,57,45,99,97,100,52,45,52,101,53,51,45,56,99,53,101,45,102,57,101,97,101,99,57,98,102,53,53,56,6,104,101,105,103,104,116,125,60,168,201,175,140,129,8,142,6,1,122,0,0,0,0,102,97,116,208,136,201,175,140,129,8,136,6,1,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,50,52,50,52,57,54,56,57,45,99,97,100,52,45,52,101,53,51,45,56,99,53,101,45,102,57,101,97,101,99,57,98,102,53,53,56,161,201,175,140,129,8,148,6,1,135,201,175,140,129,8,144,6,1,33,0,201,175,140,129,8,153,6,2,105,100,1,33,0,201,175,140,129,8,153,6,8,102,105,101,108,100,95,105,100,1,33,0,201,175,140,129,8,153,6,9,99,111,110,100,105,116,105,111,110,1,168,201,175,140,129,8,152,6,1,122,0,0,0,0,102,97,139,96,168,201,175,140,129,8,154,6,1,119,8,115,58,105,108,55,118,85,50,168,201,175,140,129,8,155,6,1,119,6,77,67,57,90,97,69,168,201,175,140,129,8,156,6,1,122,0,0,0,0,0,0,0,1,2,144,224,143,199,14,1,0,15,201,175,140,129,8,49,20,1,25,1,40,1,67,1,73,1,75,1,77,1,81,1,88,1,93,2,100,1,102,1,104,1,108,1,111,2,114,4,119,2,123,10,135,1,18,155,1,1,161,1,1,163,1,1,165,1,1,169,1,1,175,1,1,179,1,15,196,1,42,243,1,1,245,1,1,247,1,1,251,1,1,254,1,1,132,2,1,134,2,1,136,2,1,140,2,1,145,2,1,148,2,13,163,2,16,181,2,255,2,222,5,1,224,5,1,226,5,1,228,5,6,239,5,1,134,6,10,148,6,1,152,6,1,154,6,3],"version":0,"object_id":"87bc006e-c1eb-47fd-9ac6-e39b17956369"},"code":0,"message":"Operation completed successfully."} \ No newline at end of file diff --git a/frontend/appflowy_web_app/cypress/fixtures/database/ad7dc45b-44b5-498f-bfa2-0f43bf05cc0d.json b/frontend/appflowy_web_app/cypress/fixtures/database/ad7dc45b-44b5-498f-bfa2-0f43bf05cc0d.json deleted file mode 100644 index ceebd01573..0000000000 --- a/frontend/appflowy_web_app/cypress/fixtures/database/ad7dc45b-44b5-498f-bfa2-0f43bf05cc0d.json +++ /dev/null @@ -1 +0,0 @@ -{"data":{"state_vector":[16,225,154,253,156,5,2,195,240,220,252,7,5,230,172,170,202,7,8,135,161,218,171,7,6,139,153,229,238,6,2,237,201,168,43,16,238,188,221,160,14,2,244,240,200,227,1,33,181,255,217,196,15,125,212,226,138,162,13,39,151,225,131,140,9,2,248,251,128,198,10,7,149,205,253,206,14,12,251,237,143,129,13,7,158,192,169,36,18,190,203,155,67,2],"doc_state":[16,102,181,255,217,196,15,0,39,1,4,100,97,116,97,8,100,97,116,97,98,97,115,101,1,40,0,181,255,217,196,15,0,2,105,100,1,119,36,97,100,55,100,99,52,53,98,45,52,52,98,53,45,52,57,56,102,45,98,102,97,50,45,48,102,52,51,98,102,48,53,99,99,48,100,39,0,181,255,217,196,15,0,6,102,105,101,108,100,115,1,39,0,181,255,217,196,15,0,5,118,105,101,119,115,1,39,0,181,255,217,196,15,0,5,109,101,116,97,115,1,40,0,181,255,217,196,15,4,3,105,105,100,1,119,36,48,99,101,49,51,52,49,53,45,54,99,99,101,45,52,52,57,55,45,57,52,99,54,45,52,55,53,97,100,57,54,99,50,52,57,101,39,0,181,255,217,196,15,2,6,51,111,45,90,115,109,1,40,0,181,255,217,196,15,6,2,105,100,1,119,6,51,111,45,90,115,109,40,0,181,255,217,196,15,6,4,110,97,109,101,1,119,11,68,101,115,99,114,105,112,116,105,111,110,40,0,181,255,217,196,15,6,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,76,39,162,40,0,181,255,217,196,15,6,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,76,39,162,40,0,181,255,217,196,15,6,10,105,115,95,112,114,105,109,97,114,121,1,120,40,0,181,255,217,196,15,6,2,116,121,1,122,0,0,0,0,0,0,0,0,39,0,181,255,217,196,15,6,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,181,255,217,196,15,13,1,48,1,40,0,181,255,217,196,15,14,4,100,97,116,97,1,119,0,33,0,181,255,217,196,15,2,6,121,52,52,50,48,119,1,0,9,39,0,181,255,217,196,15,3,36,48,99,101,49,51,52,49,53,45,54,99,99,101,45,52,52,57,55,45,57,52,99,54,45,52,55,53,97,100,57,54,99,50,52,57,101,1,40,0,181,255,217,196,15,26,2,105,100,1,119,36,48,99,101,49,51,52,49,53,45,54,99,99,101,45,52,52,57,55,45,57,52,99,54,45,52,55,53,97,100,57,54,99,50,52,57,101,40,0,181,255,217,196,15,26,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,97,100,55,100,99,52,53,98,45,52,52,98,53,45,52,57,56,102,45,98,102,97,50,45,48,102,52,51,98,102,48,53,99,99,48,100,40,0,181,255,217,196,15,26,4,110,97,109,101,1,119,8,85,110,116,105,116,108,101,100,40,0,181,255,217,196,15,26,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,76,39,162,33,0,181,255,217,196,15,26,11,109,111,100,105,102,105,101,100,95,97,116,1,39,0,181,255,217,196,15,26,15,108,97,121,111,117,116,95,115,101,116,116,105,110,103,115,1,39,0,181,255,217,196,15,32,1,49,1,40,0,181,255,217,196,15,33,21,104,105,100,101,95,117,110,103,114,111,117,112,101,100,95,99,111,108,117,109,110,1,121,40,0,181,255,217,196,15,33,22,99,111,108,108,97,112,115,101,95,104,105,100,100,101,110,95,103,114,111,117,112,115,1,121,40,0,181,255,217,196,15,26,6,108,97,121,111,117,116,1,122,0,0,0,0,0,0,0,1,39,0,181,255,217,196,15,26,14,102,105,101,108,100,95,115,101,116,116,105,110,103,115,1,39,0,181,255,217,196,15,37,6,51,111,45,90,115,109,1,40,0,181,255,217,196,15,38,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,40,0,181,255,217,196,15,38,5,119,105,100,116,104,1,122,0,0,0,0,0,0,0,150,40,0,181,255,217,196,15,38,4,119,114,97,112,1,120,33,0,181,255,217,196,15,37,6,121,52,52,50,48,119,1,0,3,39,0,181,255,217,196,15,26,7,102,105,108,116,101,114,115,0,39,0,181,255,217,196,15,26,6,103,114,111,117,112,115,0,39,0,181,255,217,196,15,26,5,115,111,114,116,115,0,39,0,181,255,217,196,15,26,12,102,105,101,108,100,95,111,114,100,101,114,115,0,8,0,181,255,217,196,15,49,1,118,1,2,105,100,119,6,51,111,45,90,115,109,129,181,255,217,196,15,50,1,39,0,181,255,217,196,15,26,10,114,111,119,95,111,114,100,101,114,115,0,8,0,181,255,217,196,15,52,3,118,2,2,105,100,119,36,50,48,56,100,50,52,56,102,45,53,99,48,56,45,52,98,101,53,45,97,48,50,50,45,101,48,97,57,55,99,50,100,55,48,53,101,6,104,101,105,103,104,116,125,60,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,100,51,50,101,52,56,97,52,45,99,102,48,100,45,52,56,97,56,45,57,53,57,57,45,53,51,51,57,97,56,49,53,56,99,53,48,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,56,101,56,55,56,51,48,55,45,97,98,49,101,45,52,50,101,53,45,56,57,55,98,45,56,97,51,101,97,55,56,97,52,53,49,53,161,181,255,217,196,15,31,1,7,0,181,255,217,196,15,47,1,33,0,181,255,217,196,15,57,6,103,114,111,117,112,115,1,33,0,181,255,217,196,15,57,8,102,105,101,108,100,95,105,100,1,33,0,181,255,217,196,15,57,2,116,121,1,33,0,181,255,217,196,15,57,7,99,111,110,116,101,110,116,1,33,0,181,255,217,196,15,57,2,105,100,1,161,181,255,217,196,15,56,1,161,181,255,217,196,15,58,1,0,4,161,181,255,217,196,15,62,1,161,181,255,217,196,15,60,1,161,181,255,217,196,15,61,1,161,181,255,217,196,15,59,1,39,0,181,255,217,196,15,3,36,101,52,99,56,57,52,50,49,45,49,50,98,50,45,52,100,48,50,45,56,54,51,100,45,50,48,57,52,57,101,101,99,57,50,55,49,1,40,0,181,255,217,196,15,73,2,105,100,1,119,36,101,52,99,56,57,52,50,49,45,49,50,98,50,45,52,100,48,50,45,56,54,51,100,45,50,48,57,52,57,101,101,99,57,50,55,49,40,0,181,255,217,196,15,73,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,97,100,55,100,99,52,53,98,45,52,52,98,53,45,52,57,56,102,45,98,102,97,50,45,48,102,52,51,98,102,48,53,99,99,48,100,40,0,181,255,217,196,15,73,4,110,97,109,101,1,119,8,85,110,116,105,116,108,101,100,40,0,181,255,217,196,15,73,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,0,0,0,0,33,0,181,255,217,196,15,73,11,109,111,100,105,102,105,101,100,95,97,116,1,39,0,181,255,217,196,15,73,15,108,97,121,111,117,116,95,115,101,116,116,105,110,103,115,1,40,0,181,255,217,196,15,73,6,108,97,121,111,117,116,1,122,0,0,0,0,0,0,0,0,39,0,181,255,217,196,15,73,14,102,105,101,108,100,95,115,101,116,116,105,110,103,115,1,39,0,181,255,217,196,15,73,7,102,105,108,116,101,114,115,0,39,0,181,255,217,196,15,73,6,103,114,111,117,112,115,0,39,0,181,255,217,196,15,73,5,115,111,114,116,115,0,39,0,181,255,217,196,15,73,12,102,105,101,108,100,95,111,114,100,101,114,115,0,8,0,181,255,217,196,15,85,1,118,1,2,105,100,119,6,51,111,45,90,115,109,129,181,255,217,196,15,86,1,39,0,181,255,217,196,15,73,10,114,111,119,95,111,114,100,101,114,115,0,8,0,181,255,217,196,15,88,3,118,2,2,105,100,119,36,50,48,56,100,50,52,56,102,45,53,99,48,56,45,52,98,101,53,45,97,48,50,50,45,101,48,97,57,55,99,50,100,55,48,53,101,6,104,101,105,103,104,116,125,60,118,2,2,105,100,119,36,100,51,50,101,52,56,97,52,45,99,102,48,100,45,52,56,97,56,45,57,53,57,57,45,53,51,51,57,97,56,49,53,56,99,53,48,6,104,101,105,103,104,116,125,60,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,56,101,56,55,56,51,48,55,45,97,98,49,101,45,52,50,101,53,45,56,57,55,98,45,56,97,51,101,97,55,56,97,52,53,49,53,161,181,255,217,196,15,78,1,161,181,255,217,196,15,63,2,161,181,255,217,196,15,92,1,168,181,255,217,196,15,95,1,122,0,0,0,0,102,76,39,194,136,181,255,217,196,15,87,1,118,1,2,105,100,119,6,81,51,56,55,119,49,39,0,181,255,217,196,15,81,6,81,51,56,55,119,49,1,40,0,181,255,217,196,15,98,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,161,181,255,217,196,15,94,1,136,181,255,217,196,15,51,1,118,1,2,105,100,119,6,81,51,56,55,119,49,39,0,181,255,217,196,15,37,6,81,51,56,55,119,49,1,40,0,181,255,217,196,15,102,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,39,0,181,255,217,196,15,2,6,81,51,56,55,119,49,1,40,0,181,255,217,196,15,104,2,105,100,1,119,6,81,51,56,55,119,49,40,0,181,255,217,196,15,104,4,110,97,109,101,1,119,8,67,104,101,99,107,98,111,120,40,0,181,255,217,196,15,104,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,76,39,194,40,0,181,255,217,196,15,104,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,76,39,194,40,0,181,255,217,196,15,104,10,105,115,95,112,114,105,109,97,114,121,1,121,40,0,181,255,217,196,15,104,2,116,121,1,122,0,0,0,0,0,0,0,5,39,0,181,255,217,196,15,104,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,181,255,217,196,15,111,1,53,1,168,181,255,217,196,15,100,1,122,0,0,0,0,102,76,39,200,168,181,255,217,196,15,69,1,119,8,103,58,85,113,84,54,68,80,168,181,255,217,196,15,70,1,122,0,0,0,0,0,0,0,3,168,181,255,217,196,15,72,1,119,6,121,52,52,50,48,119,168,181,255,217,196,15,71,1,119,0,167,181,255,217,196,15,64,0,8,0,181,255,217,196,15,118,6,118,2,2,105,100,119,6,121,52,52,50,48,119,7,118,105,115,105,98,108,101,120,118,2,2,105,100,119,4,117,76,117,51,7,118,105,115,105,98,108,101,120,118,2,7,118,105,115,105,98,108,101,120,2,105,100,119,4,73,113,105,73,118,2,2,105,100,119,4,82,69,88,119,7,118,105,115,105,98,108,101,120,118,2,7,118,105,115,105,98,108,101,120,2,105,100,119,3,89,101,115,118,2,2,105,100,119,2,78,111,7,118,105,115,105,98,108,101,120,1,149,205,253,206,14,0,161,135,161,218,171,7,5,12,1,238,188,221,160,14,0,161,149,205,253,206,14,11,2,1,212,226,138,162,13,0,161,251,237,143,129,13,6,39,1,251,237,143,129,13,0,161,248,251,128,198,10,6,7,1,248,251,128,198,10,0,161,151,225,131,140,9,1,7,1,151,225,131,140,9,0,161,244,240,200,227,1,32,2,1,195,240,220,252,7,0,161,139,153,229,238,6,1,5,2,230,172,170,202,7,0,161,195,240,220,252,7,4,7,168,230,172,170,202,7,6,1,122,0,0,0,0,102,88,25,34,1,135,161,218,171,7,0,161,225,154,253,156,5,1,6,1,139,153,229,238,6,0,161,158,192,169,36,17,2,1,225,154,253,156,5,0,161,237,201,168,43,15,2,1,244,240,200,227,1,0,33,1,4,109,101,116,97,12,108,97,115,116,95,115,121,110,99,95,97,116,33,1,190,203,155,67,0,161,135,161,218,171,7,5,2,1,237,201,168,43,0,161,212,226,138,162,13,38,16,1,158,192,169,36,0,161,238,188,221,160,14,1,18,16,225,154,253,156,5,1,0,2,195,240,220,252,7,1,0,5,230,172,170,202,7,1,0,7,135,161,218,171,7,1,0,6,139,153,229,238,6,1,0,2,237,201,168,43,1,0,16,238,188,221,160,14,1,0,2,244,240,200,227,1,1,0,33,181,255,217,196,15,10,16,10,31,1,42,4,51,1,56,1,58,15,78,1,87,1,92,4,100,1,212,226,138,162,13,1,0,39,151,225,131,140,9,1,0,2,248,251,128,198,10,1,0,7,149,205,253,206,14,1,0,12,251,237,143,129,13,1,0,7,158,192,169,36,1,0,18,190,203,155,67,1,0,2],"version":0,"object_id":"ad7dc45b-44b5-498f-bfa2-0f43bf05cc0d"},"code":0,"message":"Operation completed successfully."} \ No newline at end of file diff --git a/frontend/appflowy_web_app/cypress/fixtures/database/ce267d12-3b61-4ebb-bb03-d65272f5f817.json b/frontend/appflowy_web_app/cypress/fixtures/database/ce267d12-3b61-4ebb-bb03-d65272f5f817.json deleted file mode 100644 index 428ca72d5b..0000000000 --- a/frontend/appflowy_web_app/cypress/fixtures/database/ce267d12-3b61-4ebb-bb03-d65272f5f817.json +++ /dev/null @@ -1 +0,0 @@ -{"data":{"state_vector":[20,162,178,170,161,1,6,131,222,171,184,9,39,130,208,239,179,11,2,133,224,179,154,9,2,231,138,159,208,10,2,231,217,162,139,1,5,170,249,160,147,7,21,139,202,180,177,14,33,236,192,251,208,2,9,204,220,240,227,3,212,1,145,151,150,143,2,7,146,214,128,188,1,65,243,175,215,198,3,7,212,178,171,164,8,14,149,159,177,202,15,2,149,178,144,155,15,8,247,242,142,226,14,6,249,247,244,162,15,37,219,228,172,146,1,10,251,157,254,151,3,107],"doc_state":[20,1,149,159,177,202,15,0,161,170,249,160,147,7,20,2,22,249,247,244,162,15,0,39,0,251,157,254,151,3,3,36,50,98,102,53,48,99,48,51,45,102,52,49,102,45,52,51,54,51,45,98,53,98,49,45,49,48,49,50,49,54,97,54,99,53,99,99,1,40,0,249,247,244,162,15,0,2,105,100,1,119,36,50,98,102,53,48,99,48,51,45,102,52,49,102,45,52,51,54,51,45,98,53,98,49,45,49,48,49,50,49,54,97,54,99,53,99,99,40,0,249,247,244,162,15,0,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,99,101,50,54,55,100,49,50,45,51,98,54,49,45,52,101,98,98,45,98,98,48,51,45,100,54,53,50,55,50,102,53,102,56,49,55,40,0,249,247,244,162,15,0,4,110,97,109,101,1,119,16,86,105,101,119,32,111,102,32,67,97,108,101,110,100,97,114,40,0,249,247,244,162,15,0,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,0,0,0,0,40,0,249,247,244,162,15,0,11,109,111,100,105,102,105,101,100,95,97,116,1,122,0,0,0,0,0,0,0,0,39,0,249,247,244,162,15,0,15,108,97,121,111,117,116,95,115,101,116,116,105,110,103,115,1,39,0,249,247,244,162,15,6,1,50,1,40,0,249,247,244,162,15,7,9,108,97,121,111,117,116,95,116,121,1,122,0,0,0,0,0,0,0,0,40,0,249,247,244,162,15,7,17,115,104,111,119,95,119,101,101,107,95,110,117,109,98,101,114,115,1,120,40,0,249,247,244,162,15,7,17,102,105,114,115,116,95,100,97,121,95,111,102,95,119,101,101,107,1,122,0,0,0,0,0,0,0,0,40,0,249,247,244,162,15,7,13,115,104,111,119,95,119,101,101,107,101,110,100,115,1,120,40,0,249,247,244,162,15,7,8,102,105,101,108,100,95,105,100,1,119,6,71,115,66,65,97,76,40,0,249,247,244,162,15,0,6,108,97,121,111,117,116,1,122,0,0,0,0,0,0,0,2,39,0,249,247,244,162,15,0,14,102,105,101,108,100,95,115,101,116,116,105,110,103,115,1,39,0,249,247,244,162,15,0,7,102,105,108,116,101,114,115,0,39,0,249,247,244,162,15,0,6,103,114,111,117,112,115,0,39,0,249,247,244,162,15,0,5,115,111,114,116,115,0,39,0,249,247,244,162,15,0,12,102,105,101,108,100,95,111,114,100,101,114,115,0,8,0,249,247,244,162,15,18,11,118,1,2,105,100,119,6,72,95,74,113,85,76,118,1,2,105,100,119,6,55,85,107,117,54,82,118,1,2,105,100,119,6,95,82,45,112,104,105,118,1,2,105,100,119,6,99,78,53,98,120,74,118,1,2,105,100,119,6,71,115,66,65,97,76,118,1,2,105,100,119,6,71,79,80,107,116,118,118,1,2,105,100,119,6,70,99,112,109,80,101,118,1,2,105,100,119,6,112,70,120,57,67,45,118,1,2,105,100,119,6,101,49,98,55,48,88,118,1,2,105,100,119,6,80,78,113,89,102,76,118,1,2,105,100,119,6,75,71,50,113,74,65,39,0,249,247,244,162,15,0,10,114,111,119,95,111,114,100,101,114,115,0,8,0,249,247,244,162,15,30,6,118,2,2,105,100,119,36,55,55,49,55,48,55,57,98,45,48,53,98,54,45,52,97,48,97,45,56,101,101,52,45,52,56,55,51,57,102,98,102,51,97,53,50,6,104,101,105,103,104,116,125,60,118,2,2,105,100,119,36,97,55,51,54,55,52,97,101,45,51,51,48,49,45,52,53,97,51,45,98,56,48,49,45,51,102,49,50,101,54,102,99,98,53,54,54,6,104,101,105,103,104,116,125,60,118,2,2,105,100,119,36,50,49,53,48,99,102,102,54,45,102,102,56,48,45,52,51,51,52,45,56,99,56,97,45,57,52,101,56,50,97,54,52,51,55,57,97,6,104,101,105,103,104,116,125,60,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,53,49,99,102,48,57,48,54,45,97,100,52,54,45,52,100,97,101,45,97,51,98,57,45,50,101,48,48,51,102,56,51,54,56,99,49,118,2,2,105,100,119,36,97,48,48,101,99,102,55,56,45,97,56,50,51,45,52,51,102,49,45,98,53,52,50,45,101,100,48,55,49,51,57,52,97,55,49,55,6,104,101,105,103,104,116,125,60,118,2,2,105,100,119,36,57,50,97,50,49,51,55,101,45,98,48,48,98,45,52,51,56,56,45,56,53,49,102,45,97,48,101,102,99,51,100,101,55,99,97,51,6,104,101,105,103,104,116,125,60,2,149,178,144,155,15,0,161,231,217,162,139,1,4,7,168,149,178,144,155,15,6,1,122,0,0,0,0,102,88,25,34,1,247,242,142,226,14,0,161,243,175,215,198,3,6,6,1,139,202,180,177,14,0,33,1,4,109,101,116,97,12,108,97,115,116,95,115,121,110,99,95,97,116,33,1,130,208,239,179,11,0,161,247,242,142,226,14,5,2,1,231,138,159,208,10,0,161,219,228,172,146,1,9,2,1,131,222,171,184,9,0,161,146,214,128,188,1,64,39,1,133,224,179,154,9,0,161,231,138,159,208,10,1,2,1,212,178,171,164,8,0,161,162,178,170,161,1,5,14,1,170,249,160,147,7,0,161,133,224,179,154,9,1,21,204,1,204,220,240,227,3,0,161,251,157,254,151,3,91,1,136,251,157,254,151,3,74,1,118,2,2,105,100,119,36,97,55,51,54,55,52,97,101,45,51,51,48,49,45,52,53,97,51,45,98,56,48,49,45,51,102,49,50,101,54,102,99,98,53,54,54,6,104,101,105,103,104,116,125,60,161,204,220,240,227,3,0,1,136,204,220,240,227,3,1,1,118,2,2,105,100,119,36,50,49,53,48,99,102,102,54,45,102,102,56,48,45,52,51,51,52,45,56,99,56,97,45,57,52,101,56,50,97,54,52,51,55,57,97,6,104,101,105,103,104,116,125,60,161,204,220,240,227,3,2,1,136,204,220,240,227,3,3,1,118,2,2,105,100,119,36,53,49,99,102,48,57,48,54,45,97,100,52,54,45,52,100,97,101,45,97,51,98,57,45,50,101,48,48,51,102,56,51,54,56,99,49,6,104,101,105,103,104,116,125,60,161,204,220,240,227,3,4,1,136,204,220,240,227,3,5,1,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,97,48,48,101,99,102,55,56,45,97,56,50,51,45,52,51,102,49,45,98,53,52,50,45,101,100,48,55,49,51,57,52,97,55,49,55,39,0,251,157,254,151,3,3,36,54,54,97,54,102,51,98,99,45,99,55,56,102,45,52,102,55,52,45,97,48,57,101,45,48,56,100,52,55,49,55,98,102,49,102,100,1,40,0,204,220,240,227,3,8,2,105,100,1,119,36,54,54,97,54,102,51,98,99,45,99,55,56,102,45,52,102,55,52,45,97,48,57,101,45,48,56,100,52,55,49,55,98,102,49,102,100,40,0,204,220,240,227,3,8,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,99,101,50,54,55,100,49,50,45,51,98,54,49,45,52,101,98,98,45,98,98,48,51,45,100,54,53,50,55,50,102,53,102,56,49,55,40,0,204,220,240,227,3,8,4,110,97,109,101,1,119,4,71,114,105,100,40,0,204,220,240,227,3,8,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,0,0,0,0,33,0,204,220,240,227,3,8,11,109,111,100,105,102,105,101,100,95,97,116,1,39,0,204,220,240,227,3,8,15,108,97,121,111,117,116,95,115,101,116,116,105,110,103,115,1,40,0,204,220,240,227,3,8,6,108,97,121,111,117,116,1,122,0,0,0,0,0,0,0,0,39,0,204,220,240,227,3,8,14,102,105,101,108,100,95,115,101,116,116,105,110,103,115,1,39,0,204,220,240,227,3,8,7,102,105,108,116,101,114,115,0,39,0,204,220,240,227,3,8,6,103,114,111,117,112,115,0,39,0,204,220,240,227,3,8,5,115,111,114,116,115,0,39,0,204,220,240,227,3,8,12,102,105,101,108,100,95,111,114,100,101,114,115,0,8,0,204,220,240,227,3,20,5,118,1,2,105,100,119,6,72,95,74,113,85,76,118,1,2,105,100,119,6,55,85,107,117,54,82,118,1,2,105,100,119,6,95,82,45,112,104,105,118,1,2,105,100,119,6,99,78,53,98,120,74,118,1,2,105,100,119,6,71,115,66,65,97,76,39,0,204,220,240,227,3,8,10,114,111,119,95,111,114,100,101,114,115,0,8,0,204,220,240,227,3,26,5,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,55,55,49,55,48,55,57,98,45,48,53,98,54,45,52,97,48,97,45,56,101,101,52,45,52,56,55,51,57,102,98,102,51,97,53,50,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,97,55,51,54,55,52,97,101,45,51,51,48,49,45,52,53,97,51,45,98,56,48,49,45,51,102,49,50,101,54,102,99,98,53,54,54,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,50,49,53,48,99,102,102,54,45,102,102,56,48,45,52,51,51,52,45,56,99,56,97,45,57,52,101,56,50,97,54,52,51,55,57,97,118,2,2,105,100,119,36,53,49,99,102,48,57,48,54,45,97,100,52,54,45,52,100,97,101,45,97,51,98,57,45,50,101,48,48,51,102,56,51,54,56,99,49,6,104,101,105,103,104,116,125,60,118,2,2,105,100,119,36,97,48,48,101,99,102,55,56,45,97,56,50,51,45,52,51,102,49,45,98,53,52,50,45,101,100,48,55,49,51,57,52,97,55,49,55,6,104,101,105,103,104,116,125,60,161,251,157,254,151,3,85,1,168,251,157,254,151,3,87,1,122,0,0,0,0,0,0,0,6,39,0,251,157,254,151,3,88,1,54,1,40,0,204,220,240,227,3,34,7,99,111,110,116,101,110,116,1,119,0,40,0,204,220,240,227,3,34,3,117,114,108,1,119,0,168,204,220,240,227,3,32,1,122,0,0,0,0,102,77,165,82,40,0,251,157,254,151,3,89,7,99,111,110,116,101,110,116,1,119,0,40,0,251,157,254,151,3,89,3,117,114,108,1,119,0,161,204,220,240,227,3,6,1,161,204,220,240,227,3,13,1,161,204,220,240,227,3,40,1,136,251,157,254,151,3,92,1,118,1,2,105,100,119,6,71,79,80,107,116,118,39,0,251,157,254,151,3,52,6,71,79,80,107,116,118,1,40,0,204,220,240,227,3,44,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,161,204,220,240,227,3,41,1,136,204,220,240,227,3,25,1,118,1,2,105,100,119,6,71,79,80,107,116,118,39,0,204,220,240,227,3,16,6,71,79,80,107,116,118,1,40,0,204,220,240,227,3,48,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,39,0,251,157,254,151,3,2,6,71,79,80,107,116,118,1,40,0,204,220,240,227,3,50,2,105,100,1,119,6,71,79,80,107,116,118,40,0,204,220,240,227,3,50,4,110,97,109,101,1,119,4,84,101,120,116,40,0,204,220,240,227,3,50,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,77,165,99,33,0,204,220,240,227,3,50,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,40,0,204,220,240,227,3,50,10,105,115,95,112,114,105,109,97,114,121,1,121,33,0,204,220,240,227,3,50,2,116,121,1,39,0,204,220,240,227,3,50,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,204,220,240,227,3,57,1,48,1,40,0,204,220,240,227,3,58,4,100,97,116,97,1,119,0,161,204,220,240,227,3,54,1,168,204,220,240,227,3,56,1,122,0,0,0,0,0,0,0,3,39,0,204,220,240,227,3,57,1,51,1,33,0,204,220,240,227,3,62,7,99,111,110,116,101,110,116,1,161,204,220,240,227,3,60,1,40,0,204,220,240,227,3,58,7,99,111,110,116,101,110,116,1,119,36,123,34,111,112,116,105,111,110,115,34,58,91,93,44,34,100,105,115,97,98,108,101,95,99,111,108,111,114,34,58,102,97,108,115,101,125,161,204,220,240,227,3,42,1,161,204,220,240,227,3,46,1,168,251,157,254,151,3,75,1,122,0,0,0,0,102,77,165,108,168,251,157,254,151,3,76,1,119,121,123,34,111,112,116,105,111,110,115,34,58,91,123,34,105,100,34,58,34,110,103,110,85,34,44,34,110,97,109,101,34,58,34,49,49,49,34,44,34,99,111,108,111,114,34,58,34,80,105,110,107,34,125,44,123,34,105,100,34,58,34,73,73,66,100,34,44,34,110,97,109,101,34,58,34,49,50,50,34,44,34,99,111,108,111,114,34,58,34,80,117,114,112,108,101,34,125,93,44,34,100,105,115,97,98,108,101,95,99,111,108,111,114,34,58,102,97,108,115,101,125,161,204,220,240,227,3,64,1,161,204,220,240,227,3,63,1,168,204,220,240,227,3,70,1,122,0,0,0,0,102,77,165,117,168,204,220,240,227,3,71,1,119,121,123,34,111,112,116,105,111,110,115,34,58,91,123,34,105,100,34,58,34,89,101,75,100,34,44,34,110,97,109,101,34,58,34,51,50,49,34,44,34,99,111,108,111,114,34,58,34,80,105,110,107,34,125,44,123,34,105,100,34,58,34,104,77,109,67,34,44,34,110,97,109,101,34,58,34,49,50,51,34,44,34,99,111,108,111,114,34,58,34,80,117,114,112,108,101,34,125,93,44,34,100,105,115,97,98,108,101,95,99,111,108,111,114,34,58,102,97,108,115,101,125,161,204,220,240,227,3,66,1,136,204,220,240,227,3,43,1,118,1,2,105,100,119,6,70,99,112,109,80,101,39,0,251,157,254,151,3,52,6,70,99,112,109,80,101,1,40,0,204,220,240,227,3,76,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,161,204,220,240,227,3,67,1,136,204,220,240,227,3,47,1,118,1,2,105,100,119,6,70,99,112,109,80,101,39,0,204,220,240,227,3,16,6,70,99,112,109,80,101,1,40,0,204,220,240,227,3,80,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,39,0,251,157,254,151,3,2,6,70,99,112,109,80,101,1,40,0,204,220,240,227,3,82,2,105,100,1,119,6,70,99,112,109,80,101,40,0,204,220,240,227,3,82,4,110,97,109,101,1,119,4,84,101,120,116,40,0,204,220,240,227,3,82,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,77,165,120,33,0,204,220,240,227,3,82,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,40,0,204,220,240,227,3,82,10,105,115,95,112,114,105,109,97,114,121,1,121,33,0,204,220,240,227,3,82,2,116,121,1,39,0,204,220,240,227,3,82,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,204,220,240,227,3,89,1,48,1,40,0,204,220,240,227,3,90,4,100,97,116,97,1,119,0,168,204,220,240,227,3,86,1,122,0,0,0,0,102,77,165,125,168,204,220,240,227,3,88,1,122,0,0,0,0,0,0,0,5,39,0,204,220,240,227,3,89,1,53,1,161,204,220,240,227,3,74,1,136,204,220,240,227,3,75,1,118,1,2,105,100,119,6,112,70,120,57,67,45,39,0,251,157,254,151,3,52,6,112,70,120,57,67,45,1,40,0,204,220,240,227,3,97,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,161,204,220,240,227,3,78,1,136,204,220,240,227,3,79,1,118,1,2,105,100,119,6,112,70,120,57,67,45,39,0,204,220,240,227,3,16,6,112,70,120,57,67,45,1,40,0,204,220,240,227,3,101,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,39,0,251,157,254,151,3,2,6,112,70,120,57,67,45,1,40,0,204,220,240,227,3,103,2,105,100,1,119,6,112,70,120,57,67,45,40,0,204,220,240,227,3,103,4,110,97,109,101,1,119,4,84,101,120,116,40,0,204,220,240,227,3,103,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,77,165,129,33,0,204,220,240,227,3,103,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,40,0,204,220,240,227,3,103,10,105,115,95,112,114,105,109,97,114,121,1,121,33,0,204,220,240,227,3,103,2,116,121,1,39,0,204,220,240,227,3,103,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,204,220,240,227,3,110,1,48,1,40,0,204,220,240,227,3,111,4,100,97,116,97,1,119,0,168,204,220,240,227,3,107,1,122,0,0,0,0,102,77,165,135,168,204,220,240,227,3,109,1,122,0,0,0,0,0,0,0,7,39,0,204,220,240,227,3,110,1,55,1,161,204,220,240,227,3,95,1,136,204,220,240,227,3,96,1,118,1,2,105,100,119,6,101,49,98,55,48,88,39,0,251,157,254,151,3,52,6,101,49,98,55,48,88,1,40,0,204,220,240,227,3,118,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,161,204,220,240,227,3,99,1,136,204,220,240,227,3,100,1,118,1,2,105,100,119,6,101,49,98,55,48,88,39,0,204,220,240,227,3,16,6,101,49,98,55,48,88,1,40,0,204,220,240,227,3,122,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,39,0,251,157,254,151,3,2,6,101,49,98,55,48,88,1,40,0,204,220,240,227,3,124,2,105,100,1,119,6,101,49,98,55,48,88,40,0,204,220,240,227,3,124,4,110,97,109,101,1,119,4,84,101,120,116,40,0,204,220,240,227,3,124,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,77,165,151,33,0,204,220,240,227,3,124,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,40,0,204,220,240,227,3,124,10,105,115,95,112,114,105,109,97,114,121,1,121,33,0,204,220,240,227,3,124,2,116,121,1,39,0,204,220,240,227,3,124,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,204,220,240,227,3,131,1,1,48,1,40,0,204,220,240,227,3,132,1,4,100,97,116,97,1,119,0,161,204,220,240,227,3,128,1,1,168,204,220,240,227,3,130,1,1,122,0,0,0,0,0,0,0,8,39,0,204,220,240,227,3,131,1,1,56,1,40,0,204,220,240,227,3,136,1,11,100,97,116,101,95,102,111,114,109,97,116,1,122,0,0,0,0,0,0,0,3,40,0,204,220,240,227,3,136,1,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,8,40,0,204,220,240,227,3,136,1,11,116,105,109,101,95,102,111,114,109,97,116,1,122,0,0,0,0,0,0,0,1,40,0,204,220,240,227,3,136,1,12,105,110,99,108,117,100,101,95,116,105,109,101,1,120,168,204,220,240,227,3,134,1,1,122,0,0,0,0,102,77,165,161,40,0,204,220,240,227,3,132,1,11,116,105,109,101,95,102,111,114,109,97,116,1,122,0,0,0,0,0,0,0,1,40,0,204,220,240,227,3,132,1,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,8,40,0,204,220,240,227,3,132,1,12,105,110,99,108,117,100,101,95,116,105,109,101,1,120,40,0,204,220,240,227,3,132,1,11,100,97,116,101,95,102,111,114,109,97,116,1,122,0,0,0,0,0,0,0,3,161,204,220,240,227,3,116,1,161,204,220,240,227,3,120,1,161,204,220,240,227,3,146,1,1,136,204,220,240,227,3,117,1,118,1,2,105,100,119,6,80,78,113,89,102,76,39,0,251,157,254,151,3,52,6,80,78,113,89,102,76,1,40,0,204,220,240,227,3,150,1,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,161,204,220,240,227,3,147,1,1,136,204,220,240,227,3,121,1,118,1,2,105,100,119,6,80,78,113,89,102,76,39,0,204,220,240,227,3,16,6,80,78,113,89,102,76,1,40,0,204,220,240,227,3,154,1,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,39,0,251,157,254,151,3,2,6,80,78,113,89,102,76,1,40,0,204,220,240,227,3,156,1,2,105,100,1,119,6,80,78,113,89,102,76,40,0,204,220,240,227,3,156,1,4,110,97,109,101,1,119,4,84,101,120,116,40,0,204,220,240,227,3,156,1,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,77,165,164,33,0,204,220,240,227,3,156,1,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,40,0,204,220,240,227,3,156,1,10,105,115,95,112,114,105,109,97,114,121,1,121,33,0,204,220,240,227,3,156,1,2,116,121,1,39,0,204,220,240,227,3,156,1,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,204,220,240,227,3,163,1,1,48,1,40,0,204,220,240,227,3,164,1,4,100,97,116,97,1,119,0,161,204,220,240,227,3,160,1,1,168,204,220,240,227,3,162,1,1,122,0,0,0,0,0,0,0,9,39,0,204,220,240,227,3,163,1,1,57,1,40,0,204,220,240,227,3,168,1,12,105,110,99,108,117,100,101,95,116,105,109,101,1,120,40,0,204,220,240,227,3,168,1,11,116,105,109,101,95,102,111,114,109,97,116,1,122,0,0,0,0,0,0,0,1,40,0,204,220,240,227,3,168,1,11,100,97,116,101,95,102,111,114,109,97,116,1,122,0,0,0,0,0,0,0,3,40,0,204,220,240,227,3,168,1,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,9,168,204,220,240,227,3,166,1,1,122,0,0,0,0,102,77,165,166,40,0,204,220,240,227,3,164,1,11,116,105,109,101,95,102,111,114,109,97,116,1,122,0,0,0,0,0,0,0,1,40,0,204,220,240,227,3,164,1,11,100,97,116,101,95,102,111,114,109,97,116,1,122,0,0,0,0,0,0,0,3,40,0,204,220,240,227,3,164,1,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,9,40,0,204,220,240,227,3,164,1,12,105,110,99,108,117,100,101,95,116,105,109,101,1,120,161,204,220,240,227,3,148,1,1,161,204,220,240,227,3,152,1,1,161,204,220,240,227,3,178,1,1,136,204,220,240,227,3,149,1,1,118,1,2,105,100,119,6,75,71,50,113,74,65,39,0,251,157,254,151,3,52,6,75,71,50,113,74,65,1,40,0,204,220,240,227,3,182,1,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,161,204,220,240,227,3,179,1,1,136,204,220,240,227,3,153,1,1,118,1,2,105,100,119,6,75,71,50,113,74,65,39,0,204,220,240,227,3,16,6,75,71,50,113,74,65,1,40,0,204,220,240,227,3,186,1,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,39,0,251,157,254,151,3,2,6,75,71,50,113,74,65,1,40,0,204,220,240,227,3,188,1,2,105,100,1,119,6,75,71,50,113,74,65,40,0,204,220,240,227,3,188,1,4,110,97,109,101,1,119,4,84,101,120,116,40,0,204,220,240,227,3,188,1,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,77,165,168,33,0,204,220,240,227,3,188,1,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,40,0,204,220,240,227,3,188,1,10,105,115,95,112,114,105,109,97,114,121,1,121,33,0,204,220,240,227,3,188,1,2,116,121,1,39,0,204,220,240,227,3,188,1,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,204,220,240,227,3,195,1,1,48,1,40,0,204,220,240,227,3,196,1,4,100,97,116,97,1,119,0,161,204,220,240,227,3,192,1,1,168,204,220,240,227,3,194,1,1,122,0,0,0,0,0,0,0,10,39,0,204,220,240,227,3,195,1,2,49,48,1,33,0,204,220,240,227,3,200,1,11,100,97,116,97,98,97,115,101,95,105,100,1,161,204,220,240,227,3,198,1,1,40,0,204,220,240,227,3,196,1,11,100,97,116,97,98,97,115,101,95,105,100,1,119,0,161,204,220,240,227,3,180,1,1,161,204,220,240,227,3,184,1,1,168,204,220,240,227,3,202,1,1,122,0,0,0,0,102,77,165,173,168,204,220,240,227,3,201,1,1,119,36,97,100,55,100,99,52,53,98,45,52,52,98,53,45,52,57,56,102,45,98,102,97,50,45,48,102,52,51,98,102,48,53,99,99,48,100,168,204,220,240,227,3,204,1,1,122,0,0,0,0,102,77,187,135,136,204,220,240,227,3,7,1,118,2,6,104,101,105,103,104,116,125,60,2,105,100,119,36,57,50,97,50,49,51,55,101,45,98,48,48,98,45,52,51,56,56,45,56,53,49,102,45,97,48,101,102,99,51,100,101,55,99,97,51,168,204,220,240,227,3,205,1,1,122,0,0,0,0,102,77,187,135,136,204,220,240,227,3,31,1,118,2,2,105,100,119,36,57,50,97,50,49,51,55,101,45,98,48,48,98,45,52,51,56,56,45,56,53,49,102,45,97,48,101,102,99,51,100,101,55,99,97,51,6,104,101,105,103,104,116,125,60,1,243,175,215,198,3,0,161,236,192,251,208,2,8,7,105,251,157,254,151,3,0,39,1,4,100,97,116,97,8,100,97,116,97,98,97,115,101,1,40,0,251,157,254,151,3,0,2,105,100,1,119,36,99,101,50,54,55,100,49,50,45,51,98,54,49,45,52,101,98,98,45,98,98,48,51,45,100,54,53,50,55,50,102,53,102,56,49,55,39,0,251,157,254,151,3,0,6,102,105,101,108,100,115,1,39,0,251,157,254,151,3,0,5,118,105,101,119,115,1,39,0,251,157,254,151,3,0,5,109,101,116,97,115,1,40,0,251,157,254,151,3,4,3,105,105,100,1,119,36,101,101,51,97,101,56,99,101,45,57,53,57,97,45,52,100,102,51,45,56,55,51,52,45,52,48,98,53,51,53,102,102,56,56,101,51,39,0,251,157,254,151,3,2,6,72,95,74,113,85,76,1,40,0,251,157,254,151,3,6,2,105,100,1,119,6,72,95,74,113,85,76,40,0,251,157,254,151,3,6,4,110,97,109,101,1,119,5,84,105,116,108,101,40,0,251,157,254,151,3,6,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,76,101,231,40,0,251,157,254,151,3,6,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,76,101,231,40,0,251,157,254,151,3,6,10,105,115,95,112,114,105,109,97,114,121,1,120,40,0,251,157,254,151,3,6,2,116,121,1,122,0,0,0,0,0,0,0,0,39,0,251,157,254,151,3,6,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,251,157,254,151,3,13,1,48,1,40,0,251,157,254,151,3,14,4,100,97,116,97,1,119,0,39,0,251,157,254,151,3,2,6,55,85,107,117,54,82,1,40,0,251,157,254,151,3,16,2,105,100,1,119,6,55,85,107,117,54,82,40,0,251,157,254,151,3,16,4,110,97,109,101,1,119,4,68,97,116,101,40,0,251,157,254,151,3,16,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,76,101,231,40,0,251,157,254,151,3,16,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,76,101,231,40,0,251,157,254,151,3,16,10,105,115,95,112,114,105,109,97,114,121,1,121,40,0,251,157,254,151,3,16,2,116,121,1,122,0,0,0,0,0,0,0,2,39,0,251,157,254,151,3,16,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,251,157,254,151,3,23,1,50,1,40,0,251,157,254,151,3,24,11,100,97,116,101,95,102,111,114,109,97,116,1,122,0,0,0,0,0,0,0,3,40,0,251,157,254,151,3,24,11,116,105,109,101,95,102,111,114,109,97,116,1,122,0,0,0,0,0,0,0,1,40,0,251,157,254,151,3,24,11,116,105,109,101,122,111,110,101,95,105,100,1,119,0,39,0,251,157,254,151,3,2,6,95,82,45,112,104,105,1,40,0,251,157,254,151,3,28,2,105,100,1,119,6,95,82,45,112,104,105,40,0,251,157,254,151,3,28,4,110,97,109,101,1,119,4,84,97,103,115,40,0,251,157,254,151,3,28,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,76,101,231,33,0,251,157,254,151,3,28,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,40,0,251,157,254,151,3,28,10,105,115,95,112,114,105,109,97,114,121,1,121,40,0,251,157,254,151,3,28,2,116,121,1,122,0,0,0,0,0,0,0,4,39,0,251,157,254,151,3,28,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,251,157,254,151,3,35,1,52,1,33,0,251,157,254,151,3,36,7,99,111,110,116,101,110,116,1,39,0,251,157,254,151,3,3,36,101,101,51,97,101,56,99,101,45,57,53,57,97,45,52,100,102,51,45,56,55,51,52,45,52,48,98,53,51,53,102,102,56,56,101,51,1,40,0,251,157,254,151,3,38,2,105,100,1,119,36,101,101,51,97,101,56,99,101,45,57,53,57,97,45,52,100,102,51,45,56,55,51,52,45,52,48,98,53,51,53,102,102,56,56,101,51,40,0,251,157,254,151,3,38,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,99,101,50,54,55,100,49,50,45,51,98,54,49,45,52,101,98,98,45,98,98,48,51,45,100,54,53,50,55,50,102,53,102,56,49,55,40,0,251,157,254,151,3,38,4,110,97,109,101,1,119,8,85,110,116,105,116,108,101,100,40,0,251,157,254,151,3,38,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,76,101,231,33,0,251,157,254,151,3,38,11,109,111,100,105,102,105,101,100,95,97,116,1,39,0,251,157,254,151,3,38,15,108,97,121,111,117,116,95,115,101,116,116,105,110,103,115,1,39,0,251,157,254,151,3,44,1,50,1,40,0,251,157,254,151,3,45,13,115,104,111,119,95,119,101,101,107,101,110,100,115,1,120,40,0,251,157,254,151,3,45,9,108,97,121,111,117,116,95,116,121,1,122,0,0,0,0,0,0,0,0,40,0,251,157,254,151,3,45,8,102,105,101,108,100,95,105,100,1,119,6,55,85,107,117,54,82,40,0,251,157,254,151,3,45,17,102,105,114,115,116,95,100,97,121,95,111,102,95,119,101,101,107,1,122,0,0,0,0,0,0,0,0,40,0,251,157,254,151,3,45,17,115,104,111,119,95,119,101,101,107,95,110,117,109,98,101,114,115,1,120,40,0,251,157,254,151,3,38,6,108,97,121,111,117,116,1,122,0,0,0,0,0,0,0,2,39,0,251,157,254,151,3,38,14,102,105,101,108,100,95,115,101,116,116,105,110,103,115,1,39,0,251,157,254,151,3,52,6,72,95,74,113,85,76,1,40,0,251,157,254,151,3,53,5,119,105,100,116,104,1,122,0,0,0,0,0,0,0,150,40,0,251,157,254,151,3,53,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,0,40,0,251,157,254,151,3,53,4,119,114,97,112,1,120,39,0,251,157,254,151,3,52,6,55,85,107,117,54,82,1,40,0,251,157,254,151,3,57,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,40,0,251,157,254,151,3,57,5,119,105,100,116,104,1,122,0,0,0,0,0,0,0,150,40,0,251,157,254,151,3,57,4,119,114,97,112,1,120,39,0,251,157,254,151,3,52,6,95,82,45,112,104,105,1,40,0,251,157,254,151,3,61,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,40,0,251,157,254,151,3,61,5,119,105,100,116,104,1,122,0,0,0,0,0,0,0,150,40,0,251,157,254,151,3,61,4,119,114,97,112,1,120,39,0,251,157,254,151,3,38,7,102,105,108,116,101,114,115,0,39,0,251,157,254,151,3,38,6,103,114,111,117,112,115,0,39,0,251,157,254,151,3,38,5,115,111,114,116,115,0,39,0,251,157,254,151,3,38,12,102,105,101,108,100,95,111,114,100,101,114,115,0,8,0,251,157,254,151,3,68,3,118,1,2,105,100,119,6,72,95,74,113,85,76,118,1,2,105,100,119,6,55,85,107,117,54,82,118,1,2,105,100,119,6,95,82,45,112,104,105,39,0,251,157,254,151,3,38,10,114,111,119,95,111,114,100,101,114,115,0,161,251,157,254,151,3,43,1,8,0,251,157,254,151,3,72,1,118,2,2,105,100,119,36,55,55,49,55,48,55,57,98,45,48,53,98,54,45,52,97,48,97,45,56,101,101,52,45,52,56,55,51,57,102,98,102,51,97,53,50,6,104,101,105,103,104,116,125,60,161,251,157,254,151,3,32,1,161,251,157,254,151,3,37,1,161,251,157,254,151,3,73,1,136,251,157,254,151,3,71,1,118,1,2,105,100,119,6,99,78,53,98,120,74,39,0,251,157,254,151,3,52,6,99,78,53,98,120,74,1,40,0,251,157,254,151,3,79,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,39,0,251,157,254,151,3,2,6,99,78,53,98,120,74,1,40,0,251,157,254,151,3,81,2,105,100,1,119,6,99,78,53,98,120,74,40,0,251,157,254,151,3,81,4,110,97,109,101,1,119,4,84,101,120,116,40,0,251,157,254,151,3,81,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,76,102,8,33,0,251,157,254,151,3,81,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,40,0,251,157,254,151,3,81,10,105,115,95,112,114,105,109,97,114,121,1,121,33,0,251,157,254,151,3,81,2,116,121,1,39,0,251,157,254,151,3,81,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,251,157,254,151,3,88,1,48,1,40,0,251,157,254,151,3,89,4,100,97,116,97,1,119,0,161,251,157,254,151,3,77,1,136,251,157,254,151,3,78,1,118,1,2,105,100,119,6,71,115,66,65,97,76,39,0,251,157,254,151,3,52,6,71,115,66,65,97,76,1,40,0,251,157,254,151,3,93,10,118,105,115,105,98,105,108,105,116,121,1,122,0,0,0,0,0,0,0,1,39,0,251,157,254,151,3,2,6,71,115,66,65,97,76,1,40,0,251,157,254,151,3,95,2,105,100,1,119,6,71,115,66,65,97,76,40,0,251,157,254,151,3,95,4,110,97,109,101,1,119,4,68,97,116,101,40,0,251,157,254,151,3,95,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,76,102,24,40,0,251,157,254,151,3,95,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,76,102,24,40,0,251,157,254,151,3,95,10,105,115,95,112,114,105,109,97,114,121,1,121,40,0,251,157,254,151,3,95,2,116,121,1,122,0,0,0,0,0,0,0,2,39,0,251,157,254,151,3,95,11,116,121,112,101,95,111,112,116,105,111,110,1,39,0,251,157,254,151,3,102,1,50,1,40,0,251,157,254,151,3,103,11,116,105,109,101,95,102,111,114,109,97,116,1,122,0,0,0,0,0,0,0,0,40,0,251,157,254,151,3,103,11,116,105,109,101,122,111,110,101,95,105,100,1,119,0,40,0,251,157,254,151,3,103,11,100,97,116,101,95,102,111,114,109,97,116,1,122,0,0,0,0,0,0,0,1,1,236,192,251,208,2,0,161,145,151,150,143,2,6,9,1,145,151,150,143,2,0,161,131,222,171,184,9,38,7,1,146,214,128,188,1,0,161,212,178,171,164,8,13,65,1,162,178,170,161,1,0,161,139,202,180,177,14,32,6,2,219,228,172,146,1,0,161,247,242,142,226,14,5,1,161,130,208,239,179,11,1,9,1,231,217,162,139,1,0,161,149,159,177,202,15,1,5,19,130,208,239,179,11,1,0,2,162,178,170,161,1,1,0,6,131,222,171,184,9,1,0,39,133,224,179,154,9,1,0,2,231,217,162,139,1,1,0,5,231,138,159,208,10,1,0,2,170,249,160,147,7,1,0,21,139,202,180,177,14,1,0,33,236,192,251,208,2,1,0,9,204,220,240,227,3,39,0,1,2,1,4,1,6,1,13,1,32,1,40,3,46,1,54,1,56,1,60,1,63,2,66,2,70,2,74,1,78,1,86,1,88,1,95,1,99,1,107,1,109,1,116,1,120,1,128,1,1,130,1,1,134,1,1,146,1,3,152,1,1,160,1,1,162,1,1,166,1,1,178,1,3,184,1,1,192,1,1,194,1,1,198,1,1,201,1,2,204,1,2,145,151,150,143,2,1,0,7,146,214,128,188,1,1,0,65,243,175,215,198,3,1,0,7,212,178,171,164,8,1,0,14,149,159,177,202,15,1,0,2,149,178,144,155,15,1,0,7,247,242,142,226,14,1,0,6,219,228,172,146,1,1,0,10,251,157,254,151,3,8,32,1,37,1,43,1,73,1,75,3,85,1,87,1,91,1],"version":0,"object_id":"ce267d12-3b61-4ebb-bb03-d65272f5f817"},"code":0,"message":"Operation completed successfully."} \ No newline at end of file diff --git a/frontend/appflowy_web_app/cypress/fixtures/database/rows/4c658817-20db-4f56-b7f9-0637a22dfeb6.json b/frontend/appflowy_web_app/cypress/fixtures/database/rows/4c658817-20db-4f56-b7f9-0637a22dfeb6.json deleted file mode 100644 index a55622fdfb..0000000000 --- a/frontend/appflowy_web_app/cypress/fixtures/database/rows/4c658817-20db-4f56-b7f9-0637a22dfeb6.json +++ /dev/null @@ -1 +0,0 @@ -{"2f944220-9f45-40d9-96b5-e8c0888daf7c":[58,1,230,232,236,161,15,0,161,147,212,241,172,2,1,6,1,144,227,205,159,15,0,161,150,141,187,97,5,10,17,186,193,182,130,15,0,161,237,231,215,147,4,0,1,39,0,128,159,198,124,8,6,115,111,118,85,116,69,1,40,0,186,193,182,130,15,1,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,67,9,22,40,0,186,193,182,130,15,1,4,100,97,116,97,1,119,0,40,0,186,193,182,130,15,1,13,101,110,100,95,116,105,109,101,115,116,97,109,112,1,119,0,40,0,186,193,182,130,15,1,12,105,110,99,108,117,100,101,95,116,105,109,101,1,121,40,0,186,193,182,130,15,1,8,105,115,95,114,97,110,103,101,1,121,40,0,186,193,182,130,15,1,11,114,101,109,105,110,100,101,114,95,105,100,1,119,0,40,0,186,193,182,130,15,1,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,2,40,0,186,193,182,130,15,1,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,67,9,22,161,186,193,182,130,15,0,1,39,0,128,159,198,124,8,6,106,87,101,95,116,54,1,40,0,186,193,182,130,15,11,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,67,52,223,39,0,186,193,182,130,15,11,4,100,97,116,97,0,8,0,186,193,182,130,15,13,1,119,36,50,102,57,52,52,50,50,48,45,57,102,52,53,45,52,48,100,57,45,57,54,98,53,45,101,56,99,48,56,56,56,100,97,102,55,99,40,0,186,193,182,130,15,11,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,10,40,0,186,193,182,130,15,11,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,67,52,223,1,209,188,177,215,14,0,161,205,239,215,19,1,26,1,167,253,145,211,14,0,161,171,194,204,160,8,3,6,1,170,128,188,181,14,0,161,156,191,219,249,8,1,2,1,131,157,176,150,14,0,161,147,200,155,248,11,63,8,1,229,227,137,140,13,0,161,149,252,241,115,5,5,1,159,212,134,166,12,0,161,134,132,140,164,1,3,10,1,195,193,152,153,12,0,161,222,161,195,148,2,1,24,1,133,166,132,140,12,0,161,159,241,200,168,2,3,5,1,147,200,155,248,11,0,161,254,149,155,218,7,0,64,5,192,252,137,204,11,0,161,139,151,195,245,8,6,1,161,139,151,195,245,8,8,2,168,192,252,137,204,11,0,1,121,161,192,252,137,204,11,2,1,168,192,252,137,204,11,4,1,122,0,0,0,0,102,78,233,162,1,192,211,236,177,11,0,161,245,221,232,242,6,22,8,2,147,146,137,224,10,0,33,1,4,109,101,116,97,12,108,97,115,116,95,115,121,110,99,95,97,116,113,161,147,146,137,224,10,112,6,19,213,209,142,213,10,0,161,237,231,215,147,4,0,1,39,0,128,159,198,124,8,6,54,76,70,72,66,54,1,40,0,213,209,142,213,10,1,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,60,204,6,33,0,213,209,142,213,10,1,10,102,105,101,108,100,95,116,121,112,101,1,33,0,213,209,142,213,10,1,4,100,97,116,97,1,33,0,213,209,142,213,10,1,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,161,213,209,142,213,10,0,1,168,213,209,142,213,10,3,1,122,0,0,0,0,0,0,0,6,168,213,209,142,213,10,4,1,119,11,97,112,112,102,108,111,119,121,46,105,111,168,213,209,142,213,10,5,1,122,0,0,0,0,102,60,204,7,161,213,209,142,213,10,6,1,33,0,128,159,198,124,8,6,106,87,101,95,116,54,1,0,8,161,213,209,142,213,10,10,1,0,7,161,213,209,142,213,10,20,1,0,7,161,213,209,142,213,10,28,1,0,7,1,252,175,185,165,10,0,161,157,218,228,199,8,1,2,1,252,249,181,162,10,0,161,135,190,197,222,2,11,6,1,234,188,148,129,10,0,161,131,157,176,150,14,7,10,1,243,149,144,209,9,0,161,151,148,151,141,4,7,19,4,171,231,189,153,9,0,161,235,147,219,255,2,26,1,0,4,161,171,231,189,153,9,0,1,0,5,1,156,191,219,249,8,0,161,238,201,163,245,7,15,2,6,139,151,195,245,8,0,33,0,128,159,198,124,1,36,49,101,100,98,98,102,101,100,45,101,52,51,54,45,53,98,55,51,45,56,49,98,101,45,56,54,98,55,98,50,57,57,49,102,98,49,1,161,186,193,182,130,15,10,2,168,139,151,195,245,8,0,1,119,4,240,159,143,175,161,139,151,195,245,8,2,2,33,0,128,159,198,124,1,36,57,97,53,50,49,56,100,102,45,53,99,54,57,45,53,50,99,54,45,56,102,48,49,45,48,52,102,51,50,52,56,51,49,100,53,51,1,161,139,151,195,245,8,5,2,1,157,218,228,199,8,0,161,225,218,138,252,4,1,2,1,188,146,237,189,8,0,161,230,232,236,161,15,5,4,1,171,194,204,160,8,0,161,195,193,152,153,12,23,4,1,243,186,209,152,8,0,161,167,253,145,211,14,5,2,1,153,186,129,131,8,0,161,243,149,144,209,9,18,19,1,238,201,163,245,7,0,161,195,136,140,158,7,3,16,1,254,149,155,218,7,0,161,153,186,129,131,8,18,1,1,195,136,140,158,7,0,161,188,146,237,189,8,3,4,1,245,221,232,242,6,0,161,170,128,188,181,14,1,23,1,252,139,187,215,6,0,161,229,227,137,140,13,4,8,1,253,240,216,178,6,0,161,134,225,192,253,1,38,11,3,180,238,233,229,5,0,161,147,146,137,224,10,112,1,161,147,146,137,224,10,118,15,161,180,238,233,229,5,15,4,2,185,193,252,188,5,0,161,133,166,132,140,12,4,6,168,185,193,252,188,5,5,1,122,0,0,0,0,102,88,25,34,1,225,218,138,252,4,0,161,129,245,221,210,4,5,2,1,129,245,221,210,4,0,161,252,139,187,215,6,7,6,1,175,245,211,160,4,0,161,250,139,140,49,23,2,6,237,231,215,147,4,0,161,128,159,198,124,9,1,39,0,128,159,198,124,8,6,70,114,115,115,74,100,1,40,0,237,231,215,147,4,1,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,48,177,241,40,0,237,231,215,147,4,1,4,100,97,116,97,1,119,4,120,90,48,51,40,0,237,231,215,147,4,1,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,3,40,0,237,231,215,147,4,1,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,48,177,241,1,151,148,151,141,4,0,161,192,211,236,177,11,7,8,1,230,189,235,175,3,0,161,243,186,209,152,8,1,34,1,169,180,242,165,3,0,161,159,212,134,166,12,9,6,1,157,197,206,156,3,0,161,252,249,181,162,10,5,29,27,235,147,219,255,2,0,161,213,209,142,213,10,36,1,39,0,128,159,198,124,8,6,86,89,52,50,103,49,1,40,0,235,147,219,255,2,1,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,65,128,254,33,0,235,147,219,255,2,1,4,100,97,116,97,1,33,0,235,147,219,255,2,1,10,102,105,101,108,100,95,116,121,112,101,1,33,0,235,147,219,255,2,1,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,161,235,147,219,255,2,0,1,168,235,147,219,255,2,3,1,119,13,49,46,57,57,57,57,57,57,57,57,57,57,57,168,235,147,219,255,2,4,1,122,0,0,0,0,0,0,0,1,168,235,147,219,255,2,5,1,122,0,0,0,0,102,65,129,93,161,235,147,219,255,2,6,1,33,0,128,159,198,124,8,6,115,111,118,85,116,69,1,0,4,161,235,147,219,255,2,10,1,39,0,128,159,198,124,8,6,120,69,81,65,111,75,1,40,0,235,147,219,255,2,17,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,67,49,251,33,0,235,147,219,255,2,17,10,102,105,101,108,100,95,116,121,112,101,1,33,0,235,147,219,255,2,17,4,100,97,116,97,1,33,0,235,147,219,255,2,17,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,161,235,147,219,255,2,16,1,161,235,147,219,255,2,20,1,161,235,147,219,255,2,19,1,161,235,147,219,255,2,21,1,161,235,147,219,255,2,22,1,168,235,147,219,255,2,23,1,119,83,123,34,111,112,116,105,111,110,115,34,58,91,123,34,105,100,34,58,34,111,84,120,83,34,44,34,110,97,109,101,34,58,34,56,56,57,57,34,44,34,99,111,108,111,114,34,58,34,80,117,114,112,108,101,34,125,93,44,34,115,101,108,101,99,116,101,100,95,111,112,116,105,111,110,95,105,100,115,34,58,91,93,125,168,235,147,219,255,2,24,1,122,0,0,0,0,0,0,0,7,168,235,147,219,255,2,25,1,122,0,0,0,0,102,67,49,255,1,135,190,197,222,2,0,161,234,188,148,129,10,9,12,1,242,204,147,190,2,0,161,150,141,187,97,5,2,1,147,212,241,172,2,0,161,252,175,185,165,10,1,2,1,159,241,200,168,2,0,161,209,188,177,215,14,25,4,1,222,161,195,148,2,0,161,213,141,134,218,1,3,2,1,134,225,192,253,1,0,161,169,180,242,165,3,5,39,1,213,141,134,218,1,0,161,157,197,206,156,3,28,4,1,134,132,140,164,1,0,161,230,189,235,175,3,33,4,15,128,159,198,124,0,39,1,4,100,97,116,97,4,100,97,116,97,1,39,1,4,100,97,116,97,4,109,101,116,97,1,39,1,4,100,97,116,97,7,99,111,109,109,101,110,116,0,40,0,128,159,198,124,0,2,105,100,1,119,36,50,102,57,52,52,50,50,48,45,57,102,52,53,45,52,48,100,57,45,57,54,98,53,45,101,56,99,48,56,56,56,100,97,102,55,99,40,0,128,159,198,124,0,6,104,101,105,103,104,116,1,122,0,0,0,0,0,0,0,60,40,0,128,159,198,124,0,10,118,105,115,105,98,105,108,105,116,121,1,120,40,0,128,159,198,124,0,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,48,108,138,33,0,128,159,198,124,0,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,39,0,128,159,198,124,0,5,99,101,108,108,115,1,161,128,159,198,124,7,1,39,0,128,159,198,124,8,6,89,53,52,81,73,115,1,40,0,128,159,198,124,10,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,48,111,158,40,0,128,159,198,124,10,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,0,40,0,128,159,198,124,10,4,100,97,116,97,1,119,3,49,50,51,40,0,128,159,198,124,10,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,48,111,158,2,149,252,241,115,0,161,180,238,233,229,5,15,1,161,180,238,233,229,5,19,9,1,150,141,187,97,0,161,175,245,211,160,4,1,6,1,250,139,140,49,0,161,253,240,216,178,6,10,24,1,205,239,215,19,0,161,144,227,205,159,15,9,2,58,128,159,198,124,2,7,1,9,1,129,245,221,210,4,1,0,6,254,149,155,218,7,1,0,1,131,157,176,150,14,1,0,8,133,166,132,140,12,1,0,5,134,132,140,164,1,1,0,4,135,190,197,222,2,1,0,12,134,225,192,253,1,1,0,39,139,151,195,245,8,2,0,3,4,5,144,227,205,159,15,1,0,10,147,146,137,224,10,1,0,119,147,212,241,172,2,1,0,2,149,252,241,115,1,0,10,147,200,155,248,11,1,0,64,151,148,151,141,4,1,0,8,150,141,187,97,1,0,6,153,186,129,131,8,1,0,19,156,191,219,249,8,1,0,2,157,197,206,156,3,1,0,29,157,218,228,199,8,1,0,2,159,212,134,166,12,1,0,10,159,241,200,168,2,1,0,4,167,253,145,211,14,1,0,6,169,180,242,165,3,1,0,6,170,128,188,181,14,1,0,2,171,194,204,160,8,1,0,4,171,231,189,153,9,1,0,11,175,245,211,160,4,1,0,2,180,238,233,229,5,1,0,20,185,193,252,188,5,1,0,6,186,193,182,130,15,2,0,1,10,1,188,146,237,189,8,1,0,4,192,211,236,177,11,1,0,8,192,252,137,204,11,2,0,3,4,1,195,136,140,158,7,1,0,4,195,193,152,153,12,1,0,24,205,239,215,19,1,0,2,209,188,177,215,14,1,0,26,213,141,134,218,1,1,0,4,213,209,142,213,10,3,0,1,3,4,10,34,222,161,195,148,2,1,0,2,225,218,138,252,4,1,0,2,229,227,137,140,13,1,0,5,230,232,236,161,15,1,0,6,230,189,235,175,3,1,0,34,234,188,148,129,10,1,0,10,235,147,219,255,2,4,0,1,3,4,10,7,19,8,237,231,215,147,4,1,0,1,238,201,163,245,7,1,0,16,242,204,147,190,2,1,0,2,243,149,144,209,9,1,0,19,243,186,209,152,8,1,0,2,245,221,232,242,6,1,0,23,250,139,140,49,1,0,24,252,175,185,165,10,1,0,2,252,249,181,162,10,1,0,6,253,240,216,178,6,1,0,11,252,139,187,215,6,1,0,8],"318aa415-92ae-489a-a14f-a24692a2efa6":[34,16,133,247,247,224,15,0,161,204,206,244,208,8,26,1,39,0,204,206,244,208,8,9,6,70,114,115,115,74,100,1,40,0,133,247,247,224,15,1,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,65,140,55,40,0,133,247,247,224,15,1,4,100,97,116,97,1,119,36,48,52,102,52,55,48,51,55,45,49,56,54,97,45,52,56,55,102,45,98,54,56,101,45,102,49,98,102,97,48,102,101,54,54,53,101,40,0,133,247,247,224,15,1,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,3,40,0,133,247,247,224,15,1,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,65,140,55,161,133,247,247,224,15,0,1,39,0,204,206,244,208,8,9,6,115,111,118,85,116,69,1,40,0,133,247,247,224,15,7,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,65,147,65,33,0,133,247,247,224,15,7,4,100,97,116,97,1,33,0,133,247,247,224,15,7,10,102,105,101,108,100,95,116,121,112,101,1,33,0,133,247,247,224,15,7,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,161,133,247,247,224,15,6,1,168,133,247,247,224,15,10,1,122,0,0,0,0,0,0,0,4,168,133,247,247,224,15,9,1,119,73,57,100,48,48,56,50,51,97,45,100,57,101,50,45,52,102,98,55,45,98,100,98,54,45,99,97,102,54,101,98,99,54,99,49,50,51,44,49,99,52,102,53,52,54,57,45,54,101,49,49,45,52,55,48,51,45,57,48,56,54,45,101,98,98,50,51,57,49,53,100,53,100,56,168,133,247,247,224,15,11,1,122,0,0,0,0,102,65,147,66,1,143,148,196,184,15,0,161,241,201,182,143,5,8,2,1,246,154,152,188,14,0,161,200,145,163,182,5,11,6,1,145,225,236,177,14,0,161,160,131,157,175,8,3,2,1,252,203,148,253,13,0,161,170,153,233,132,10,11,26,1,142,228,130,255,12,0,161,143,148,196,184,15,1,6,1,182,246,158,246,11,0,161,246,154,152,188,14,5,29,2,186,166,174,226,11,0,161,212,236,181,165,9,4,6,168,186,166,174,226,11,5,1,122,0,0,0,0,102,88,25,34,1,159,139,140,218,11,0,161,173,216,200,210,1,23,4,1,214,215,253,213,11,0,161,226,183,173,212,7,23,1,1,200,218,157,187,11,0,161,248,139,142,248,1,1,35,1,145,236,232,195,10,0,33,1,4,109,101,116,97,12,108,97,115,116,95,115,121,110,99,95,97,116,8,1,214,213,255,190,10,0,161,222,208,153,250,3,38,7,1,233,137,131,159,10,0,161,145,236,232,195,10,7,10,1,215,229,183,133,10,0,161,214,215,253,213,11,0,48,1,190,196,251,132,10,0,161,200,218,157,187,11,34,4,1,170,153,233,132,10,0,161,142,228,130,255,12,5,12,1,218,242,205,188,9,0,161,190,196,251,132,10,3,10,1,212,236,181,165,9,0,161,176,202,194,187,3,2,5,34,204,206,244,208,8,0,39,1,4,100,97,116,97,4,100,97,116,97,1,39,1,4,100,97,116,97,4,109,101,116,97,1,39,1,4,100,97,116,97,7,99,111,109,109,101,110,116,0,40,0,204,206,244,208,8,0,2,105,100,1,119,36,51,49,56,97,97,52,49,53,45,57,50,97,101,45,52,56,57,97,45,97,49,52,102,45,97,50,52,54,57,50,97,50,101,102,97,54,40,0,204,206,244,208,8,0,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,52,99,54,53,56,56,49,55,45,50,48,100,98,45,52,102,53,54,45,98,55,102,57,45,48,54,51,55,97,50,50,100,102,101,98,54,40,0,204,206,244,208,8,0,6,104,101,105,103,104,116,1,122,0,0,0,0,0,0,0,60,40,0,204,206,244,208,8,0,10,118,105,115,105,98,105,108,105,116,121,1,120,40,0,204,206,244,208,8,0,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,61,247,11,33,0,204,206,244,208,8,0,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,39,0,204,206,244,208,8,0,5,99,101,108,108,115,1,161,204,206,244,208,8,8,1,39,0,204,206,244,208,8,9,6,86,89,52,50,103,49,1,40,0,204,206,244,208,8,11,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,61,247,15,40,0,204,206,244,208,8,11,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,1,40,0,204,206,244,208,8,11,4,100,97,116,97,1,119,3,54,54,54,40,0,204,206,244,208,8,11,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,61,247,15,161,204,206,244,208,8,10,1,39,0,204,206,244,208,8,9,6,106,87,101,95,116,54,1,40,0,204,206,244,208,8,17,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,61,247,83,33,0,204,206,244,208,8,17,13,101,110,100,95,116,105,109,101,115,116,97,109,112,1,33,0,204,206,244,208,8,17,8,105,115,95,114,97,110,103,101,1,33,0,204,206,244,208,8,17,11,114,101,109,105,110,100,101,114,95,105,100,1,33,0,204,206,244,208,8,17,4,100,97,116,97,1,33,0,204,206,244,208,8,17,10,102,105,101,108,100,95,116,121,112,101,1,33,0,204,206,244,208,8,17,12,105,110,99,108,117,100,101,95,116,105,109,101,1,33,0,204,206,244,208,8,17,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,161,204,206,244,208,8,16,1,168,204,206,244,208,8,19,1,119,0,161,204,206,244,208,8,23,1,168,204,206,244,208,8,24,1,121,168,204,206,244,208,8,21,1,119,21,51,50,78,50,75,100,121,72,114,104,84,55,83,99,54,76,106,78,90,100,51,161,204,206,244,208,8,22,1,168,204,206,244,208,8,20,1,121,161,204,206,244,208,8,25,1,1,217,249,214,203,8,0,161,142,228,130,255,12,5,2,1,228,182,208,185,8,0,161,227,143,130,249,4,7,10,1,160,131,157,175,8,0,161,182,246,158,246,11,28,4,1,226,183,173,212,7,0,161,233,137,131,159,10,9,24,1,200,145,163,182,5,0,161,228,182,208,185,8,9,12,1,241,201,182,143,5,0,161,214,213,255,190,10,6,9,1,227,143,130,249,4,0,161,215,229,183,133,10,47,8,1,160,249,160,227,4,0,161,159,139,140,218,11,3,6,1,222,208,153,250,3,0,161,189,166,144,23,5,39,1,176,202,194,187,3,0,161,252,203,148,253,13,25,3,1,248,139,142,248,1,0,161,160,249,160,227,4,5,2,1,173,216,200,210,1,0,161,145,225,236,177,14,1,24,5,128,181,139,77,0,168,133,247,247,224,15,12,1,122,0,0,0,0,102,67,57,178,168,204,206,244,208,8,28,1,122,0,0,0,0,0,0,0,10,167,204,206,244,208,8,31,0,8,0,128,181,139,77,2,1,119,36,50,102,57,52,52,50,50,48,45,57,102,52,53,45,52,48,100,57,45,57,54,98,53,45,101,56,99,48,56,56,56,100,97,102,55,99,168,204,206,244,208,8,33,1,122,0,0,0,0,102,67,57,178,1,189,166,144,23,0,161,218,242,205,188,9,9,6,33,133,247,247,224,15,3,0,1,6,1,9,4,200,145,163,182,5,1,0,12,200,218,157,187,11,1,0,35,204,206,244,208,8,7,8,1,10,1,16,1,19,8,28,1,31,1,33,1,142,228,130,255,12,1,0,6,143,148,196,184,15,1,0,2,145,236,232,195,10,1,0,8,145,225,236,177,14,1,0,2,212,236,181,165,9,1,0,5,214,215,253,213,11,1,0,1,215,229,183,133,10,1,0,48,214,213,255,190,10,1,0,7,217,249,214,203,8,1,0,2,218,242,205,188,9,1,0,10,222,208,153,250,3,1,0,39,159,139,140,218,11,1,0,4,160,131,157,175,8,1,0,4,160,249,160,227,4,1,0,6,226,183,173,212,7,1,0,24,227,143,130,249,4,1,0,8,228,182,208,185,8,1,0,10,233,137,131,159,10,1,0,10,170,153,233,132,10,1,0,12,173,216,200,210,1,1,0,24,176,202,194,187,3,1,0,3,241,201,182,143,5,1,0,9,246,154,152,188,14,1,0,6,182,246,158,246,11,1,0,29,248,139,142,248,1,1,0,2,186,166,174,226,11,1,0,6,252,203,148,253,13,1,0,26,189,166,144,23,1,0,6,190,196,251,132,10,1,0,4],"dd6c8d13-4867-41c6-8599-b888350f52ee":[53,1,197,130,149,248,15,0,161,158,234,217,249,6,4,8,1,235,218,250,209,15,0,161,228,141,195,172,1,28,3,1,186,200,234,175,15,0,161,147,160,184,220,9,5,12,1,253,249,233,173,15,0,161,142,253,173,141,11,1,2,1,145,176,227,152,15,0,161,227,177,139,177,7,6,2,1,213,199,181,135,15,0,161,155,234,245,236,5,17,39,9,129,162,211,229,14,0,39,1,4,100,97,116,97,4,100,97,116,97,1,39,1,4,100,97,116,97,4,109,101,116,97,1,39,1,4,100,97,116,97,7,99,111,109,109,101,110,116,0,40,0,129,162,211,229,14,0,2,105,100,1,119,36,100,100,54,99,56,100,49,51,45,52,56,54,55,45,52,49,99,54,45,56,53,57,57,45,98,56,56,56,51,53,48,102,53,50,101,101,40,0,129,162,211,229,14,0,6,104,101,105,103,104,116,1,122,0,0,0,0,0,0,0,60,40,0,129,162,211,229,14,0,10,118,105,115,105,98,105,108,105,116,121,1,120,40,0,129,162,211,229,14,0,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,48,108,138,33,0,129,162,211,229,14,0,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,39,0,129,162,211,229,14,0,5,99,101,108,108,115,1,1,185,146,147,208,14,0,161,222,239,172,216,10,3,2,1,250,188,188,173,14,0,161,147,160,184,220,9,5,2,1,177,253,176,145,14,0,161,139,215,211,140,4,29,1,1,153,153,172,165,13,0,161,168,180,153,248,6,23,4,2,185,249,173,251,12,0,161,155,214,174,214,9,111,16,161,185,249,173,251,12,15,4,1,138,141,245,198,12,0,161,240,197,174,164,3,5,4,1,163,150,249,138,12,0,161,174,167,159,184,4,3,9,1,225,225,173,133,12,0,161,205,245,156,144,3,7,10,1,240,245,224,143,11,0,161,163,150,249,138,12,8,6,1,142,253,173,141,11,0,161,140,141,210,196,7,15,2,1,222,239,172,216,10,0,161,158,239,153,203,9,25,4,1,160,254,254,214,10,0,161,224,134,128,190,4,5,2,1,242,190,222,204,10,0,161,213,199,181,135,15,38,8,3,212,192,173,181,10,0,161,185,249,173,251,12,15,1,161,185,249,173,251,12,19,5,161,212,192,173,181,10,5,4,1,201,249,157,231,9,0,161,240,245,224,143,11,5,39,1,147,160,184,220,9,0,161,179,235,169,194,8,1,6,1,155,214,174,214,9,0,33,1,4,109,101,116,97,12,108,97,115,116,95,115,121,110,99,95,97,116,118,1,158,239,153,203,9,0,161,220,226,182,197,1,5,26,38,150,189,211,186,9,0,161,182,238,220,247,3,24,1,39,0,129,162,211,229,14,8,6,70,114,115,115,74,100,1,40,0,150,189,211,186,9,1,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,65,140,50,33,0,150,189,211,186,9,1,4,100,97,116,97,1,33,0,150,189,211,186,9,1,10,102,105,101,108,100,95,116,121,112,101,1,33,0,150,189,211,186,9,1,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,161,150,189,211,186,9,0,1,161,150,189,211,186,9,4,1,161,150,189,211,186,9,3,1,161,150,189,211,186,9,5,1,161,150,189,211,186,9,6,1,168,150,189,211,186,9,7,1,122,0,0,0,0,0,0,0,3,168,150,189,211,186,9,8,1,119,36,54,49,50,100,50,99,51,98,45,56,50,98,99,45,52,55,51,98,45,98,49,52,53,45,55,102,53,55,49,56,54,101,51,102,55,101,168,150,189,211,186,9,9,1,122,0,0,0,0,102,65,140,51,161,150,189,211,186,9,10,1,39,0,129,162,211,229,14,8,6,115,111,118,85,116,69,1,40,0,150,189,211,186,9,15,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,65,147,58,33,0,150,189,211,186,9,15,4,100,97,116,97,1,33,0,150,189,211,186,9,15,10,102,105,101,108,100,95,116,121,112,101,1,33,0,150,189,211,186,9,15,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,161,150,189,211,186,9,14,1,161,150,189,211,186,9,17,1,161,150,189,211,186,9,18,1,161,150,189,211,186,9,19,1,161,150,189,211,186,9,20,1,161,150,189,211,186,9,22,1,161,150,189,211,186,9,21,1,161,150,189,211,186,9,23,1,161,150,189,211,186,9,24,1,168,150,189,211,186,9,25,1,122,0,0,0,0,0,0,0,4,168,150,189,211,186,9,26,1,119,147,1,49,99,52,102,53,52,54,57,45,54,101,49,49,45,52,55,48,51,45,57,48,56,54,45,101,98,98,50,51,57,49,53,100,53,100,56,44,57,100,48,48,56,50,51,97,45,100,57,101,50,45,52,102,98,55,45,98,100,98,54,45,99,97,102,54,101,98,99,54,99,49,50,51,44,48,52,48,102,98,48,98,102,45,50,101,100,97,45,52,99,97,51,45,56,54,99,97,45,53,98,57,49,98,55,48,50,102,101,49,54,44,52,49,57,50,51,51,57,51,45,102,55,99,51,45,52,50,51,53,45,98,54,49,51,45,102,57,97,101,56,52,102,102,53,56,56,57,168,150,189,211,186,9,27,1,122,0,0,0,0,102,65,147,60,161,150,189,211,186,9,28,1,39,0,129,162,211,229,14,8,6,89,80,102,105,50,109,1,40,0,150,189,211,186,9,33,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,65,147,206,40,0,150,189,211,186,9,33,4,100,97,116,97,1,119,3,89,101,115,40,0,150,189,211,186,9,33,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,5,40,0,150,189,211,186,9,33,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,65,147,206,2,141,231,246,230,8,0,161,229,240,171,135,7,4,1,168,141,231,246,230,8,0,1,122,0,0,0,0,102,88,25,35,1,179,235,169,194,8,0,161,137,254,221,87,15,2,1,219,210,242,162,8,0,161,235,218,250,209,15,2,5,1,140,141,210,196,7,0,161,222,168,212,145,3,3,16,1,227,177,139,177,7,0,161,153,153,172,165,13,3,7,1,229,240,171,135,7,0,161,219,210,242,162,8,4,5,1,158,234,217,249,6,0,161,212,192,173,181,10,5,5,1,168,180,153,248,6,0,161,185,146,147,208,14,1,24,1,195,153,238,185,6,0,161,145,176,227,152,15,1,35,1,144,169,186,164,6,0,161,160,254,254,214,10,1,2,1,155,234,245,236,5,0,161,253,249,233,173,15,1,18,1,202,211,156,221,5,0,161,177,253,176,145,14,0,62,1,250,181,208,232,4,0,161,144,169,186,164,6,1,2,2,224,134,128,190,4,0,161,197,130,149,248,15,7,1,161,212,192,173,181,10,9,5,1,174,167,159,184,4,0,161,195,153,238,185,6,34,4,1,139,215,211,140,4,0,161,152,171,228,253,2,9,30,34,182,238,220,247,3,0,161,129,162,211,229,14,7,1,39,0,129,162,211,229,14,8,6,86,89,52,50,103,49,1,40,0,182,238,220,247,3,1,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,61,144,110,33,0,182,238,220,247,3,1,10,102,105,101,108,100,95,116,121,112,101,1,33,0,182,238,220,247,3,1,4,100,97,116,97,1,33,0,182,238,220,247,3,1,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,161,182,238,220,247,3,0,1,168,182,238,220,247,3,4,1,119,4,56,56,56,57,168,182,238,220,247,3,3,1,122,0,0,0,0,0,0,0,1,168,182,238,220,247,3,5,1,122,0,0,0,0,102,61,145,39,161,182,238,220,247,3,6,1,39,0,129,162,211,229,14,8,6,54,76,70,72,66,54,1,40,0,182,238,220,247,3,11,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,61,145,93,33,0,182,238,220,247,3,11,10,102,105,101,108,100,95,116,121,112,101,1,33,0,182,238,220,247,3,11,4,100,97,116,97,1,33,0,182,238,220,247,3,11,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,161,182,238,220,247,3,10,1,161,182,238,220,247,3,13,1,161,182,238,220,247,3,14,1,161,182,238,220,247,3,15,1,161,182,238,220,247,3,16,1,168,182,238,220,247,3,18,1,119,9,98,97,105,100,117,46,99,111,109,168,182,238,220,247,3,17,1,122,0,0,0,0,0,0,0,6,168,182,238,220,247,3,19,1,122,0,0,0,0,102,61,145,95,161,182,238,220,247,3,20,1,39,0,129,162,211,229,14,8,6,106,87,101,95,116,54,1,40,0,182,238,220,247,3,25,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,61,247,78,33,0,182,238,220,247,3,25,10,102,105,101,108,100,95,116,121,112,101,1,40,0,182,238,220,247,3,25,13,101,110,100,95,116,105,109,101,115,116,97,109,112,1,119,0,33,0,182,238,220,247,3,25,4,100,97,116,97,1,40,0,182,238,220,247,3,25,8,105,115,95,114,97,110,103,101,1,121,40,0,182,238,220,247,3,25,11,114,101,109,105,110,100,101,114,95,105,100,1,119,0,40,0,182,238,220,247,3,25,12,105,110,99,108,117,100,101,95,116,105,109,101,1,121,33,0,182,238,220,247,3,25,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,1,240,197,174,164,3,0,161,141,225,222,162,2,1,6,1,222,168,212,145,3,0,161,138,141,245,198,12,3,4,1,205,245,156,144,3,0,161,202,211,156,221,5,61,8,1,152,171,228,253,2,0,161,242,190,222,204,10,7,10,1,141,225,222,162,2,0,161,250,181,208,232,4,1,2,5,160,193,167,129,2,0,168,150,189,211,186,9,32,1,122,0,0,0,0,102,67,57,110,168,182,238,220,247,3,27,1,122,0,0,0,0,0,0,0,10,167,182,238,220,247,3,29,0,8,0,160,193,167,129,2,2,1,119,36,51,49,56,97,97,52,49,53,45,57,50,97,101,45,52,56,57,97,45,97,49,52,102,45,97,50,52,54,57,50,97,50,101,102,97,54,168,182,238,220,247,3,33,1,122,0,0,0,0,102,67,57,110,1,220,226,182,197,1,0,161,130,157,172,36,11,6,1,228,141,195,172,1,0,161,186,200,234,175,15,11,29,1,137,254,221,87,0,161,201,249,157,231,9,38,16,1,130,157,172,36,0,161,225,225,173,133,12,9,12,52,129,162,211,229,14,1,7,1,130,157,172,36,1,0,12,195,153,238,185,6,1,0,35,197,130,149,248,15,1,0,8,201,249,157,231,9,1,0,39,138,141,245,198,12,1,0,4,139,215,211,140,4,1,0,30,140,141,210,196,7,1,0,16,205,245,156,144,3,1,0,8,141,225,222,162,2,1,0,2,142,253,173,141,11,1,0,2,144,169,186,164,6,1,0,2,145,176,227,152,15,1,0,2,202,211,156,221,5,1,0,62,137,254,221,87,1,0,16,212,192,173,181,10,1,0,10,213,199,181,135,15,1,0,39,147,160,184,220,9,1,0,6,150,189,211,186,9,5,0,1,3,8,14,1,17,12,32,1,152,171,228,253,2,1,0,10,153,153,172,165,13,1,0,4,141,231,246,230,8,1,0,1,155,214,174,214,9,1,0,118,220,226,182,197,1,1,0,6,155,234,245,236,5,1,0,18,158,239,153,203,9,1,0,26,158,234,217,249,6,1,0,5,224,134,128,190,4,1,0,6,160,254,254,214,10,1,0,2,225,225,173,133,12,1,0,10,222,168,212,145,3,1,0,4,222,239,172,216,10,1,0,4,227,177,139,177,7,1,0,7,163,150,249,138,12,1,0,9,228,141,195,172,1,1,0,29,168,180,153,248,6,1,0,24,219,210,242,162,8,1,0,5,229,240,171,135,7,1,0,5,235,218,250,209,15,1,0,3,174,167,159,184,4,1,0,4,240,197,174,164,3,1,0,6,177,253,176,145,14,1,0,1,242,190,222,204,10,1,0,8,240,245,224,143,11,1,0,6,179,235,169,194,8,1,0,2,182,238,220,247,3,8,0,1,3,4,10,1,13,8,24,1,27,1,29,1,33,1,185,249,173,251,12,1,0,20,250,181,208,232,4,1,0,2,185,146,147,208,14,1,0,2,186,200,234,175,15,1,0,12,253,249,233,173,15,1,0,2,250,188,188,173,14,1,0,2],"0160e587-41f4-4391-abb3-d322b523edb2":[34,1,169,243,176,173,14,0,161,136,210,233,249,3,12,10,1,149,233,213,204,13,0,161,200,193,211,175,13,1,26,1,200,193,211,175,13,0,161,171,146,234,226,2,9,2,1,176,222,150,172,13,0,161,189,136,144,179,10,1,6,1,174,136,245,243,12,0,161,144,251,151,19,5,39,1,145,167,143,155,12,0,161,132,147,219,143,3,6,9,2,204,172,143,149,12,0,161,148,160,235,175,1,4,7,168,204,172,143,149,12,6,1,122,0,0,0,0,102,88,25,35,28,156,179,144,186,11,0,161,214,221,216,208,2,10,1,39,0,214,221,216,208,2,9,6,70,114,115,115,74,100,1,40,0,156,179,144,186,11,1,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,65,140,60,33,0,156,179,144,186,11,1,10,102,105,101,108,100,95,116,121,112,101,1,33,0,156,179,144,186,11,1,4,100,97,116,97,1,33,0,156,179,144,186,11,1,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,161,156,179,144,186,11,0,1,168,156,179,144,186,11,4,1,119,36,48,52,102,52,55,48,51,55,45,49,56,54,97,45,52,56,55,102,45,98,54,56,101,45,102,49,98,102,97,48,102,101,54,54,53,101,168,156,179,144,186,11,3,1,122,0,0,0,0,0,0,0,3,168,156,179,144,186,11,5,1,122,0,0,0,0,102,65,140,110,161,156,179,144,186,11,6,1,39,0,214,221,216,208,2,9,6,115,111,118,85,116,69,1,40,0,156,179,144,186,11,11,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,65,147,72,33,0,156,179,144,186,11,11,10,102,105,101,108,100,95,116,121,112,101,1,33,0,156,179,144,186,11,11,4,100,97,116,97,1,33,0,156,179,144,186,11,11,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,161,156,179,144,186,11,10,1,161,156,179,144,186,11,14,1,161,156,179,144,186,11,13,1,161,156,179,144,186,11,15,1,161,156,179,144,186,11,16,1,161,156,179,144,186,11,17,1,161,156,179,144,186,11,18,1,161,156,179,144,186,11,19,1,168,156,179,144,186,11,20,1,122,0,0,0,0,102,65,147,75,168,156,179,144,186,11,21,1,119,73,102,99,100,54,101,102,56,99,45,56,99,100,54,45,52,49,98,51,45,57,50,52,53,45,57,57,56,57,51,49,100,52,57,97,49,54,44,57,100,48,48,56,50,51,97,45,100,57,101,50,45,52,102,98,55,45,98,100,98,54,45,99,97,102,54,101,98,99,54,99,49,50,51,168,156,179,144,186,11,22,1,122,0,0,0,0,0,0,0,4,168,156,179,144,186,11,23,1,122,0,0,0,0,102,65,147,75,1,146,233,137,149,11,0,161,216,226,128,250,2,9,12,1,132,204,135,146,11,0,161,246,211,137,45,5,26,1,189,136,144,179,10,0,161,145,167,143,155,12,8,2,1,190,135,128,165,10,0,161,180,231,185,129,2,3,10,1,213,169,224,237,9,0,161,244,148,242,155,4,3,6,1,227,133,204,217,9,0,161,176,222,150,172,13,5,2,1,251,233,161,212,7,0,161,213,169,224,237,9,5,3,1,255,171,204,197,7,0,161,248,180,185,229,2,3,2,1,249,211,146,138,7,0,161,149,233,213,204,13,25,3,1,203,203,186,212,6,0,161,132,132,171,141,1,29,1,1,197,235,146,149,5,0,161,251,233,161,212,7,2,34,1,187,192,226,180,4,0,161,241,252,150,189,1,47,8,1,244,148,242,155,4,0,161,176,135,229,160,1,23,4,1,136,210,233,249,3,0,33,1,4,109,101,116,97,12,108,97,115,116,95,115,121,110,99,95,97,116,13,1,132,147,219,143,3,0,161,174,136,245,243,12,38,7,1,216,226,128,250,2,0,161,187,192,226,180,4,7,10,1,248,180,185,229,2,0,161,132,204,135,146,11,25,4,2,171,146,234,226,2,0,161,176,222,150,172,13,5,1,161,227,133,204,217,9,1,9,16,214,221,216,208,2,0,39,1,4,100,97,116,97,4,100,97,116,97,1,39,1,4,100,97,116,97,4,109,101,116,97,1,39,1,4,100,97,116,97,7,99,111,109,109,101,110,116,0,40,0,214,221,216,208,2,0,2,105,100,1,119,36,48,49,54,48,101,53,56,55,45,52,49,102,52,45,52,51,57,49,45,97,98,98,51,45,100,51,50,50,98,53,50,51,101,100,98,50,40,0,214,221,216,208,2,0,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,52,99,54,53,56,56,49,55,45,50,48,100,98,45,52,102,53,54,45,98,55,102,57,45,48,54,51,55,97,50,50,100,102,101,98,54,40,0,214,221,216,208,2,0,6,104,101,105,103,104,116,1,122,0,0,0,0,0,0,0,60,40,0,214,221,216,208,2,0,10,118,105,115,105,98,105,108,105,116,121,1,120,40,0,214,221,216,208,2,0,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,61,247,182,33,0,214,221,216,208,2,0,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,39,0,214,221,216,208,2,0,5,99,101,108,108,115,1,161,214,221,216,208,2,8,1,39,0,214,221,216,208,2,9,6,86,89,52,50,103,49,1,40,0,214,221,216,208,2,11,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,61,247,198,40,0,214,221,216,208,2,11,4,100,97,116,97,1,119,3,56,56,56,40,0,214,221,216,208,2,11,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,1,40,0,214,221,216,208,2,11,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,61,247,198,1,180,231,185,129,2,0,161,197,235,146,149,5,33,4,1,241,252,150,189,1,0,161,203,203,186,212,6,0,48,1,148,160,235,175,1,0,161,249,211,146,138,7,2,5,1,176,135,229,160,1,0,161,255,171,204,197,7,1,24,1,132,132,171,141,1,0,161,169,243,176,173,14,9,30,1,246,211,137,45,0,161,146,233,137,149,11,11,6,1,144,251,151,19,0,161,190,135,128,165,10,9,6,34,132,132,171,141,1,1,0,30,132,204,135,146,11,1,0,26,197,235,146,149,5,1,0,34,132,147,219,143,3,1,0,7,136,210,233,249,3,1,0,13,200,193,211,175,13,1,0,2,203,203,186,212,6,1,0,1,204,172,143,149,12,1,0,7,144,251,151,19,1,0,6,145,167,143,155,12,1,0,9,146,233,137,149,11,1,0,12,148,160,235,175,1,1,0,5,213,169,224,237,9,1,0,6,149,233,213,204,13,1,0,26,214,221,216,208,2,2,8,1,10,1,216,226,128,250,2,1,0,10,156,179,144,186,11,4,0,1,3,4,10,1,13,11,227,133,204,217,9,1,0,2,169,243,176,173,14,1,0,10,171,146,234,226,2,1,0,10,174,136,245,243,12,1,0,39,176,222,150,172,13,1,0,6,176,135,229,160,1,1,0,24,241,252,150,189,1,1,0,48,244,148,242,155,4,1,0,4,180,231,185,129,2,1,0,4,246,211,137,45,1,0,6,248,180,185,229,2,1,0,4,249,211,146,138,7,1,0,3,187,192,226,180,4,1,0,8,251,233,161,212,7,1,0,3,189,136,144,179,10,1,0,2,190,135,128,165,10,1,0,10,255,171,204,197,7,1,0,2],"1cb91fa2-638d-40d6-a7c4-394f0d8b1913":[36,1,238,137,157,247,15,0,161,221,232,159,196,3,14,29,1,203,245,161,202,15,0,161,188,205,187,245,3,38,7,1,187,170,199,190,15,0,161,167,237,232,169,3,9,2,1,170,171,213,133,15,0,161,183,186,132,201,5,8,6,1,201,227,232,191,14,0,161,181,253,171,218,8,3,2,54,176,143,254,225,13,0,39,1,4,100,97,116,97,4,100,97,116,97,1,39,1,4,100,97,116,97,4,109,101,116,97,1,39,1,4,100,97,116,97,7,99,111,109,109,101,110,116,0,40,0,176,143,254,225,13,0,2,105,100,1,119,36,49,99,98,57,49,102,97,50,45,54,51,56,100,45,52,48,100,54,45,97,55,99,52,45,51,57,52,102,48,100,56,98,49,57,49,51,40,0,176,143,254,225,13,0,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,52,99,54,53,56,56,49,55,45,50,48,100,98,45,52,102,53,54,45,98,55,102,57,45,48,54,51,55,97,50,50,100,102,101,98,54,40,0,176,143,254,225,13,0,6,104,101,105,103,104,116,1,122,0,0,0,0,0,0,0,60,40,0,176,143,254,225,13,0,10,118,105,115,105,98,105,108,105,116,121,1,120,40,0,176,143,254,225,13,0,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,61,145,9,33,0,176,143,254,225,13,0,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,39,0,176,143,254,225,13,0,5,99,101,108,108,115,1,161,176,143,254,225,13,8,1,39,0,176,143,254,225,13,9,6,86,89,52,50,103,49,1,40,0,176,143,254,225,13,11,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,61,208,16,33,0,176,143,254,225,13,11,10,102,105,101,108,100,95,116,121,112,101,1,33,0,176,143,254,225,13,11,4,100,97,116,97,1,33,0,176,143,254,225,13,11,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,161,176,143,254,225,13,10,1,161,176,143,254,225,13,13,1,161,176,143,254,225,13,14,1,161,176,143,254,225,13,15,1,161,176,143,254,225,13,16,1,39,0,176,143,254,225,13,9,6,106,87,101,95,116,54,1,40,0,176,143,254,225,13,21,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,61,247,108,33,0,176,143,254,225,13,21,13,101,110,100,95,116,105,109,101,115,116,97,109,112,1,33,0,176,143,254,225,13,21,12,105,110,99,108,117,100,101,95,116,105,109,101,1,33,0,176,143,254,225,13,21,10,102,105,101,108,100,95,116,121,112,101,1,33,0,176,143,254,225,13,21,8,105,115,95,114,97,110,103,101,1,33,0,176,143,254,225,13,21,11,114,101,109,105,110,100,101,114,95,105,100,1,33,0,176,143,254,225,13,21,4,100,97,116,97,1,33,0,176,143,254,225,13,21,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,161,176,143,254,225,13,20,1,161,176,143,254,225,13,28,1,161,176,143,254,225,13,25,1,161,176,143,254,225,13,23,1,161,176,143,254,225,13,24,1,161,176,143,254,225,13,26,1,161,176,143,254,225,13,27,1,161,176,143,254,225,13,29,1,161,176,143,254,225,13,30,1,161,176,143,254,225,13,32,1,161,176,143,254,225,13,33,1,161,176,143,254,225,13,31,1,161,176,143,254,225,13,34,1,161,176,143,254,225,13,35,1,161,176,143,254,225,13,36,1,161,176,143,254,225,13,37,1,161,176,143,254,225,13,38,1,168,176,143,254,225,13,39,1,122,0,0,0,0,0,0,0,2,168,176,143,254,225,13,41,1,119,10,49,55,49,54,50,48,49,48,55,48,168,176,143,254,225,13,42,1,121,168,176,143,254,225,13,43,1,120,168,176,143,254,225,13,44,1,119,0,168,176,143,254,225,13,40,1,119,10,49,55,49,54,53,52,54,54,55,48,168,176,143,254,225,13,45,1,122,0,0,0,0,102,61,247,110,1,130,240,184,196,13,0,161,178,163,133,192,10,8,2,1,153,175,162,242,12,0,161,252,192,199,162,9,2,5,1,153,220,165,207,12,0,161,249,139,227,143,7,23,4,1,166,194,251,203,12,0,161,215,220,249,203,2,5,2,1,152,210,190,161,12,0,161,190,172,167,219,5,7,10,6,175,192,222,199,11,0,161,176,143,254,225,13,46,1,39,0,176,143,254,225,13,9,6,89,80,102,105,50,109,1,40,0,175,192,222,199,11,1,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,61,250,101,40,0,175,192,222,199,11,1,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,5,40,0,175,192,222,199,11,1,4,100,97,116,97,1,119,3,89,101,115,40,0,175,192,222,199,11,1,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,61,250,101,1,184,246,254,146,11,0,161,182,191,217,143,3,5,12,1,215,244,255,242,10,0,161,187,170,199,190,15,1,24,1,178,163,133,192,10,0,161,203,245,161,202,15,6,9,1,170,181,130,222,9,0,161,168,209,143,134,8,0,48,1,252,192,199,162,9,0,161,215,244,255,242,10,23,3,1,185,162,192,153,9,0,161,179,139,229,135,7,11,6,1,181,253,171,218,8,0,161,155,177,225,165,7,25,4,1,168,209,143,134,8,0,161,238,137,157,247,15,28,1,1,155,177,225,165,7,0,161,185,162,192,153,9,5,26,1,249,139,227,143,7,0,161,201,227,232,191,14,1,24,1,179,139,229,135,7,0,161,152,210,190,161,12,9,12,1,234,179,204,154,6,0,161,208,165,135,137,5,5,2,1,190,172,167,219,5,0,161,170,181,130,222,9,47,8,1,183,186,132,201,5,0,161,224,212,129,14,3,9,1,208,165,135,137,5,0,161,153,220,165,207,12,3,6,1,244,213,208,134,5,0,161,234,179,204,154,6,1,34,1,188,205,187,245,3,0,161,170,171,213,133,15,5,39,20,130,147,164,198,3,0,161,175,192,222,199,11,0,1,168,176,143,254,225,13,18,1,119,9,48,46,53,54,54,54,54,54,54,168,176,143,254,225,13,17,1,122,0,0,0,0,0,0,0,1,168,176,143,254,225,13,19,1,122,0,0,0,0,102,65,130,1,161,130,147,164,198,3,0,1,39,0,176,143,254,225,13,9,6,70,114,115,115,74,100,1,40,0,130,147,164,198,3,5,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,65,140,53,40,0,130,147,164,198,3,5,4,100,97,116,97,1,119,4,120,90,48,51,40,0,130,147,164,198,3,5,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,3,40,0,130,147,164,198,3,5,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,65,140,53,161,130,147,164,198,3,4,1,39,0,176,143,254,225,13,9,6,115,111,118,85,116,69,1,40,0,130,147,164,198,3,11,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,65,147,63,33,0,130,147,164,198,3,11,4,100,97,116,97,1,33,0,130,147,164,198,3,11,10,102,105,101,108,100,95,116,121,112,101,1,33,0,130,147,164,198,3,11,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,168,130,147,164,198,3,10,1,122,0,0,0,0,102,65,147,221,168,130,147,164,198,3,14,1,122,0,0,0,0,0,0,0,4,168,130,147,164,198,3,13,1,119,73,56,51,51,50,99,52,56,51,45,102,56,57,99,45,52,48,53,55,45,57,101,99,57,45,101,50,53,53,56,54,53,48,52,52,51,56,44,48,52,48,102,98,48,98,102,45,50,101,100,97,45,52,99,97,51,45,56,54,99,97,45,53,98,57,49,98,55,48,50,102,101,49,54,168,130,147,164,198,3,15,1,122,0,0,0,0,102,65,147,221,1,221,232,159,196,3,0,161,184,246,254,146,11,11,15,2,167,237,232,169,3,0,161,215,220,249,203,2,5,1,161,166,194,251,203,12,1,9,1,182,191,217,143,3,0,33,1,4,109,101,116,97,12,108,97,115,116,95,115,121,110,99,95,97,116,6,1,215,220,249,203,2,0,161,130,240,184,196,13,1,6,2,235,238,250,238,1,0,161,153,175,162,242,12,4,6,168,235,238,250,238,1,5,1,122,0,0,0,0,102,88,25,34,1,224,212,129,14,0,161,244,213,208,134,5,33,4,36,130,240,184,196,13,1,0,2,130,147,164,198,3,4,0,1,4,1,10,1,13,3,201,227,232,191,14,1,0,2,203,245,161,202,15,1,0,7,208,165,135,137,5,1,0,6,215,220,249,203,2,1,0,6,152,210,190,161,12,1,0,10,153,220,165,207,12,1,0,4,215,244,255,242,10,1,0,24,155,177,225,165,7,1,0,26,153,175,162,242,12,1,0,5,221,232,159,196,3,1,0,15,224,212,129,14,1,0,4,166,194,251,203,12,1,0,2,167,237,232,169,3,1,0,10,168,209,143,134,8,1,0,1,170,181,130,222,9,1,0,48,234,179,204,154,6,1,0,2,170,171,213,133,15,1,0,6,235,238,250,238,1,1,0,6,238,137,157,247,15,1,0,29,175,192,222,199,11,1,0,1,176,143,254,225,13,4,8,1,10,1,13,8,23,24,178,163,133,192,10,1,0,9,179,139,229,135,7,1,0,12,244,213,208,134,5,1,0,34,181,253,171,218,8,1,0,4,182,191,217,143,3,1,0,6,183,186,132,201,5,1,0,9,184,246,254,146,11,1,0,12,249,139,227,143,7,1,0,24,185,162,192,153,9,1,0,6,187,170,199,190,15,1,0,2,188,205,187,245,3,1,0,39,252,192,199,162,9,1,0,3,190,172,167,219,5,1,0,8],"3ccd17e0-d78b-44e2-afd1-1bf7cc49cb56":[34,1,206,233,228,195,15,0,161,170,189,188,208,5,5,2,1,253,134,198,190,15,0,161,193,228,168,220,13,3,2,1,153,141,151,158,15,0,161,222,235,157,159,14,8,6,1,148,232,153,164,14,0,161,136,255,156,248,4,24,3,1,222,235,157,159,14,0,161,252,145,253,197,3,3,9,1,182,194,169,138,14,0,161,169,138,231,172,3,5,2,1,193,228,168,220,13,0,161,155,223,214,152,11,25,4,2,215,249,231,218,13,0,161,145,163,160,202,11,4,6,168,215,249,231,218,13,5,1,122,0,0,0,0,102,88,25,35,1,194,189,155,132,13,0,161,144,133,245,185,2,23,1,1,226,198,237,226,12,0,161,158,166,203,46,23,4,1,137,217,190,128,12,0,161,133,194,167,165,11,38,7,42,214,218,172,227,11,0,39,1,4,100,97,116,97,4,100,97,116,97,1,39,1,4,100,97,116,97,4,109,101,116,97,1,39,1,4,100,97,116,97,7,99,111,109,109,101,110,116,0,40,0,214,218,172,227,11,0,2,105,100,1,119,36,51,99,99,100,49,55,101,48,45,100,55,56,98,45,52,52,101,50,45,97,102,100,49,45,49,98,102,55,99,99,52,57,99,98,53,54,40,0,214,218,172,227,11,0,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,52,99,54,53,56,56,49,55,45,50,48,100,98,45,52,102,53,54,45,98,55,102,57,45,48,54,51,55,97,50,50,100,102,101,98,54,40,0,214,218,172,227,11,0,6,104,101,105,103,104,116,1,122,0,0,0,0,0,0,0,60,40,0,214,218,172,227,11,0,10,118,105,115,105,98,105,108,105,116,121,1,120,40,0,214,218,172,227,11,0,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,61,247,32,33,0,214,218,172,227,11,0,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,39,0,214,218,172,227,11,0,5,99,101,108,108,115,1,161,214,218,172,227,11,8,1,39,0,214,218,172,227,11,9,6,86,89,52,50,103,49,1,40,0,214,218,172,227,11,11,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,61,247,36,40,0,214,218,172,227,11,11,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,1,40,0,214,218,172,227,11,11,4,100,97,116,97,1,119,3,55,55,55,40,0,214,218,172,227,11,11,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,61,247,36,161,214,218,172,227,11,10,1,39,0,214,218,172,227,11,9,6,106,87,101,95,116,54,1,40,0,214,218,172,227,11,17,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,61,247,92,33,0,214,218,172,227,11,17,12,105,110,99,108,117,100,101,95,116,105,109,101,1,33,0,214,218,172,227,11,17,8,105,115,95,114,97,110,103,101,1,33,0,214,218,172,227,11,17,13,101,110,100,95,116,105,109,101,115,116,97,109,112,1,33,0,214,218,172,227,11,17,4,100,97,116,97,1,33,0,214,218,172,227,11,17,10,102,105,101,108,100,95,116,121,112,101,1,33,0,214,218,172,227,11,17,11,114,101,109,105,110,100,101,114,95,105,100,1,33,0,214,218,172,227,11,17,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,161,214,218,172,227,11,16,1,161,214,218,172,227,11,21,1,161,214,218,172,227,11,23,1,161,214,218,172,227,11,19,1,161,214,218,172,227,11,20,1,161,214,218,172,227,11,24,1,161,214,218,172,227,11,22,1,161,214,218,172,227,11,25,1,161,214,218,172,227,11,26,1,168,214,218,172,227,11,27,1,119,0,168,214,218,172,227,11,32,1,119,10,49,55,49,53,57,52,49,56,48,48,168,214,218,172,227,11,29,1,120,168,214,218,172,227,11,28,1,122,0,0,0,0,0,0,0,2,168,214,218,172,227,11,30,1,121,168,214,218,172,227,11,31,1,119,21,71,99,83,71,68,56,119,81,82,81,90,116,68,101,122,109,115,122,73,97,74,168,214,218,172,227,11,33,1,122,0,0,0,0,102,61,247,101,1,179,151,213,226,11,0,161,142,245,238,213,8,11,6,1,145,163,160,202,11,0,161,148,232,153,164,14,2,5,1,133,194,167,165,11,0,161,153,141,151,158,15,5,39,1,155,223,214,152,11,0,161,179,151,213,226,11,5,26,1,182,179,204,141,10,0,161,148,207,232,183,3,11,10,1,142,245,238,213,8,0,161,179,170,225,17,9,12,6,158,225,233,217,7,0,161,214,218,172,227,11,34,1,39,0,214,218,172,227,11,9,6,89,80,102,105,50,109,1,40,0,158,225,233,217,7,1,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,61,250,105,40,0,158,225,233,217,7,1,4,100,97,116,97,1,119,3,89,101,115,40,0,158,225,233,217,7,1,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,5,40,0,158,225,233,217,7,1,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,61,250,105,1,158,231,208,154,7,0,161,148,249,197,139,5,8,2,1,170,189,188,208,5,0,161,226,198,237,226,12,3,6,2,184,177,221,199,5,0,161,169,138,231,172,3,5,1,161,182,194,169,138,14,1,11,1,148,249,197,139,5,0,161,137,217,190,128,12,6,9,1,136,255,156,248,4,0,161,184,177,221,199,5,11,25,16,208,242,145,212,4,0,161,158,225,233,217,7,0,1,39,0,214,218,172,227,11,9,6,70,114,115,115,74,100,1,40,0,208,242,145,212,4,1,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,65,140,57,40,0,208,242,145,212,4,1,4,100,97,116,97,1,119,36,54,49,50,100,50,99,51,98,45,56,50,98,99,45,52,55,51,98,45,98,49,52,53,45,55,102,53,55,49,56,54,101,51,102,55,101,40,0,208,242,145,212,4,1,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,3,40,0,208,242,145,212,4,1,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,65,140,57,161,208,242,145,212,4,0,1,39,0,214,218,172,227,11,9,6,115,111,118,85,116,69,1,40,0,208,242,145,212,4,7,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,65,147,68,33,0,208,242,145,212,4,7,10,102,105,101,108,100,95,116,121,112,101,1,33,0,208,242,145,212,4,7,4,100,97,116,97,1,33,0,208,242,145,212,4,7,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,168,208,242,145,212,4,6,1,122,0,0,0,0,102,65,147,69,168,208,242,145,212,4,9,1,122,0,0,0,0,0,0,0,4,168,208,242,145,212,4,10,1,119,73,52,53,53,98,100,49,56,51,45,54,54,57,102,45,52,98,49,55,45,56,99,56,57,45,56,102,56,53,48,102,102,50,48,51,54,52,44,57,97,102,51,49,102,100,53,45,98,54,53,52,45,52,54,54,54,45,98,101,101,57,45,101,50,52,55,49,51,55,50,53,49,102,53,168,208,242,145,212,4,11,1,122,0,0,0,0,102,65,147,69,1,252,145,253,197,3,0,161,222,146,227,251,2,33,4,1,148,207,232,183,3,0,33,1,4,109,101,116,97,12,108,97,115,116,95,115,121,110,99,95,97,116,12,1,169,138,231,172,3,0,161,158,231,208,154,7,1,6,1,222,146,227,251,2,0,161,206,233,228,195,15,1,34,1,144,133,245,185,2,0,161,182,179,204,141,10,9,24,1,198,237,231,132,2,0,161,194,189,155,132,13,0,48,1,153,149,181,61,0,161,198,237,231,132,2,47,8,1,158,166,203,46,0,161,253,134,198,190,15,1,24,1,179,170,225,17,0,161,153,149,181,61,7,10,34,193,228,168,220,13,1,0,4,194,189,155,132,13,1,0,1,133,194,167,165,11,1,0,39,198,237,231,132,2,1,0,48,136,255,156,248,4,1,0,25,137,217,190,128,12,1,0,7,142,245,238,213,8,1,0,12,206,233,228,195,15,1,0,2,144,133,245,185,2,1,0,24,145,163,160,202,11,1,0,5,208,242,145,212,4,3,0,1,6,1,9,3,148,207,232,183,3,1,0,12,148,249,197,139,5,1,0,9,148,232,153,164,14,1,0,3,214,218,172,227,11,4,8,1,10,1,16,1,19,16,215,249,231,218,13,1,0,6,153,149,181,61,1,0,8,153,141,151,158,15,1,0,6,155,223,214,152,11,1,0,26,158,166,203,46,1,0,24,222,146,227,251,2,1,0,34,158,225,233,217,7,1,0,1,222,235,157,159,14,1,0,9,226,198,237,226,12,1,0,4,158,231,208,154,7,1,0,2,169,138,231,172,3,1,0,6,170,189,188,208,5,1,0,6,179,170,225,17,1,0,10,179,151,213,226,11,1,0,6,182,194,169,138,14,1,0,2,182,179,204,141,10,1,0,10,184,177,221,199,5,1,0,12,252,145,253,197,3,1,0,4,253,134,198,190,15,1,0,2],"4b560c2d-3f39-4086-aa3d-c2590d129850":[23,1,171,144,240,132,15,0,161,177,185,133,253,10,8,6,1,132,180,178,130,15,0,161,209,233,132,156,1,11,28,1,156,220,236,216,13,0,33,1,4,109,101,116,97,12,108,97,115,116,95,115,121,110,99,95,97,116,17,1,208,149,239,158,11,0,161,168,150,210,229,5,33,4,1,177,185,133,253,10,0,161,208,149,239,158,11,3,9,1,207,140,163,157,10,0,161,213,152,246,243,7,6,9,1,228,231,188,223,9,0,161,230,228,200,164,7,5,2,1,160,143,150,180,9,0,161,244,180,255,255,8,1,6,1,244,180,255,255,8,0,161,207,140,163,157,10,8,2,1,213,152,246,243,7,0,161,141,170,152,151,3,38,7,1,183,242,197,235,7,0,161,160,143,150,180,9,5,2,10,159,171,231,213,7,0,39,1,4,100,97,116,97,4,100,97,116,97,1,39,1,4,100,97,116,97,4,109,101,116,97,1,39,1,4,100,97,116,97,7,99,111,109,109,101,110,116,0,40,0,159,171,231,213,7,0,2,105,100,1,119,36,52,98,53,54,48,99,50,100,45,51,102,51,57,45,52,48,56,54,45,97,97,51,100,45,99,50,53,57,48,100,49,50,57,56,53,48,40,0,159,171,231,213,7,0,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,52,99,54,53,56,56,49,55,45,50,48,100,98,45,52,102,53,54,45,98,55,102,57,45,48,54,51,55,97,50,50,100,102,101,98,54,40,0,159,171,231,213,7,0,6,104,101,105,103,104,116,1,122,0,0,0,0,0,0,0,60,40,0,159,171,231,213,7,0,10,118,105,115,105,98,105,108,105,116,121,1,120,40,0,159,171,231,213,7,0,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,69,115,240,40,0,159,171,231,213,7,0,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,69,115,240,39,0,159,171,231,213,7,0,5,99,101,108,108,115,1,1,240,167,210,197,7,0,161,156,220,236,216,13,16,2,1,230,228,200,164,7,0,161,151,235,176,134,1,3,6,2,246,239,129,224,6,0,161,185,162,129,222,6,4,6,168,246,239,129,224,6,5,1,122,0,0,0,0,102,88,25,34,1,185,162,129,222,6,0,161,209,145,212,136,2,2,5,1,209,227,201,148,6,0,161,254,152,161,193,3,1,24,1,168,150,210,229,5,0,161,228,231,188,223,9,1,34,1,254,152,161,193,3,0,161,240,167,210,197,7,1,2,1,141,170,152,151,3,0,161,171,144,240,132,15,5,39,1,209,145,212,136,2,0,161,132,180,178,130,15,27,3,2,209,233,132,156,1,0,161,160,143,150,180,9,5,1,161,183,242,197,235,7,1,11,1,151,235,176,134,1,0,161,209,227,201,148,6,23,4,22,160,143,150,180,9,1,0,6,228,231,188,223,9,1,0,2,132,180,178,130,15,1,0,28,230,228,200,164,7,1,0,6,168,150,210,229,5,1,0,34,171,144,240,132,15,1,0,6,141,170,152,151,3,1,0,39,207,140,163,157,10,1,0,9,240,167,210,197,7,1,0,2,209,227,201,148,6,1,0,24,208,149,239,158,11,1,0,4,177,185,133,253,10,1,0,9,244,180,255,255,8,1,0,2,213,152,246,243,7,1,0,7,209,233,132,156,1,1,0,12,151,235,176,134,1,1,0,4,183,242,197,235,7,1,0,2,209,145,212,136,2,1,0,3,185,162,129,222,6,1,0,5,246,239,129,224,6,1,0,6,156,220,236,216,13,1,0,17,254,152,161,193,3,1,0,2],"88fa36b2-6d72-44de-b0df-d3b2e6d744d6":[18,1,177,156,240,229,15,0,161,147,222,174,199,5,34,4,1,224,147,154,227,15,0,161,247,228,241,157,4,5,2,1,236,146,204,211,15,0,161,177,156,240,229,15,3,10,1,254,245,134,175,14,0,161,194,254,163,142,12,1,5,1,184,249,249,169,13,0,161,245,165,251,164,5,11,25,81,221,254,206,225,12,0,39,1,4,100,97,116,97,4,100,97,116,97,1,39,1,4,100,97,116,97,4,109,101,116,97,1,39,1,4,100,97,116,97,7,99,111,109,109,101,110,116,0,40,0,221,254,206,225,12,0,2,105,100,1,119,36,56,56,102,97,51,54,98,50,45,54,100,55,50,45,52,52,100,101,45,98,48,100,102,45,100,51,98,50,101,54,100,55,52,52,100,54,40,0,221,254,206,225,12,0,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,52,99,54,53,56,56,49,55,45,50,48,100,98,45,52,102,53,54,45,98,55,102,57,45,48,54,51,55,97,50,50,100,102,101,98,54,40,0,221,254,206,225,12,0,6,104,101,105,103,104,116,1,122,0,0,0,0,0,0,0,60,40,0,221,254,206,225,12,0,10,118,105,115,105,98,105,108,105,116,121,1,120,40,0,221,254,206,225,12,0,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,75,60,209,33,0,221,254,206,225,12,0,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,39,0,221,254,206,225,12,0,5,99,101,108,108,115,1,39,0,221,254,206,225,12,9,6,70,114,115,115,74,100,1,40,0,221,254,206,225,12,10,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,3,40,0,221,254,206,225,12,10,4,100,97,116,97,1,119,36,48,52,102,52,55,48,51,55,45,49,56,54,97,45,52,56,55,102,45,98,54,56,101,45,102,49,98,102,97,48,102,101,54,54,53,101,161,221,254,206,225,12,8,1,39,0,221,254,206,225,12,9,6,84,102,117,121,104,84,1,40,0,221,254,206,225,12,14,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,75,61,9,40,0,221,254,206,225,12,14,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,0,40,0,221,254,206,225,12,14,4,100,97,116,97,1,119,27,229,150,157,228,186,134,229,165,189,229,164,154,233,133,146,231,157,161,229,144,167,231,157,161,229,144,167,40,0,221,254,206,225,12,14,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,75,61,9,161,221,254,206,225,12,13,1,39,0,221,254,206,225,12,9,6,52,57,85,69,86,53,1,40,0,221,254,206,225,12,20,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,75,61,9,33,0,221,254,206,225,12,20,4,100,97,116,97,1,33,0,221,254,206,225,12,20,12,105,110,99,108,117,100,101,95,116,105,109,101,1,33,0,221,254,206,225,12,20,8,105,115,95,114,97,110,103,101,1,33,0,221,254,206,225,12,20,11,114,101,109,105,110,100,101,114,95,105,100,1,33,0,221,254,206,225,12,20,10,102,105,101,108,100,95,116,121,112,101,1,33,0,221,254,206,225,12,20,13,101,110,100,95,116,105,109,101,115,116,97,109,112,1,33,0,221,254,206,225,12,20,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,161,221,254,206,225,12,19,1,161,221,254,206,225,12,23,1,161,221,254,206,225,12,22,1,161,221,254,206,225,12,26,1,161,221,254,206,225,12,27,1,161,221,254,206,225,12,24,1,161,221,254,206,225,12,25,1,161,221,254,206,225,12,28,1,161,221,254,206,225,12,29,1,161,221,254,206,225,12,31,1,161,221,254,206,225,12,34,1,161,221,254,206,225,12,33,1,161,221,254,206,225,12,30,1,161,221,254,206,225,12,32,1,161,221,254,206,225,12,35,1,161,221,254,206,225,12,36,1,161,221,254,206,225,12,37,1,161,221,254,206,225,12,41,1,161,221,254,206,225,12,40,1,161,221,254,206,225,12,43,1,161,221,254,206,225,12,42,1,161,221,254,206,225,12,38,1,161,221,254,206,225,12,39,1,161,221,254,206,225,12,44,1,161,221,254,206,225,12,45,1,161,221,254,206,225,12,48,1,161,221,254,206,225,12,49,1,161,221,254,206,225,12,47,1,161,221,254,206,225,12,50,1,161,221,254,206,225,12,46,1,161,221,254,206,225,12,51,1,161,221,254,206,225,12,52,1,161,221,254,206,225,12,53,1,168,221,254,206,225,12,55,1,122,0,0,0,0,0,0,0,2,168,221,254,206,225,12,56,1,119,0,168,221,254,206,225,12,59,1,121,168,221,254,206,225,12,54,1,119,0,168,221,254,206,225,12,57,1,119,10,49,55,49,54,54,51,56,56,49,52,168,221,254,206,225,12,58,1,121,168,221,254,206,225,12,60,1,122,0,0,0,0,102,75,61,9,161,221,254,206,225,12,61,1,39,0,221,254,206,225,12,9,6,120,69,81,65,111,75,1,40,0,221,254,206,225,12,70,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,75,61,9,40,0,221,254,206,225,12,70,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,7,40,0,221,254,206,225,12,70,4,100,97,116,97,1,119,79,123,34,111,112,116,105,111,110,115,34,58,91,123,34,105,100,34,58,34,109,100,117,67,34,44,34,110,97,109,101,34,58,34,34,44,34,99,111,108,111,114,34,58,34,80,117,114,112,108,101,34,125,93,44,34,115,101,108,101,99,116,101,100,95,111,112,116,105,111,110,95,105,100,115,34,58,91,93,125,40,0,221,254,206,225,12,70,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,75,61,9,168,221,254,206,225,12,69,1,122,0,0,0,0,102,75,66,138,39,0,221,254,206,225,12,9,6,89,53,52,81,73,115,1,40,0,221,254,206,225,12,76,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,75,66,138,40,0,221,254,206,225,12,76,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,0,40,0,221,254,206,225,12,76,4,100,97,116,97,1,119,9,232,129,154,232,129,154,228,188,154,40,0,221,254,206,225,12,76,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,75,66,138,1,194,254,163,142,12,0,161,184,249,249,169,13,24,2,1,169,227,206,252,11,0,161,246,235,254,152,6,12,3,1,139,151,193,189,8,0,161,211,169,247,209,2,8,2,1,195,158,149,248,6,0,161,236,146,204,211,15,9,6,1,246,235,254,152,6,0,33,1,4,109,101,116,97,12,108,97,115,116,95,115,121,110,99,95,97,116,13,1,147,222,174,199,5,0,161,169,227,206,252,11,2,35,2,245,165,251,164,5,0,161,247,228,241,157,4,5,1,161,224,147,154,227,15,1,11,1,247,228,241,157,4,0,161,139,151,193,189,8,1,6,1,143,134,194,128,4,0,161,195,158,149,248,6,5,39,1,211,169,247,209,2,0,161,168,233,136,186,2,6,9,1,168,233,136,186,2,0,161,143,134,194,128,4,38,7,2,187,223,143,158,1,0,161,254,245,134,175,14,4,6,168,187,223,143,158,1,5,1,122,0,0,0,0,102,88,25,35,18,224,147,154,227,15,1,0,2,194,254,163,142,12,1,0,2,195,158,149,248,6,1,0,6,168,233,136,186,2,1,0,7,169,227,206,252,11,1,0,3,139,151,193,189,8,1,0,2,236,146,204,211,15,1,0,10,143,134,194,128,4,1,0,39,177,156,240,229,15,1,0,4,147,222,174,199,5,1,0,35,211,169,247,209,2,1,0,9,245,165,251,164,5,1,0,12,246,235,254,152,6,1,0,13,247,228,241,157,4,1,0,6,184,249,249,169,13,1,0,25,187,223,143,158,1,1,0,6,221,254,206,225,12,5,8,1,13,1,19,1,22,40,69,1,254,245,134,175,14,1,0,5],"1047f2d0-3757-4799-bcf2-e8f97464d2b5":[56,1,136,227,164,244,15,0,161,217,223,147,169,3,9,24,1,255,191,221,240,15,0,161,194,230,250,156,15,1,6,1,250,198,240,231,15,0,161,225,252,149,175,6,15,2,1,212,143,130,218,15,0,161,134,233,204,201,4,1,5,1,203,248,239,208,15,0,161,244,182,169,180,5,5,4,1,157,234,181,165,15,0,161,181,209,194,135,15,0,65,1,194,230,250,156,15,0,161,233,156,145,228,3,25,2,1,147,237,217,153,15,0,161,203,130,173,226,5,11,26,1,181,209,194,135,15,0,161,136,227,164,244,15,23,1,1,177,145,243,240,13,0,161,233,249,213,131,12,1,25,1,238,159,247,242,12,0,161,203,248,239,208,15,3,4,1,219,150,244,224,12,0,161,250,198,240,231,15,1,2,1,131,245,207,191,12,0,161,199,180,231,144,7,7,6,1,189,250,148,144,12,0,161,177,145,243,240,13,24,4,1,233,249,213,131,12,0,161,253,161,181,242,3,3,2,16,232,166,159,250,11,0,161,201,208,178,205,1,0,1,39,0,217,152,170,150,10,8,6,70,114,115,115,74,100,1,40,0,232,166,159,250,11,1,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,65,140,47,40,0,232,166,159,250,11,1,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,3,40,0,232,166,159,250,11,1,4,100,97,116,97,1,119,36,48,52,102,52,55,48,51,55,45,49,56,54,97,45,52,56,55,102,45,98,54,56,101,45,102,49,98,102,97,48,102,101,54,54,53,101,40,0,232,166,159,250,11,1,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,65,140,47,161,232,166,159,250,11,0,1,39,0,217,152,170,150,10,8,6,115,111,118,85,116,69,1,40,0,232,166,159,250,11,7,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,65,147,55,33,0,232,166,159,250,11,7,10,102,105,101,108,100,95,116,121,112,101,1,33,0,232,166,159,250,11,7,4,100,97,116,97,1,33,0,232,166,159,250,11,7,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,161,232,166,159,250,11,6,1,168,232,166,159,250,11,10,1,119,73,50,100,54,48,51,48,99,51,45,57,55,49,101,45,52,100,52,53,45,98,53,55,48,45,100,101,57,50,102,100,101,97,100,97,101,54,44,102,99,100,54,101,102,56,99,45,56,99,100,54,45,52,49,98,51,45,57,50,52,53,45,57,57,56,57,51,49,100,52,57,97,49,54,168,232,166,159,250,11,9,1,122,0,0,0,0,0,0,0,4,168,232,166,159,250,11,11,1,122,0,0,0,0,102,65,147,55,1,196,171,189,241,11,0,161,226,147,204,180,8,1,2,1,146,223,210,202,11,0,161,219,150,244,224,12,1,68,1,226,144,128,160,11,0,161,255,191,221,240,15,5,2,5,140,145,178,142,11,0,40,0,217,152,170,150,10,1,36,53,101,48,55,56,53,50,97,45,102,98,53,100,45,53,49,97,52,45,57,98,48,97,45,98,99,100,53,99,54,52,102,57,49,53,100,1,119,6,226,152,152,239,184,143,161,227,133,179,139,3,0,2,40,0,217,152,170,150,10,1,36,54,53,50,51,98,51,50,55,45,100,100,55,49,45,53,97,53,97,45,56,100,98,56,45,102,99,100,98,97,56,100,101,48,57,97,50,1,121,161,140,145,178,142,11,2,1,168,140,145,178,142,11,4,1,122,0,0,0,0,102,78,229,130,1,201,229,189,239,10,0,161,204,184,157,221,9,7,10,9,217,152,170,150,10,0,39,1,4,100,97,116,97,4,100,97,116,97,1,39,1,4,100,97,116,97,4,109,101,116,97,1,39,1,4,100,97,116,97,7,99,111,109,109,101,110,116,0,40,0,217,152,170,150,10,0,2,105,100,1,119,36,49,48,52,55,102,50,100,48,45,51,55,53,55,45,52,55,57,57,45,98,99,102,50,45,101,56,102,57,55,52,54,52,100,50,98,53,40,0,217,152,170,150,10,0,6,104,101,105,103,104,116,1,122,0,0,0,0,0,0,0,60,40,0,217,152,170,150,10,0,10,118,105,115,105,98,105,108,105,116,121,1,120,40,0,217,152,170,150,10,0,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,48,108,138,33,0,217,152,170,150,10,0,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,39,0,217,152,170,150,10,0,5,99,101,108,108,115,1,1,174,133,132,239,9,0,161,252,135,150,213,5,1,2,1,204,184,157,221,9,0,161,157,234,181,165,15,64,8,1,228,231,180,195,9,0,161,228,255,253,171,9,5,5,1,228,255,253,171,9,0,161,147,193,234,210,6,15,10,66,216,221,232,222,8,0,161,217,152,170,150,10,7,1,39,0,217,152,170,150,10,8,6,54,76,70,72,66,54,1,40,0,216,221,232,222,8,1,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,60,201,79,33,0,216,221,232,222,8,1,4,100,97,116,97,1,33,0,216,221,232,222,8,1,10,102,105,101,108,100,95,116,121,112,101,1,33,0,216,221,232,222,8,1,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,161,216,221,232,222,8,0,1,39,0,217,152,170,150,10,8,6,89,53,52,81,73,115,1,40,0,216,221,232,222,8,7,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,60,201,92,33,0,216,221,232,222,8,7,10,102,105,101,108,100,95,116,121,112,101,1,33,0,216,221,232,222,8,7,4,100,97,116,97,1,33,0,216,221,232,222,8,7,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,161,216,221,232,222,8,6,1,161,216,221,232,222,8,9,1,161,216,221,232,222,8,10,1,161,216,221,232,222,8,11,1,161,216,221,232,222,8,12,1,161,216,221,232,222,8,13,1,161,216,221,232,222,8,14,1,161,216,221,232,222,8,15,1,161,216,221,232,222,8,16,1,161,216,221,232,222,8,18,1,161,216,221,232,222,8,17,1,161,216,221,232,222,8,19,1,161,216,221,232,222,8,20,1,161,216,221,232,222,8,21,1,161,216,221,232,222,8,22,1,161,216,221,232,222,8,23,1,161,216,221,232,222,8,24,1,161,216,221,232,222,8,25,1,161,216,221,232,222,8,26,1,161,216,221,232,222,8,27,1,161,216,221,232,222,8,28,1,168,216,221,232,222,8,30,1,122,0,0,0,0,0,0,0,0,168,216,221,232,222,8,29,1,119,72,106,115,106,115,104,100,104,100,104,98,32,115,104,115,104,115,104,104,115,104,115,104,115,106,115,106,115,106,115,106,32,117,115,105,115,105,115,106,115,106,230,128,157,230,128,157,229,167,144,32,85,231,155,190,232,174,176,229,190,151,229,176,177,232,161,140,229,147,136,229,147,136,168,216,221,232,222,8,31,1,122,0,0,0,0,102,60,201,101,161,216,221,232,222,8,32,1,161,216,221,232,222,8,4,1,161,216,221,232,222,8,3,1,161,216,221,232,222,8,5,1,161,216,221,232,222,8,36,1,161,216,221,232,222,8,38,1,161,216,221,232,222,8,37,1,161,216,221,232,222,8,39,1,161,216,221,232,222,8,40,1,168,216,221,232,222,8,42,1,122,0,0,0,0,0,0,0,6,168,216,221,232,222,8,41,1,119,6,104,97,104,97,104,97,168,216,221,232,222,8,43,1,122,0,0,0,0,102,60,204,16,161,216,221,232,222,8,44,1,39,0,217,152,170,150,10,8,6,86,89,52,50,103,49,1,40,0,216,221,232,222,8,49,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,61,144,108,33,0,216,221,232,222,8,49,10,102,105,101,108,100,95,116,121,112,101,1,33,0,216,221,232,222,8,49,4,100,97,116,97,1,33,0,216,221,232,222,8,49,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,161,216,221,232,222,8,48,1,161,216,221,232,222,8,51,1,161,216,221,232,222,8,52,1,161,216,221,232,222,8,53,1,161,216,221,232,222,8,54,1,161,216,221,232,222,8,55,1,161,216,221,232,222,8,56,1,161,216,221,232,222,8,57,1,161,216,221,232,222,8,58,1,168,216,221,232,222,8,59,1,122,0,0,0,0,0,0,0,1,168,216,221,232,222,8,60,1,119,6,54,54,54,48,48,48,168,216,221,232,222,8,61,1,122,0,0,0,0,102,61,145,64,1,226,147,204,180,8,0,161,131,245,207,191,12,5,2,1,210,134,148,169,8,0,161,242,252,231,244,2,11,6,1,249,253,252,152,8,0,161,146,223,210,202,11,67,49,1,171,244,155,131,8,0,161,249,253,252,152,8,48,9,1,155,170,193,254,7,0,161,211,253,225,214,2,33,4,1,199,180,231,144,7,0,161,228,231,180,195,9,4,8,1,147,193,234,210,6,0,161,206,133,235,151,4,111,20,1,225,252,149,175,6,0,161,238,159,247,242,12,3,16,3,149,130,129,246,5,0,161,217,152,170,150,10,7,1,33,0,217,152,170,150,10,8,6,89,53,52,81,73,115,1,0,4,2,203,130,173,226,5,0,161,255,191,221,240,15,5,1,161,226,144,128,160,11,1,11,1,252,135,150,213,5,0,161,196,171,189,241,11,1,2,7,233,143,142,198,5,0,161,149,130,129,246,5,0,1,39,0,217,152,170,150,10,8,6,106,87,101,95,116,54,1,40,0,233,143,142,198,5,1,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,67,54,21,39,0,233,143,142,198,5,1,4,100,97,116,97,0,8,0,233,143,142,198,5,3,1,119,36,49,48,52,55,102,50,100,48,45,51,55,53,55,45,52,55,57,57,45,98,99,102,50,45,101,56,102,57,55,52,54,52,100,50,98,53,40,0,233,143,142,198,5,1,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,10,40,0,233,143,142,198,5,1,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,67,54,21,1,244,182,169,180,5,0,161,174,133,132,239,9,1,6,1,211,218,202,203,4,0,161,253,204,235,17,8,6,1,134,233,204,201,4,0,161,147,237,217,153,15,25,2,1,206,133,235,151,4,0,33,1,4,109,101,116,97,12,108,97,115,116,95,115,121,110,99,95,97,116,118,1,253,161,181,242,3,0,161,180,216,208,192,2,25,4,1,233,156,145,228,3,0,161,173,195,221,80,38,26,1,217,223,147,169,3,0,161,171,244,155,131,8,8,10,2,134,177,139,145,3,0,161,212,143,130,218,15,4,7,168,134,177,139,145,3,6,1,122,0,0,0,0,102,88,25,34,4,227,133,179,139,3,0,161,232,166,159,250,11,12,1,168,201,208,178,205,1,4,1,119,3,89,101,115,168,201,208,178,205,1,3,1,122,0,0,0,0,0,0,0,5,168,201,208,178,205,1,5,1,122,0,0,0,0,102,67,57,98,1,190,184,172,134,3,0,161,189,250,148,144,12,3,7,1,242,252,231,244,2,0,161,201,229,189,239,10,9,12,1,211,253,225,214,2,0,161,143,144,136,34,1,34,1,180,216,208,192,2,0,161,210,134,148,169,8,5,26,6,201,208,178,205,1,0,161,216,221,232,222,8,62,1,39,0,217,152,170,150,10,8,6,89,80,102,105,50,109,1,40,0,201,208,178,205,1,1,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,61,250,104,33,0,201,208,178,205,1,1,10,102,105,101,108,100,95,116,121,112,101,1,33,0,201,208,178,205,1,1,4,100,97,116,97,1,33,0,201,208,178,205,1,1,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,1,173,195,221,80,0,161,211,218,202,203,4,5,39,1,143,144,136,34,0,161,190,184,172,134,3,6,2,1,253,204,235,17,0,161,155,170,193,254,7,3,9,56,189,250,148,144,12,1,0,4,190,184,172,134,3,1,0,7,194,230,250,156,15,1,0,2,131,245,207,191,12,1,0,6,196,171,189,241,11,1,0,2,134,233,204,201,4,1,0,2,199,180,231,144,7,1,0,8,136,227,164,244,15,1,0,24,201,229,189,239,10,1,0,10,201,208,178,205,1,2,0,1,3,3,203,248,239,208,15,1,0,4,204,184,157,221,9,1,0,8,203,130,173,226,5,1,0,12,206,133,235,151,4,1,0,118,143,144,136,34,1,0,2,140,145,178,142,11,2,1,2,4,1,134,177,139,145,3,1,0,7,146,223,210,202,11,1,0,68,147,193,234,210,6,1,0,20,210,134,148,169,8,1,0,6,211,253,225,214,2,1,0,34,211,218,202,203,4,1,0,6,147,237,217,153,15,1,0,26,212,143,130,218,15,1,0,5,217,223,147,169,3,1,0,10,217,152,170,150,10,1,7,1,219,150,244,224,12,1,0,2,155,170,193,254,7,1,0,4,157,234,181,165,15,1,0,65,216,221,232,222,8,6,0,1,3,4,9,24,36,9,48,1,51,12,149,130,129,246,5,1,0,6,225,252,149,175,6,1,0,16,226,147,204,180,8,1,0,2,226,144,128,160,11,1,0,2,228,255,253,171,9,1,0,10,228,231,180,195,9,1,0,5,227,133,179,139,3,1,0,1,232,166,159,250,11,3,0,1,6,1,9,4,233,249,213,131,12,1,0,2,233,156,145,228,3,1,0,26,171,244,155,131,8,1,0,9,233,143,142,198,5,1,0,1,173,195,221,80,1,0,39,238,159,247,242,12,1,0,4,174,133,132,239,9,1,0,2,177,145,243,240,13,1,0,25,242,252,231,244,2,1,0,12,244,182,169,180,5,1,0,6,181,209,194,135,15,1,0,1,180,216,208,192,2,1,0,26,249,253,252,152,8,1,0,49,250,198,240,231,15,1,0,2,252,135,150,213,5,1,0,2,253,204,235,17,1,0,9,253,161,181,242,3,1,0,4,255,191,221,240,15,1,0,6],"3aadcc41-4b4d-4570-a5de-06ebe3f460ec":[19,1,198,208,139,248,15,0,161,232,249,153,165,14,34,4,2,178,201,178,226,15,0,161,249,156,177,132,11,4,6,168,178,201,178,226,15,5,1,122,0,0,0,0,102,88,25,35,1,217,221,136,169,15,0,161,247,190,164,130,3,1,6,1,239,247,162,248,14,0,33,1,4,109,101,116,97,12,108,97,115,116,95,115,121,110,99,95,97,116,8,1,130,223,137,178,14,0,161,238,205,207,218,4,8,6,1,232,249,153,165,14,0,161,187,231,222,18,1,35,1,214,231,164,137,11,0,161,217,221,136,169,15,5,2,1,249,156,177,132,11,0,161,163,196,200,219,4,1,5,1,238,165,252,166,9,0,161,192,192,155,154,2,1,25,1,157,244,237,240,7,0,161,129,142,211,195,2,6,9,1,163,196,200,219,4,0,161,238,165,252,166,9,24,2,1,238,205,207,218,4,0,161,198,208,139,248,15,3,9,1,220,144,217,197,4,0,161,130,223,137,178,14,5,39,2,176,222,138,182,3,0,161,217,221,136,169,15,5,1,161,214,231,164,137,11,1,9,19,217,192,185,135,3,0,39,1,4,100,97,116,97,4,100,97,116,97,1,39,1,4,100,97,116,97,4,109,101,116,97,1,39,1,4,100,97,116,97,7,99,111,109,109,101,110,116,0,40,0,217,192,185,135,3,0,2,105,100,1,119,36,51,97,97,100,99,99,52,49,45,52,98,52,100,45,52,53,55,48,45,97,53,100,101,45,48,54,101,98,101,51,102,52,54,48,101,99,40,0,217,192,185,135,3,0,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,52,99,54,53,56,56,49,55,45,50,48,100,98,45,52,102,53,54,45,98,55,102,57,45,48,54,51,55,97,50,50,100,102,101,98,54,40,0,217,192,185,135,3,0,6,104,101,105,103,104,116,1,122,0,0,0,0,0,0,0,60,40,0,217,192,185,135,3,0,10,118,105,115,105,98,105,108,105,116,121,1,120,40,0,217,192,185,135,3,0,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,75,60,195,33,0,217,192,185,135,3,0,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,39,0,217,192,185,135,3,0,5,99,101,108,108,115,1,39,0,217,192,185,135,3,9,6,70,114,115,115,74,100,1,40,0,217,192,185,135,3,10,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,3,40,0,217,192,185,135,3,10,4,100,97,116,97,1,119,36,48,52,102,52,55,48,51,55,45,49,56,54,97,45,52,56,55,102,45,98,54,56,101,45,102,49,98,102,97,48,102,101,54,54,53,101,168,217,192,185,135,3,8,1,122,0,0,0,0,102,75,60,207,39,0,217,192,185,135,3,9,6,84,102,117,121,104,84,1,40,0,217,192,185,135,3,14,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,75,60,207,40,0,217,192,185,135,3,14,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,0,40,0,217,192,185,135,3,14,4,100,97,116,97,1,119,18,229,147,136,229,147,136,229,147,136,229,147,136,229,147,136,229,147,136,40,0,217,192,185,135,3,14,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,75,60,207,1,247,190,164,130,3,0,161,157,244,237,240,7,8,2,1,129,142,211,195,2,0,161,220,144,217,197,4,38,7,1,192,192,155,154,2,0,161,176,222,138,182,3,9,2,1,187,231,222,18,0,161,239,247,162,248,14,7,2,19,192,192,155,154,2,1,0,2,129,142,211,195,2,1,0,7,130,223,137,178,14,1,0,6,163,196,200,219,4,1,0,2,198,208,139,248,15,1,0,4,232,249,153,165,14,1,0,35,238,165,252,166,9,1,0,25,238,205,207,218,4,1,0,9,176,222,138,182,3,1,0,10,239,247,162,248,14,1,0,8,178,201,178,226,15,1,0,6,214,231,164,137,11,1,0,2,247,190,164,130,3,1,0,2,217,221,136,169,15,1,0,6,249,156,177,132,11,1,0,5,187,231,222,18,1,0,2,220,144,217,197,4,1,0,39,157,244,237,240,7,1,0,9,217,192,185,135,3,1,8,1]} \ No newline at end of file diff --git a/frontend/appflowy_web_app/cypress/fixtures/database/rows/87bc006e-c1eb-47fd-9ac6-e39b17956369.json b/frontend/appflowy_web_app/cypress/fixtures/database/rows/87bc006e-c1eb-47fd-9ac6-e39b17956369.json deleted file mode 100644 index 4eefa98010..0000000000 --- a/frontend/appflowy_web_app/cypress/fixtures/database/rows/87bc006e-c1eb-47fd-9ac6-e39b17956369.json +++ /dev/null @@ -1 +0,0 @@ -{"9cde7c15-347c-447a-9ea1-76bc3a8d4e96":[2,10,179,237,201,251,15,0,39,1,4,100,97,116,97,4,100,97,116,97,1,39,1,4,100,97,116,97,4,109,101,116,97,1,39,1,4,100,97,116,97,7,99,111,109,109,101,110,116,0,40,0,179,237,201,251,15,0,2,105,100,1,119,36,57,99,100,101,55,99,49,53,45,51,52,55,99,45,52,52,55,97,45,57,101,97,49,45,55,54,98,99,51,97,56,100,52,101,57,54,40,0,179,237,201,251,15,0,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,56,55,98,99,48,48,54,101,45,99,49,101,98,45,52,55,102,100,45,57,97,99,54,45,101,51,57,98,49,55,57,53,54,51,54,57,40,0,179,237,201,251,15,0,6,104,101,105,103,104,116,1,122,0,0,0,0,0,0,0,60,40,0,179,237,201,251,15,0,10,118,105,115,105,98,105,108,105,116,121,1,120,40,0,179,237,201,251,15,0,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,97,115,63,40,0,179,237,201,251,15,0,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,97,115,63,39,0,179,237,201,251,15,0,5,99,101,108,108,115,1,2,181,140,221,245,11,0,33,1,4,109,101,116,97,12,108,97,115,116,95,115,121,110,99,95,97,116,5,168,181,140,221,245,11,4,1,122,0,0,0,0,102,97,116,211,1,181,140,221,245,11,1,0,5],"16da0f68-f414-4c59-95eb-3b45b4b61dc3":[2,38,162,144,245,250,8,0,39,1,4,100,97,116,97,4,100,97,116,97,1,39,1,4,100,97,116,97,4,109,101,116,97,1,39,1,4,100,97,116,97,7,99,111,109,109,101,110,116,0,40,0,162,144,245,250,8,0,2,105,100,1,119,36,49,54,100,97,48,102,54,56,45,102,52,49,52,45,52,99,53,57,45,57,53,101,98,45,51,98,52,53,98,52,98,54,49,100,99,51,40,0,162,144,245,250,8,0,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,56,55,98,99,48,48,54,101,45,99,49,101,98,45,52,55,102,100,45,57,97,99,54,45,101,51,57,98,49,55,57,53,54,51,54,57,40,0,162,144,245,250,8,0,6,104,101,105,103,104,116,1,122,0,0,0,0,0,0,0,60,40,0,162,144,245,250,8,0,10,118,105,115,105,98,105,108,105,116,121,1,120,40,0,162,144,245,250,8,0,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,97,115,234,33,0,162,144,245,250,8,0,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,39,0,162,144,245,250,8,0,5,99,101,108,108,115,1,39,0,162,144,245,250,8,9,6,77,67,57,90,97,69,1,40,0,162,144,245,250,8,10,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,0,40,0,162,144,245,250,8,10,4,100,97,116,97,1,119,3,49,50,51,39,0,162,144,245,250,8,9,6,108,73,72,113,101,57,1,40,0,162,144,245,250,8,13,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,5,40,0,162,144,245,250,8,13,4,100,97,116,97,1,119,3,89,101,115,39,0,162,144,245,250,8,9,6,111,121,80,121,97,117,1,40,0,162,144,245,250,8,16,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,1,40,0,162,144,245,250,8,16,4,100,97,116,97,1,119,3,54,48,49,39,0,162,144,245,250,8,9,6,53,69,90,81,65,87,1,40,0,162,144,245,250,8,19,4,100,97,116,97,1,119,4,71,102,87,50,40,0,162,144,245,250,8,19,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,3,161,162,144,245,250,8,8,1,39,0,162,144,245,250,8,9,6,102,116,73,53,52,121,1,40,0,162,144,245,250,8,23,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,97,116,46,40,0,162,144,245,250,8,23,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,4,40,0,162,144,245,250,8,23,4,100,97,116,97,1,119,4,104,57,106,100,40,0,162,144,245,250,8,23,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,97,116,46,161,162,144,245,250,8,22,1,39,0,162,144,245,250,8,9,6,84,79,87,83,70,104,1,40,0,162,144,245,250,8,29,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,97,116,61,33,0,162,144,245,250,8,29,4,100,97,116,97,1,33,0,162,144,245,250,8,29,10,102,105,101,108,100,95,116,121,112,101,1,33,0,162,144,245,250,8,29,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,168,162,144,245,250,8,28,1,122,0,0,0,0,102,97,116,63,168,162,144,245,250,8,31,1,119,88,123,34,111,112,116,105,111,110,115,34,58,91,123,34,105,100,34,58,34,99,55,88,104,34,44,34,110,97,109,101,34,58,34,49,49,49,34,44,34,99,111,108,111,114,34,58,34,80,117,114,112,108,101,34,125,93,44,34,115,101,108,101,99,116,101,100,95,111,112,116,105,111,110,95,105,100,115,34,58,91,34,99,55,88,104,34,93,125,168,162,144,245,250,8,32,1,122,0,0,0,0,0,0,0,7,168,162,144,245,250,8,33,1,122,0,0,0,0,102,97,116,63,2,161,140,129,164,8,0,33,1,4,109,101,116,97,12,108,97,115,116,95,115,121,110,99,95,97,116,5,168,161,140,129,164,8,4,1,122,0,0,0,0,102,97,116,213,2,161,140,129,164,8,1,0,5,162,144,245,250,8,4,8,1,22,1,28,1,31,3],"9e5efed0-6220-48be-8704-d8ec0166796c":[2,2,144,209,245,144,10,0,33,1,4,109,101,116,97,12,108,97,115,116,95,115,121,110,99,95,97,116,5,168,144,209,245,144,10,4,1,122,0,0,0,0,102,97,116,215,56,246,244,204,133,9,0,39,1,4,100,97,116,97,4,100,97,116,97,1,39,1,4,100,97,116,97,4,109,101,116,97,1,39,1,4,100,97,116,97,7,99,111,109,109,101,110,116,0,40,0,246,244,204,133,9,0,2,105,100,1,119,36,57,101,53,101,102,101,100,48,45,54,50,50,48,45,52,56,98,101,45,56,55,48,52,45,100,56,101,99,48,49,54,54,55,57,54,99,40,0,246,244,204,133,9,0,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,56,55,98,99,48,48,54,101,45,99,49,101,98,45,52,55,102,100,45,57,97,99,54,45,101,51,57,98,49,55,57,53,54,51,54,57,40,0,246,244,204,133,9,0,6,104,101,105,103,104,116,1,122,0,0,0,0,0,0,0,60,40,0,246,244,204,133,9,0,10,118,105,115,105,98,105,108,105,116,121,1,120,40,0,246,244,204,133,9,0,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,97,115,238,33,0,246,244,204,133,9,0,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,39,0,246,244,204,133,9,0,5,99,101,108,108,115,1,39,0,246,244,204,133,9,9,6,108,73,72,113,101,57,1,40,0,246,244,204,133,9,10,4,100,97,116,97,1,119,3,89,101,115,40,0,246,244,204,133,9,10,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,5,39,0,246,244,204,133,9,9,6,77,67,57,90,97,69,1,33,0,246,244,204,133,9,13,10,102,105,101,108,100,95,116,121,112,101,1,33,0,246,244,204,133,9,13,4,100,97,116,97,1,39,0,246,244,204,133,9,9,6,111,121,80,121,97,117,1,33,0,246,244,204,133,9,16,10,102,105,101,108,100,95,116,121,112,101,1,33,0,246,244,204,133,9,16,4,100,97,116,97,1,39,0,246,244,204,133,9,9,6,53,69,90,81,65,87,1,40,0,246,244,204,133,9,19,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,3,40,0,246,244,204,133,9,19,4,100,97,116,97,1,119,4,71,102,87,50,161,246,244,204,133,9,8,1,40,0,246,244,204,133,9,16,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,97,116,17,168,246,244,204,133,9,18,1,119,3,54,48,51,168,246,244,204,133,9,17,1,122,0,0,0,0,0,0,0,1,40,0,246,244,204,133,9,16,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,97,116,17,161,246,244,204,133,9,22,1,40,0,246,244,204,133,9,13,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,97,116,39,168,246,244,204,133,9,14,1,122,0,0,0,0,0,0,0,0,168,246,244,204,133,9,15,1,119,7,49,50,51,57,57,48,48,40,0,246,244,204,133,9,13,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,97,116,39,161,246,244,204,133,9,27,1,39,0,246,244,204,133,9,9,6,102,116,73,53,52,121,1,40,0,246,244,204,133,9,33,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,97,116,51,40,0,246,244,204,133,9,33,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,4,40,0,246,244,204,133,9,33,4,100,97,116,97,1,119,4,104,57,106,100,40,0,246,244,204,133,9,33,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,97,116,51,161,246,244,204,133,9,32,1,39,0,246,244,204,133,9,9,6,84,79,87,83,70,104,1,40,0,246,244,204,133,9,39,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,97,116,71,33,0,246,244,204,133,9,39,4,100,97,116,97,1,33,0,246,244,204,133,9,39,10,102,105,101,108,100,95,116,121,112,101,1,33,0,246,244,204,133,9,39,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,161,246,244,204,133,9,38,1,161,246,244,204,133,9,42,1,161,246,244,204,133,9,41,1,161,246,244,204,133,9,43,1,161,246,244,204,133,9,44,1,161,246,244,204,133,9,46,1,161,246,244,204,133,9,45,1,161,246,244,204,133,9,47,1,168,246,244,204,133,9,48,1,122,0,0,0,0,102,97,116,75,168,246,244,204,133,9,50,1,122,0,0,0,0,0,0,0,7,168,246,244,204,133,9,49,1,119,135,1,123,34,111,112,116,105,111,110,115,34,58,91,123,34,105,100,34,58,34,102,104,112,70,34,44,34,110,97,109,101,34,58,34,51,34,44,34,99,111,108,111,114,34,58,34,80,117,114,112,108,101,34,125,44,123,34,105,100,34,58,34,111,105,110,85,34,44,34,110,97,109,101,34,58,34,54,34,44,34,99,111,108,111,114,34,58,34,80,117,114,112,108,101,34,125,93,44,34,115,101,108,101,99,116,101,100,95,111,112,116,105,111,110,95,105,100,115,34,58,91,34,102,104,112,70,34,44,34,111,105,110,85,34,93,125,168,246,244,204,133,9,51,1,122,0,0,0,0,102,97,116,75,2,144,209,245,144,10,1,0,5,246,244,204,133,9,8,8,1,14,2,17,2,22,1,27,1,32,1,38,1,41,11],"3b5ef824-475c-4848-acff-418e259a3d53":[2,2,219,196,219,154,10,0,33,1,4,109,101,116,97,12,108,97,115,116,95,115,121,110,99,95,97,116,5,168,219,196,219,154,10,4,1,122,0,0,0,0,102,97,116,208,60,150,233,209,1,0,39,1,4,100,97,116,97,4,100,97,116,97,1,39,1,4,100,97,116,97,4,109,101,116,97,1,39,1,4,100,97,116,97,7,99,111,109,109,101,110,116,0,40,0,150,233,209,1,0,2,105,100,1,119,36,51,98,53,101,102,56,50,52,45,52,55,53,99,45,52,56,52,56,45,97,99,102,102,45,52,49,56,101,50,53,57,97,51,100,53,51,40,0,150,233,209,1,0,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,56,55,98,99,48,48,54,101,45,99,49,101,98,45,52,55,102,100,45,57,97,99,54,45,101,51,57,98,49,55,57,53,54,51,54,57,40,0,150,233,209,1,0,6,104,101,105,103,104,116,1,122,0,0,0,0,0,0,0,60,40,0,150,233,209,1,0,10,118,105,115,105,98,105,108,105,116,121,1,120,40,0,150,233,209,1,0,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,97,115,237,33,0,150,233,209,1,0,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,39,0,150,233,209,1,0,5,99,101,108,108,115,1,39,0,150,233,209,1,9,6,111,121,80,121,97,117,1,33,0,150,233,209,1,10,10,102,105,101,108,100,95,116,121,112,101,1,33,0,150,233,209,1,10,4,100,97,116,97,1,39,0,150,233,209,1,9,6,53,69,90,81,65,87,1,40,0,150,233,209,1,13,4,100,97,116,97,1,119,4,71,102,87,50,40,0,150,233,209,1,13,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,3,39,0,150,233,209,1,9,6,77,67,57,90,97,69,1,33,0,150,233,209,1,16,10,102,105,101,108,100,95,116,121,112,101,1,33,0,150,233,209,1,16,4,100,97,116,97,1,39,0,150,233,209,1,9,6,108,73,72,113,101,57,1,40,0,150,233,209,1,19,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,5,40,0,150,233,209,1,19,4,100,97,116,97,1,119,3,89,101,115,161,150,233,209,1,8,1,40,0,150,233,209,1,10,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,97,116,15,161,150,233,209,1,12,1,161,150,233,209,1,11,1,33,0,150,233,209,1,10,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,161,150,233,209,1,22,1,40,0,150,233,209,1,16,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,97,116,31,168,150,233,209,1,17,1,122,0,0,0,0,0,0,0,0,168,150,233,209,1,18,1,119,6,49,50,51,53,54,55,40,0,150,233,209,1,16,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,97,116,31,161,150,233,209,1,27,1,39,0,150,233,209,1,9,6,102,116,73,53,52,121,1,40,0,150,233,209,1,33,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,97,116,49,40,0,150,233,209,1,33,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,4,40,0,150,233,209,1,33,4,100,97,116,97,1,119,4,104,57,106,100,40,0,150,233,209,1,33,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,97,116,49,161,150,233,209,1,32,1,39,0,150,233,209,1,9,6,84,79,87,83,70,104,1,40,0,150,233,209,1,39,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,97,116,65,33,0,150,233,209,1,39,10,102,105,101,108,100,95,116,121,112,101,1,33,0,150,233,209,1,39,4,100,97,116,97,1,33,0,150,233,209,1,39,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,161,150,233,209,1,38,1,161,150,233,209,1,42,1,161,150,233,209,1,41,1,161,150,233,209,1,43,1,161,150,233,209,1,44,1,161,150,233,209,1,46,1,161,150,233,209,1,45,1,161,150,233,209,1,47,1,161,150,233,209,1,48,1,168,150,233,209,1,49,1,122,0,0,0,0,0,0,0,7,168,150,233,209,1,50,1,119,135,1,123,34,111,112,116,105,111,110,115,34,58,91,123,34,105,100,34,58,34,45,106,68,117,34,44,34,110,97,109,101,34,58,34,50,34,44,34,99,111,108,111,114,34,58,34,80,117,114,112,108,101,34,125,44,123,34,105,100,34,58,34,76,57,87,81,34,44,34,110,97,109,101,34,58,34,51,34,44,34,99,111,108,111,114,34,58,34,80,117,114,112,108,101,34,125,93,44,34,115,101,108,101,99,116,101,100,95,111,112,116,105,111,110,95,105,100,115,34,58,91,34,45,106,68,117,34,44,34,76,57,87,81,34,93,125,168,150,233,209,1,51,1,122,0,0,0,0,102,97,116,68,168,150,233,209,1,52,1,122,0,0,0,0,102,97,116,93,168,150,233,209,1,25,1,122,0,0,0,0,0,0,0,1,168,150,233,209,1,24,1,119,3,54,48,55,168,150,233,209,1,26,1,122,0,0,0,0,102,97,116,93,2,150,233,209,1,8,8,1,11,2,17,2,22,1,24,4,32,1,38,1,41,12,219,196,219,154,10,1,0,5],"24249689-cad4-4e53-8c5e-f9eaec9bf558":[2,10,242,202,217,154,13,0,39,1,4,100,97,116,97,4,100,97,116,97,1,39,1,4,100,97,116,97,4,109,101,116,97,1,39,1,4,100,97,116,97,7,99,111,109,109,101,110,116,0,40,0,242,202,217,154,13,0,2,105,100,1,119,36,50,52,50,52,57,54,56,57,45,99,97,100,52,45,52,101,53,51,45,56,99,53,101,45,102,57,101,97,101,99,57,98,102,53,53,56,40,0,242,202,217,154,13,0,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,56,55,98,99,48,48,54,101,45,99,49,101,98,45,52,55,102,100,45,57,97,99,54,45,101,51,57,98,49,55,57,53,54,51,54,57,40,0,242,202,217,154,13,0,6,104,101,105,103,104,116,1,122,0,0,0,0,0,0,0,60,40,0,242,202,217,154,13,0,10,118,105,115,105,98,105,108,105,116,121,1,120,40,0,242,202,217,154,13,0,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,97,116,208,40,0,242,202,217,154,13,0,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,97,116,208,39,0,242,202,217,154,13,0,5,99,101,108,108,115,1,2,243,240,149,193,10,0,33,1,4,109,101,116,97,12,108,97,115,116,95,115,121,110,99,95,97,116,5,168,243,240,149,193,10,4,1,122,0,0,0,0,102,97,116,218,1,243,240,149,193,10,1,0,5],"1111b146-4c6c-4fc6-95e1-70c246147f8f":[2,10,165,220,194,235,14,0,39,1,4,100,97,116,97,4,100,97,116,97,1,39,1,4,100,97,116,97,4,109,101,116,97,1,39,1,4,100,97,116,97,7,99,111,109,109,101,110,116,0,40,0,165,220,194,235,14,0,2,105,100,1,119,36,49,49,49,49,98,49,52,54,45,52,99,54,99,45,52,102,99,54,45,57,53,101,49,45,55,48,99,50,52,54,49,52,55,102,56,102,40,0,165,220,194,235,14,0,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,56,55,98,99,48,48,54,101,45,99,49,101,98,45,52,55,102,100,45,57,97,99,54,45,101,51,57,98,49,55,57,53,54,51,54,57,40,0,165,220,194,235,14,0,6,104,101,105,103,104,116,1,122,0,0,0,0,0,0,0,60,40,0,165,220,194,235,14,0,10,118,105,115,105,98,105,108,105,116,121,1,120,40,0,165,220,194,235,14,0,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,97,115,63,40,0,165,220,194,235,14,0,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,97,115,63,39,0,165,220,194,235,14,0,5,99,101,108,108,115,1,2,250,172,218,249,7,0,33,1,4,109,101,116,97,12,108,97,115,116,95,115,121,110,99,95,97,116,5,168,250,172,218,249,7,4,1,122,0,0,0,0,102,97,116,211,1,250,172,218,249,7,1,0,5],"3ec7b76c-68c9-4279-9b33-2365321eaf41":[2,10,243,212,210,152,3,0,39,1,4,100,97,116,97,4,100,97,116,97,1,39,1,4,100,97,116,97,4,109,101,116,97,1,39,1,4,100,97,116,97,7,99,111,109,109,101,110,116,0,40,0,243,212,210,152,3,0,2,105,100,1,119,36,51,101,99,55,98,55,54,99,45,54,56,99,57,45,52,50,55,57,45,57,98,51,51,45,50,51,54,53,51,50,49,101,97,102,52,49,40,0,243,212,210,152,3,0,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,56,55,98,99,48,48,54,101,45,99,49,101,98,45,52,55,102,100,45,57,97,99,54,45,101,51,57,98,49,55,57,53,54,51,54,57,40,0,243,212,210,152,3,0,6,104,101,105,103,104,116,1,122,0,0,0,0,0,0,0,60,40,0,243,212,210,152,3,0,10,118,105,115,105,98,105,108,105,116,121,1,120,40,0,243,212,210,152,3,0,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,97,115,63,40,0,243,212,210,152,3,0,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,97,115,63,39,0,243,212,210,152,3,0,5,99,101,108,108,115,1,2,160,128,198,202,2,0,33,1,4,109,101,116,97,12,108,97,115,116,95,115,121,110,99,95,97,116,5,168,160,128,198,202,2,4,1,122,0,0,0,0,102,97,116,210,1,160,128,198,202,2,1,0,5]} \ No newline at end of file diff --git a/frontend/appflowy_web_app/cypress/fixtures/database/rows/ad7dc45b-44b5-498f-bfa2-0f43bf05cc0d.json b/frontend/appflowy_web_app/cypress/fixtures/database/rows/ad7dc45b-44b5-498f-bfa2-0f43bf05cc0d.json deleted file mode 100644 index 10fc50a811..0000000000 --- a/frontend/appflowy_web_app/cypress/fixtures/database/rows/ad7dc45b-44b5-498f-bfa2-0f43bf05cc0d.json +++ /dev/null @@ -1 +0,0 @@ -{"208d248f-5c08-4be5-a022-e0a97c2d705e":[16,1,162,212,253,234,14,0,161,166,231,212,218,8,3,39,1,245,198,128,205,14,0,161,233,140,128,164,8,5,2,1,165,222,139,132,12,0,161,128,181,233,166,8,1,7,1,179,227,145,238,11,0,33,1,4,109,101,116,97,12,108,97,115,116,95,115,121,110,99,95,97,116,10,2,213,228,161,169,9,0,161,233,140,128,164,8,5,1,161,245,198,128,205,14,1,9,2,185,222,141,169,9,0,161,140,225,231,182,6,2,4,168,185,222,141,169,9,3,1,122,0,0,0,0,102,88,52,85,1,138,182,251,229,8,0,161,162,212,253,234,14,38,7,1,166,231,212,218,8,0,161,165,222,139,132,12,6,4,1,128,181,233,166,8,0,161,179,227,145,238,11,9,2,1,233,140,128,164,8,0,161,221,230,177,144,4,1,6,1,239,245,240,149,8,0,161,157,238,145,201,3,1,2,1,140,225,231,182,6,0,161,239,245,240,149,8,1,3,1,246,148,237,174,6,0,161,138,182,251,229,8,6,5,16,221,174,135,220,5,0,39,1,4,100,97,116,97,4,100,97,116,97,1,39,1,4,100,97,116,97,4,109,101,116,97,1,39,1,4,100,97,116,97,7,99,111,109,109,101,110,116,0,40,0,221,174,135,220,5,0,2,105,100,1,119,36,50,48,56,100,50,52,56,102,45,53,99,48,56,45,52,98,101,53,45,97,48,50,50,45,101,48,97,57,55,99,50,100,55,48,53,101,40,0,221,174,135,220,5,0,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,97,100,55,100,99,52,53,98,45,52,52,98,53,45,52,57,56,102,45,98,102,97,50,45,48,102,52,51,98,102,48,53,99,99,48,100,40,0,221,174,135,220,5,0,6,104,101,105,103,104,116,1,122,0,0,0,0,0,0,0,60,40,0,221,174,135,220,5,0,10,118,105,115,105,98,105,108,105,116,121,1,120,40,0,221,174,135,220,5,0,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,76,39,162,40,0,221,174,135,220,5,0,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,76,39,162,39,0,221,174,135,220,5,0,5,99,101,108,108,115,1,39,0,221,174,135,220,5,9,6,121,52,52,50,48,119,1,40,0,221,174,135,220,5,10,4,100,97,116,97,1,119,4,117,76,117,51,40,0,221,174,135,220,5,10,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,3,39,0,221,174,135,220,5,9,6,51,111,45,90,115,109,1,40,0,221,174,135,220,5,13,4,100,97,116,97,1,119,6,67,97,114,100,32,49,40,0,221,174,135,220,5,13,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,0,1,221,230,177,144,4,0,161,246,148,237,174,6,4,2,1,157,238,145,201,3,0,161,213,228,161,169,9,9,2,15,128,181,233,166,8,1,0,2,162,212,253,234,14,1,0,39,165,222,139,132,12,1,0,7,166,231,212,218,8,1,0,4,233,140,128,164,8,1,0,6,138,182,251,229,8,1,0,7,140,225,231,182,6,1,0,3,239,245,240,149,8,1,0,2,179,227,145,238,11,1,0,10,245,198,128,205,14,1,0,2,246,148,237,174,6,1,0,5,213,228,161,169,9,1,0,10,185,222,141,169,9,1,0,4,221,230,177,144,4,1,0,2,157,238,145,201,3,1,0,2]} \ No newline at end of file diff --git a/frontend/appflowy_web_app/cypress/fixtures/database/rows/ce267d12-3b61-4ebb-bb03-d65272f5f817.json b/frontend/appflowy_web_app/cypress/fixtures/database/rows/ce267d12-3b61-4ebb-bb03-d65272f5f817.json deleted file mode 100644 index 9820d03b24..0000000000 --- a/frontend/appflowy_web_app/cypress/fixtures/database/rows/ce267d12-3b61-4ebb-bb03-d65272f5f817.json +++ /dev/null @@ -1 +0,0 @@ -{"a00ecf78-a823-43f1-b542-ed071394a717":[14,1,235,137,137,244,15,0,161,252,139,206,213,10,1,2,1,133,172,162,242,15,0,161,137,192,210,179,15,1,2,2,186,176,205,207,15,0,161,196,230,218,150,10,3,2,168,186,176,205,207,15,1,1,122,0,0,0,0,102,88,31,89,1,137,192,210,179,15,0,161,235,137,137,244,15,1,2,1,245,193,213,231,13,0,161,153,225,207,224,1,1,2,1,246,177,194,222,13,0,161,133,172,162,242,15,1,6,1,205,151,244,151,13,0,161,246,177,194,222,13,5,12,23,239,227,232,242,10,0,39,1,4,100,97,116,97,4,100,97,116,97,1,39,1,4,100,97,116,97,4,109,101,116,97,1,39,1,4,100,97,116,97,7,99,111,109,109,101,110,116,0,40,0,239,227,232,242,10,0,2,105,100,1,119,36,97,48,48,101,99,102,55,56,45,97,56,50,51,45,52,51,102,49,45,98,53,52,50,45,101,100,48,55,49,51,57,52,97,55,49,55,40,0,239,227,232,242,10,0,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,99,101,50,54,55,100,49,50,45,51,98,54,49,45,52,101,98,98,45,98,98,48,51,45,100,54,53,50,55,50,102,53,102,56,49,55,40,0,239,227,232,242,10,0,6,104,101,105,103,104,116,1,122,0,0,0,0,0,0,0,60,40,0,239,227,232,242,10,0,10,118,105,115,105,98,105,108,105,116,121,1,120,40,0,239,227,232,242,10,0,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,77,121,252,33,0,239,227,232,242,10,0,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,39,0,239,227,232,242,10,0,5,99,101,108,108,115,1,39,0,239,227,232,242,10,9,6,55,85,107,117,54,82,1,40,0,239,227,232,242,10,10,4,100,97,116,97,1,119,10,49,55,49,54,51,48,55,50,48,48,40,0,239,227,232,242,10,10,13,101,110,100,95,116,105,109,101,115,116,97,109,112,1,119,0,40,0,239,227,232,242,10,10,12,105,110,99,108,117,100,101,95,116,105,109,101,1,121,40,0,239,227,232,242,10,10,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,2,40,0,239,227,232,242,10,10,8,105,115,95,114,97,110,103,101,1,121,40,0,239,227,232,242,10,10,11,114,101,109,105,110,100,101,114,95,105,100,1,119,0,168,239,227,232,242,10,8,1,122,0,0,0,0,102,77,124,84,39,0,239,227,232,242,10,9,6,72,95,74,113,85,76,1,40,0,239,227,232,242,10,18,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,77,124,84,40,0,239,227,232,242,10,18,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,0,40,0,239,227,232,242,10,18,4,100,97,116,97,1,119,5,49,49,49,49,49,40,0,239,227,232,242,10,18,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,77,124,84,1,252,139,206,213,10,0,161,155,206,144,191,3,37,2,1,222,138,222,196,10,0,161,246,177,194,222,13,5,2,1,196,230,218,150,10,0,161,245,193,213,231,13,1,4,1,239,171,159,202,8,0,33,1,4,109,101,116,97,12,108,97,115,116,95,115,121,110,99,95,97,116,15,1,155,206,144,191,3,0,161,239,171,159,202,8,14,38,1,153,225,207,224,1,0,161,205,151,244,151,13,11,2,14,235,137,137,244,15,1,0,2,205,151,244,151,13,1,0,12,239,171,159,202,8,1,0,15,196,230,218,150,10,1,0,4,245,193,213,231,13,1,0,2,246,177,194,222,13,1,0,6,133,172,162,242,15,1,0,2,137,192,210,179,15,1,0,2,186,176,205,207,15,1,0,2,153,225,207,224,1,1,0,2,155,206,144,191,3,1,0,38,252,139,206,213,10,1,0,2,222,138,222,196,10,1,0,2,239,227,232,242,10,1,8,1],"a73674ae-3301-45a3-b801-3f12e6fcb566":[16,1,216,136,201,234,15,0,33,1,4,109,101,116,97,12,108,97,115,116,95,115,121,110,99,95,97,116,14,8,151,154,187,181,15,0,161,167,192,205,202,8,78,1,161,167,192,205,202,8,54,1,161,167,192,205,202,8,53,1,161,167,192,205,202,8,55,1,168,151,154,187,181,15,0,1,122,0,0,0,0,102,80,8,134,168,151,154,187,181,15,2,1,119,2,104,105,168,151,154,187,181,15,1,1,122,0,0,0,0,0,0,0,0,168,151,154,187,181,15,3,1,122,0,0,0,0,102,80,8,134,1,225,161,205,165,15,0,161,236,244,246,235,2,1,4,1,232,241,163,254,12,0,161,216,136,201,234,15,13,28,1,243,242,150,150,12,0,161,198,181,227,192,1,1,2,1,170,212,149,201,11,0,161,232,241,163,254,12,27,39,1,203,244,164,187,11,0,161,189,165,195,186,4,11,6,2,225,242,132,222,9,0,161,225,161,205,165,15,3,2,168,225,242,132,222,9,1,1,122,0,0,0,0,102,88,31,91,82,167,192,205,202,8,0,39,1,4,100,97,116,97,4,100,97,116,97,1,39,1,4,100,97,116,97,4,109,101,116,97,1,39,1,4,100,97,116,97,7,99,111,109,109,101,110,116,0,40,0,167,192,205,202,8,0,2,105,100,1,119,36,97,55,51,54,55,52,97,101,45,51,51,48,49,45,52,53,97,51,45,98,56,48,49,45,51,102,49,50,101,54,102,99,98,53,54,54,40,0,167,192,205,202,8,0,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,99,101,50,54,55,100,49,50,45,51,98,54,49,45,52,101,98,98,45,98,98,48,51,45,100,54,53,50,55,50,102,53,102,56,49,55,40,0,167,192,205,202,8,0,6,104,101,105,103,104,116,1,122,0,0,0,0,0,0,0,60,40,0,167,192,205,202,8,0,10,118,105,115,105,98,105,108,105,116,121,1,120,40,0,167,192,205,202,8,0,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,77,88,135,33,0,167,192,205,202,8,0,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,39,0,167,192,205,202,8,0,5,99,101,108,108,115,1,39,0,167,192,205,202,8,9,6,55,85,107,117,54,82,1,33,0,167,192,205,202,8,10,10,102,105,101,108,100,95,116,121,112,101,1,33,0,167,192,205,202,8,10,12,105,110,99,108,117,100,101,95,116,105,109,101,1,33,0,167,192,205,202,8,10,8,105,115,95,114,97,110,103,101,1,33,0,167,192,205,202,8,10,11,114,101,109,105,110,100,101,114,95,105,100,1,33,0,167,192,205,202,8,10,13,101,110,100,95,116,105,109,101,115,116,97,109,112,1,33,0,167,192,205,202,8,10,4,100,97,116,97,1,161,167,192,205,202,8,8,1,39,0,167,192,205,202,8,9,6,95,82,45,112,104,105,1,40,0,167,192,205,202,8,18,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,77,88,139,40,0,167,192,205,202,8,18,4,100,97,116,97,1,119,4,73,73,66,100,40,0,167,192,205,202,8,18,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,4,40,0,167,192,205,202,8,18,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,77,88,139,161,167,192,205,202,8,17,1,40,0,167,192,205,202,8,10,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,77,88,142,168,167,192,205,202,8,13,1,121,168,167,192,205,202,8,11,1,122,0,0,0,0,0,0,0,2,168,167,192,205,202,8,15,1,119,0,168,167,192,205,202,8,14,1,119,0,168,167,192,205,202,8,16,1,119,10,49,55,49,54,54,48,52,49,55,52,168,167,192,205,202,8,12,1,121,40,0,167,192,205,202,8,10,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,77,88,142,161,167,192,205,202,8,23,1,39,0,167,192,205,202,8,9,6,71,115,66,65,97,76,1,40,0,167,192,205,202,8,33,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,77,88,150,33,0,167,192,205,202,8,33,8,105,115,95,114,97,110,103,101,1,33,0,167,192,205,202,8,33,11,114,101,109,105,110,100,101,114,95,105,100,1,33,0,167,192,205,202,8,33,10,102,105,101,108,100,95,116,121,112,101,1,33,0,167,192,205,202,8,33,4,100,97,116,97,1,33,0,167,192,205,202,8,33,13,101,110,100,95,116,105,109,101,115,116,97,109,112,1,33,0,167,192,205,202,8,33,12,105,110,99,108,117,100,101,95,116,105,109,101,1,33,0,167,192,205,202,8,33,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,161,167,192,205,202,8,32,1,168,167,192,205,202,8,39,1,119,0,168,167,192,205,202,8,38,1,119,10,49,55,49,55,49,50,50,53,56,51,168,167,192,205,202,8,37,1,122,0,0,0,0,0,0,0,2,168,167,192,205,202,8,40,1,121,168,167,192,205,202,8,35,1,121,168,167,192,205,202,8,36,1,119,0,168,167,192,205,202,8,41,1,122,0,0,0,0,102,77,88,151,161,167,192,205,202,8,42,1,39,0,167,192,205,202,8,9,6,72,95,74,113,85,76,1,40,0,167,192,205,202,8,51,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,77,165,75,33,0,167,192,205,202,8,51,4,100,97,116,97,1,33,0,167,192,205,202,8,51,10,102,105,101,108,100,95,116,121,112,101,1,33,0,167,192,205,202,8,51,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,161,167,192,205,202,8,50,1,39,0,167,192,205,202,8,9,6,99,78,53,98,120,74,1,40,0,167,192,205,202,8,57,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,77,165,89,40,0,167,192,205,202,8,57,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,6,40,0,167,192,205,202,8,57,4,100,97,116,97,1,119,3,49,50,51,40,0,167,192,205,202,8,57,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,77,165,89,161,167,192,205,202,8,56,1,39,0,167,192,205,202,8,9,6,71,79,80,107,116,118,1,40,0,167,192,205,202,8,63,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,77,165,117,40,0,167,192,205,202,8,63,4,100,97,116,97,1,119,4,89,101,75,100,40,0,167,192,205,202,8,63,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,3,40,0,167,192,205,202,8,63,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,77,165,117,161,167,192,205,202,8,62,1,39,0,167,192,205,202,8,9,6,112,70,120,57,67,45,1,40,0,167,192,205,202,8,69,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,77,165,142,33,0,167,192,205,202,8,69,10,102,105,101,108,100,95,116,121,112,101,1,33,0,167,192,205,202,8,69,4,100,97,116,97,1,33,0,167,192,205,202,8,69,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,161,167,192,205,202,8,68,1,161,167,192,205,202,8,71,1,161,167,192,205,202,8,72,1,161,167,192,205,202,8,73,1,161,167,192,205,202,8,74,1,168,167,192,205,202,8,75,1,122,0,0,0,0,0,0,0,7,168,167,192,205,202,8,76,1,119,134,1,123,34,111,112,116,105,111,110,115,34,58,91,123,34,105,100,34,58,34,99,72,80,113,34,44,34,110,97,109,101,34,58,34,51,51,51,51,34,44,34,99,111,108,111,114,34,58,34,80,117,114,112,108,101,34,125,44,123,34,105,100,34,58,34,74,106,52,74,34,44,34,110,97,109,101,34,58,34,51,51,51,51,34,44,34,99,111,108,111,114,34,58,34,80,117,114,112,108,101,34,125,93,44,34,115,101,108,101,99,116,101,100,95,111,112,116,105,111,110,95,105,100,115,34,58,91,34,99,72,80,113,34,93,125,168,167,192,205,202,8,77,1,122,0,0,0,0,102,77,165,145,1,170,185,193,131,8,0,161,210,149,158,230,4,5,2,1,194,218,151,236,5,0,161,170,212,149,201,11,38,2,1,210,149,158,230,4,0,161,143,148,251,251,1,1,6,2,189,165,195,186,4,0,161,210,149,158,230,4,5,1,161,170,185,193,131,8,1,11,1,236,244,246,235,2,0,161,203,244,164,187,11,5,2,1,143,148,251,251,1,0,161,243,242,150,150,12,1,2,1,198,181,227,192,1,0,161,194,218,151,236,5,1,2,16,225,161,205,165,15,1,0,4,194,218,151,236,5,1,0,2,225,242,132,222,9,1,0,2,198,181,227,192,1,1,0,2,167,192,205,202,8,10,8,1,11,7,23,1,32,1,35,8,50,1,53,4,62,1,68,1,71,8,232,241,163,254,12,1,0,28,170,212,149,201,11,1,0,39,170,185,193,131,8,1,0,2,236,244,246,235,2,1,0,2,203,244,164,187,11,1,0,6,143,148,251,251,1,1,0,2,210,149,158,230,4,1,0,6,243,242,150,150,12,1,0,2,151,154,187,181,15,1,0,4,216,136,201,234,15,1,0,14,189,165,195,186,4,1,0,12],"51cf0906-ad46-4dae-a3b9-2e003f8368c1":[15,1,196,176,146,143,13,0,161,211,131,137,205,5,5,12,1,175,214,229,215,11,0,161,164,251,162,159,11,1,4,1,167,131,238,183,11,0,161,218,233,217,251,6,1,2,1,164,251,162,159,11,0,161,135,135,213,129,7,1,2,1,252,183,246,136,10,0,161,211,131,137,205,5,5,2,1,184,218,170,237,8,0,161,226,213,154,133,6,7,16,1,253,213,204,144,8,0,161,254,207,234,185,3,1,2,1,135,135,213,129,7,0,161,196,176,146,143,13,11,2,1,218,233,217,251,6,0,161,213,133,230,230,2,38,2,1,226,213,154,133,6,0,33,1,4,109,101,116,97,12,108,97,115,116,95,115,121,110,99,95,97,116,8,1,211,131,137,205,5,0,161,253,213,204,144,8,1,6,2,205,251,166,251,4,0,161,175,214,229,215,11,3,2,168,205,251,166,251,4,1,1,122,0,0,0,0,102,88,31,89,30,239,237,241,245,4,0,39,1,4,100,97,116,97,4,100,97,116,97,1,39,1,4,100,97,116,97,4,109,101,116,97,1,39,1,4,100,97,116,97,7,99,111,109,109,101,110,116,0,40,0,239,237,241,245,4,0,2,105,100,1,119,36,53,49,99,102,48,57,48,54,45,97,100,52,54,45,52,100,97,101,45,97,51,98,57,45,50,101,48,48,51,102,56,51,54,56,99,49,40,0,239,237,241,245,4,0,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,99,101,50,54,55,100,49,50,45,51,98,54,49,45,52,101,98,98,45,98,98,48,51,45,100,54,53,50,55,50,102,53,102,56,49,55,40,0,239,237,241,245,4,0,6,104,101,105,103,104,116,1,122,0,0,0,0,0,0,0,60,40,0,239,237,241,245,4,0,10,118,105,115,105,98,105,108,105,116,121,1,120,40,0,239,237,241,245,4,0,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,77,95,173,33,0,239,237,241,245,4,0,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,39,0,239,237,241,245,4,0,5,99,101,108,108,115,1,39,0,239,237,241,245,4,9,6,55,85,107,117,54,82,1,40,0,239,237,241,245,4,10,12,105,110,99,108,117,100,101,95,116,105,109,101,1,121,40,0,239,237,241,245,4,10,11,114,101,109,105,110,100,101,114,95,105,100,1,119,0,40,0,239,237,241,245,4,10,4,100,97,116,97,1,119,10,49,55,49,54,51,48,55,50,48,48,40,0,239,237,241,245,4,10,8,105,115,95,114,97,110,103,101,1,121,40,0,239,237,241,245,4,10,13,101,110,100,95,116,105,109,101,115,116,97,109,112,1,119,0,40,0,239,237,241,245,4,10,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,2,161,239,237,241,245,4,8,1,39,0,239,237,241,245,4,9,6,72,95,74,113,85,76,1,40,0,239,237,241,245,4,18,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,77,165,79,40,0,239,237,241,245,4,18,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,0,40,0,239,237,241,245,4,18,4,100,97,116,97,1,119,2,48,48,40,0,239,237,241,245,4,18,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,77,165,79,168,239,237,241,245,4,17,1,122,0,0,0,0,102,77,165,181,39,0,239,237,241,245,4,9,6,75,71,50,113,74,65,1,40,0,239,237,241,245,4,24,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,77,165,181,40,0,239,237,241,245,4,24,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,10,39,0,239,237,241,245,4,24,4,100,97,116,97,0,8,0,239,237,241,245,4,27,1,119,36,100,51,50,101,52,56,97,52,45,99,102,48,100,45,52,56,97,56,45,57,53,57,57,45,53,51,51,57,97,56,49,53,56,99,53,48,40,0,239,237,241,245,4,24,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,77,165,181,1,254,207,234,185,3,0,161,167,131,238,183,11,1,2,1,213,133,230,230,2,0,161,184,218,170,237,8,15,39,15,226,213,154,133,6,1,0,8,196,176,146,143,13,1,0,12,164,251,162,159,11,1,0,2,135,135,213,129,7,1,0,2,167,131,238,183,11,1,0,2,205,251,166,251,4,1,0,2,175,214,229,215,11,1,0,4,239,237,241,245,4,2,8,1,17,1,211,131,137,205,5,1,0,6,213,133,230,230,2,1,0,39,184,218,170,237,8,1,0,16,218,233,217,251,6,1,0,2,252,183,246,136,10,1,0,2,253,213,204,144,8,1,0,2,254,207,234,185,3,1,0,2],"92a2137e-b00b-4388-851f-a0efc3de7ca3":[13,1,238,246,169,231,15,0,161,163,149,186,140,1,3,2,1,148,143,229,148,15,0,161,170,241,252,142,10,38,2,1,142,159,154,239,14,0,161,199,176,167,174,6,5,12,1,237,148,148,223,13,0,161,222,150,248,170,3,2,4,1,195,215,232,135,13,0,161,199,176,167,174,6,5,2,1,170,241,252,142,10,0,161,132,169,228,37,13,39,26,227,145,252,193,9,0,39,1,4,100,97,116,97,4,100,97,116,97,1,39,1,4,100,97,116,97,4,109,101,116,97,1,39,1,4,100,97,116,97,7,99,111,109,109,101,110,116,0,40,0,227,145,252,193,9,0,2,105,100,1,119,36,57,50,97,50,49,51,55,101,45,98,48,48,98,45,52,51,56,56,45,56,53,49,102,45,97,48,101,102,99,51,100,101,55,99,97,51,40,0,227,145,252,193,9,0,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,99,101,50,54,55,100,49,50,45,51,98,54,49,45,52,101,98,98,45,98,98,48,51,45,100,54,53,50,55,50,102,53,102,56,49,55,40,0,227,145,252,193,9,0,6,104,101,105,103,104,116,1,122,0,0,0,0,0,0,0,60,40,0,227,145,252,193,9,0,10,118,105,115,105,98,105,108,105,116,121,1,120,40,0,227,145,252,193,9,0,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,77,187,135,33,0,227,145,252,193,9,0,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,39,0,227,145,252,193,9,0,5,99,101,108,108,115,1,161,227,145,252,193,9,8,1,39,0,227,145,252,193,9,9,6,72,95,74,113,85,76,1,40,0,227,145,252,193,9,11,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,77,187,143,33,0,227,145,252,193,9,11,10,102,105,101,108,100,95,116,121,112,101,1,33,0,227,145,252,193,9,11,4,100,97,116,97,1,33,0,227,145,252,193,9,11,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,161,227,145,252,193,9,10,1,168,227,145,252,193,9,14,1,119,7,57,57,57,57,57,50,50,168,227,145,252,193,9,13,1,122,0,0,0,0,0,0,0,0,168,227,145,252,193,9,15,1,122,0,0,0,0,102,77,187,221,168,227,145,252,193,9,16,1,122,0,0,0,0,102,77,187,222,39,0,227,145,252,193,9,9,6,95,82,45,112,104,105,1,40,0,227,145,252,193,9,21,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,77,187,222,40,0,227,145,252,193,9,21,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,4,40,0,227,145,252,193,9,21,4,100,97,116,97,1,119,4,73,73,66,100,40,0,227,145,252,193,9,21,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,77,187,222,1,199,176,167,174,6,0,161,238,246,169,231,15,1,6,1,166,146,188,210,5,0,161,142,159,154,239,14,11,2,1,222,150,248,170,3,0,161,166,146,188,210,5,1,3,2,149,229,205,200,1,0,161,237,148,148,223,13,3,2,168,149,229,205,200,1,1,1,122,0,0,0,0,102,88,31,89,1,163,149,186,140,1,0,161,148,143,229,148,15,1,4,1,132,169,228,37,0,33,1,4,109,101,116,97,12,108,97,115,116,95,115,121,110,99,95,97,116,14,13,238,246,169,231,15,1,0,2,195,215,232,135,13,1,0,2,163,149,186,140,1,1,0,4,132,169,228,37,1,0,14,148,143,229,148,15,1,0,2,199,176,167,174,6,1,0,6,166,146,188,210,5,1,0,2,227,145,252,193,9,3,8,1,10,1,13,4,170,241,252,142,10,1,0,39,149,229,205,200,1,1,0,2,237,148,148,223,13,1,0,4,222,150,248,170,3,1,0,3,142,159,154,239,14,1,0,12],"2150cff6-ff80-4334-8c8a-94e82a64379a":[15,35,184,224,238,246,15,0,39,1,4,100,97,116,97,4,100,97,116,97,1,39,1,4,100,97,116,97,4,109,101,116,97,1,39,1,4,100,97,116,97,7,99,111,109,109,101,110,116,0,40,0,184,224,238,246,15,0,2,105,100,1,119,36,50,49,53,48,99,102,102,54,45,102,102,56,48,45,52,51,51,52,45,56,99,56,97,45,57,52,101,56,50,97,54,52,51,55,57,97,40,0,184,224,238,246,15,0,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,99,101,50,54,55,100,49,50,45,51,98,54,49,45,52,101,98,98,45,98,98,48,51,45,100,54,53,50,55,50,102,53,102,56,49,55,40,0,184,224,238,246,15,0,6,104,101,105,103,104,116,1,122,0,0,0,0,0,0,0,60,40,0,184,224,238,246,15,0,10,118,105,115,105,98,105,108,105,116,121,1,120,40,0,184,224,238,246,15,0,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,77,95,172,33,0,184,224,238,246,15,0,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,39,0,184,224,238,246,15,0,5,99,101,108,108,115,1,39,0,184,224,238,246,15,9,6,55,85,107,117,54,82,1,40,0,184,224,238,246,15,10,4,100,97,116,97,1,119,10,49,55,49,54,51,48,55,50,48,48,40,0,184,224,238,246,15,10,11,114,101,109,105,110,100,101,114,95,105,100,1,119,0,40,0,184,224,238,246,15,10,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,2,40,0,184,224,238,246,15,10,12,105,110,99,108,117,100,101,95,116,105,109,101,1,121,40,0,184,224,238,246,15,10,8,105,115,95,114,97,110,103,101,1,121,40,0,184,224,238,246,15,10,13,101,110,100,95,116,105,109,101,115,116,97,109,112,1,119,0,161,184,224,238,246,15,8,1,39,0,184,224,238,246,15,9,6,72,95,74,113,85,76,1,40,0,184,224,238,246,15,18,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,77,165,76,40,0,184,224,238,246,15,18,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,0,40,0,184,224,238,246,15,18,4,100,97,116,97,1,119,3,104,105,49,40,0,184,224,238,246,15,18,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,77,165,76,161,184,224,238,246,15,17,1,39,0,184,224,238,246,15,9,6,70,99,112,109,80,101,1,40,0,184,224,238,246,15,24,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,77,165,127,40,0,184,224,238,246,15,24,4,100,97,116,97,1,119,3,89,101,115,40,0,184,224,238,246,15,24,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,5,40,0,184,224,238,246,15,24,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,77,165,127,168,184,224,238,246,15,23,1,122,0,0,0,0,102,77,165,148,39,0,184,224,238,246,15,9,6,112,70,120,57,67,45,1,40,0,184,224,238,246,15,30,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,77,165,148,40,0,184,224,238,246,15,30,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,7,40,0,184,224,238,246,15,30,4,100,97,116,97,1,119,82,123,34,111,112,116,105,111,110,115,34,58,91,123,34,105,100,34,58,34,119,122,107,74,34,44,34,110,97,109,101,34,58,34,53,53,53,34,44,34,99,111,108,111,114,34,58,34,80,117,114,112,108,101,34,125,93,44,34,115,101,108,101,99,116,101,100,95,111,112,116,105,111,110,95,105,100,115,34,58,91,93,125,40,0,184,224,238,246,15,30,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,77,165,148,2,205,147,147,241,14,0,161,239,184,147,146,4,3,2,168,205,147,147,241,14,1,1,122,0,0,0,0,102,88,31,91,1,246,235,197,205,14,0,161,185,248,189,241,10,1,2,1,194,245,173,211,11,0,33,1,4,109,101,116,97,12,108,97,115,116,95,115,121,110,99,95,97,116,8,1,185,248,189,241,10,0,161,241,200,244,209,10,1,2,1,176,221,143,219,10,0,161,194,245,173,211,11,7,21,1,241,200,244,209,10,0,161,166,226,191,247,8,1,2,1,166,226,191,247,8,0,161,240,207,252,192,1,38,2,1,186,199,157,206,8,0,161,203,254,130,163,1,1,2,1,185,239,230,135,7,0,161,189,129,252,252,2,5,2,2,174,181,215,227,6,0,161,189,129,252,252,2,5,1,161,185,239,230,135,7,1,11,1,239,184,147,146,4,0,161,186,199,157,206,8,1,4,1,189,129,252,252,2,0,161,246,235,197,205,14,1,6,1,240,207,252,192,1,0,161,176,221,143,219,10,20,39,1,203,254,130,163,1,0,161,174,181,215,227,6,11,2,15,194,245,173,211,11,1,0,8,166,226,191,247,8,1,0,2,203,254,130,163,1,1,0,2,205,147,147,241,14,1,0,2,174,181,215,227,6,1,0,12,239,184,147,146,4,1,0,4,176,221,143,219,10,1,0,21,240,207,252,192,1,1,0,39,241,200,244,209,10,1,0,2,246,235,197,205,14,1,0,2,184,224,238,246,15,3,8,1,17,1,23,1,185,248,189,241,10,1,0,2,185,239,230,135,7,1,0,2,186,199,157,206,8,1,0,2,189,129,252,252,2,1,0,6],"7717079b-05b6-4a0a-8ee4-48739fbf3a52":[18,1,238,246,246,209,14,0,161,222,139,223,157,3,30,6,1,242,233,195,179,14,0,161,147,233,229,181,2,9,2,1,176,198,177,177,14,0,161,242,233,195,179,14,1,2,1,171,249,223,240,13,0,161,176,198,177,177,14,1,2,1,189,169,216,163,13,0,161,229,212,189,183,1,3,2,1,241,188,132,177,11,0,161,171,249,223,240,13,1,5,2,176,157,175,239,9,0,161,241,188,132,177,11,4,2,168,176,157,175,239,9,1,1,122,0,0,0,0,102,88,31,91,1,244,197,233,193,9,0,161,189,169,216,163,13,1,6,1,231,233,173,168,9,0,161,206,211,220,252,6,33,40,1,206,211,220,252,6,0,161,243,207,130,177,3,8,34,54,237,203,168,145,4,0,161,145,187,128,129,2,39,1,40,0,145,187,128,129,2,10,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,77,89,162,161,145,187,128,129,2,13,1,161,145,187,128,129,2,11,1,161,145,187,128,129,2,15,1,161,145,187,128,129,2,12,1,161,145,187,128,129,2,14,1,161,145,187,128,129,2,16,1,33,0,145,187,128,129,2,10,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,161,237,203,168,145,4,0,1,168,237,203,168,145,4,6,1,119,10,49,55,49,54,52,51,49,54,53,49,168,237,203,168,145,4,4,1,121,168,237,203,168,145,4,7,1,120,168,237,203,168,145,4,2,1,119,0,168,237,203,168,145,4,3,1,122,0,0,0,0,0,0,0,2,168,237,203,168,145,4,5,1,119,10,49,55,49,54,51,52,53,50,53,49,168,237,203,168,145,4,8,1,122,0,0,0,0,102,77,89,163,161,237,203,168,145,4,9,1,168,145,187,128,129,2,27,1,122,0,0,0,0,0,0,0,6,168,145,187,128,129,2,26,1,119,11,97,112,112,102,108,111,119,121,46,105,111,168,145,187,128,129,2,28,1,122,0,0,0,0,102,77,165,88,161,237,203,168,145,4,17,1,168,145,187,128,129,2,20,1,119,9,73,73,66,100,44,110,103,110,85,168,145,187,128,129,2,21,1,122,0,0,0,0,0,0,0,4,168,145,187,128,129,2,22,1,122,0,0,0,0,102,77,165,108,161,237,203,168,145,4,21,1,39,0,145,187,128,129,2,9,6,71,79,80,107,116,118,1,40,0,237,203,168,145,4,26,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,77,165,114,40,0,237,203,168,145,4,26,4,100,97,116,97,1,119,4,104,77,109,67,40,0,237,203,168,145,4,26,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,3,40,0,237,203,168,145,4,26,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,77,165,114,161,237,203,168,145,4,25,1,39,0,145,187,128,129,2,9,6,70,99,112,109,80,101,1,40,0,237,203,168,145,4,32,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,77,165,126,40,0,237,203,168,145,4,32,4,100,97,116,97,1,119,3,89,101,115,40,0,237,203,168,145,4,32,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,5,40,0,237,203,168,145,4,32,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,77,165,126,161,237,203,168,145,4,31,1,39,0,145,187,128,129,2,9,6,112,70,120,57,67,45,1,40,0,237,203,168,145,4,38,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,77,165,137,33,0,237,203,168,145,4,38,10,102,105,101,108,100,95,116,121,112,101,1,33,0,237,203,168,145,4,38,4,100,97,116,97,1,33,0,237,203,168,145,4,38,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,161,237,203,168,145,4,37,1,168,237,203,168,145,4,40,1,122,0,0,0,0,0,0,0,7,168,237,203,168,145,4,41,1,119,88,123,34,111,112,116,105,111,110,115,34,58,91,123,34,105,100,34,58,34,106,97,56,104,34,44,34,110,97,109,101,34,58,34,49,50,51,34,44,34,99,111,108,111,114,34,58,34,80,117,114,112,108,101,34,125,93,44,34,115,101,108,101,99,116,101,100,95,111,112,116,105,111,110,95,105,100,115,34,58,91,34,106,97,56,104,34,93,125,168,237,203,168,145,4,42,1,122,0,0,0,0,102,77,165,139,168,237,203,168,145,4,43,1,122,0,0,0,0,102,77,165,176,39,0,145,187,128,129,2,9,6,75,71,50,113,74,65,1,40,0,237,203,168,145,4,48,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,77,165,176,40,0,237,203,168,145,4,48,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,10,39,0,237,203,168,145,4,48,4,100,97,116,97,0,8,0,237,203,168,145,4,51,1,119,36,50,48,56,100,50,52,56,102,45,53,99,48,56,45,52,98,101,53,45,97,48,50,50,45,101,48,97,57,55,99,50,100,55,48,53,101,40,0,237,203,168,145,4,48,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,77,165,176,1,235,139,213,209,3,0,161,231,233,173,168,9,39,2,1,243,207,130,177,3,0,161,238,246,246,209,14,5,9,1,222,139,223,157,3,0,33,1,4,109,101,116,97,12,108,97,115,116,95,115,121,110,99,95,97,116,31,1,147,233,229,181,2,0,161,244,197,233,193,9,5,10,45,145,187,128,129,2,0,39,1,4,100,97,116,97,4,100,97,116,97,1,39,1,4,100,97,116,97,4,109,101,116,97,1,39,1,4,100,97,116,97,7,99,111,109,109,101,110,116,0,40,0,145,187,128,129,2,0,2,105,100,1,119,36,55,55,49,55,48,55,57,98,45,48,53,98,54,45,52,97,48,97,45,56,101,101,52,45,52,56,55,51,57,102,98,102,51,97,53,50,40,0,145,187,128,129,2,0,11,100,97,116,97,98,97,115,101,95,105,100,1,119,36,99,101,50,54,55,100,49,50,45,51,98,54,49,45,52,101,98,98,45,98,98,48,51,45,100,54,53,50,55,50,102,53,102,56,49,55,40,0,145,187,128,129,2,0,6,104,101,105,103,104,116,1,122,0,0,0,0,0,0,0,60,40,0,145,187,128,129,2,0,10,118,105,115,105,98,105,108,105,116,121,1,120,40,0,145,187,128,129,2,0,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,76,101,247,33,0,145,187,128,129,2,0,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,39,0,145,187,128,129,2,0,5,99,101,108,108,115,1,39,0,145,187,128,129,2,9,6,55,85,107,117,54,82,1,33,0,145,187,128,129,2,10,10,102,105,101,108,100,95,116,121,112,101,1,33,0,145,187,128,129,2,10,4,100,97,116,97,1,33,0,145,187,128,129,2,10,11,114,101,109,105,110,100,101,114,95,105,100,1,33,0,145,187,128,129,2,10,13,101,110,100,95,116,105,109,101,115,116,97,109,112,1,33,0,145,187,128,129,2,10,12,105,110,99,108,117,100,101,95,116,105,109,101,1,33,0,145,187,128,129,2,10,8,105,115,95,114,97,110,103,101,1,161,145,187,128,129,2,8,1,39,0,145,187,128,129,2,9,6,95,82,45,112,104,105,1,40,0,145,187,128,129,2,18,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,76,101,255,33,0,145,187,128,129,2,18,4,100,97,116,97,1,33,0,145,187,128,129,2,18,10,102,105,101,108,100,95,116,121,112,101,1,33,0,145,187,128,129,2,18,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,161,145,187,128,129,2,17,1,39,0,145,187,128,129,2,9,6,99,78,53,98,120,74,1,40,0,145,187,128,129,2,24,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,76,102,14,33,0,145,187,128,129,2,24,4,100,97,116,97,1,33,0,145,187,128,129,2,24,10,102,105,101,108,100,95,116,121,112,101,1,33,0,145,187,128,129,2,24,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,161,145,187,128,129,2,23,1,39,0,145,187,128,129,2,9,6,71,115,66,65,97,76,1,40,0,145,187,128,129,2,30,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,76,102,25,40,0,145,187,128,129,2,30,8,105,115,95,114,97,110,103,101,1,121,40,0,145,187,128,129,2,30,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,2,40,0,145,187,128,129,2,30,12,105,110,99,108,117,100,101,95,116,105,109,101,1,121,40,0,145,187,128,129,2,30,4,100,97,116,97,1,119,10,49,55,49,54,52,53,53,55,48,53,40,0,145,187,128,129,2,30,11,114,101,109,105,110,100,101,114,95,105,100,1,119,0,40,0,145,187,128,129,2,30,13,101,110,100,95,116,105,109,101,115,116,97,109,112,1,119,0,40,0,145,187,128,129,2,30,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,76,102,25,161,145,187,128,129,2,29,1,39,0,145,187,128,129,2,9,6,72,95,74,113,85,76,1,40,0,145,187,128,129,2,40,10,99,114,101,97,116,101,100,95,97,116,1,122,0,0,0,0,102,76,102,32,40,0,145,187,128,129,2,40,10,102,105,101,108,100,95,116,121,112,101,1,122,0,0,0,0,0,0,0,0,40,0,145,187,128,129,2,40,4,100,97,116,97,1,119,5,119,111,114,108,100,40,0,145,187,128,129,2,40,13,108,97,115,116,95,109,111,100,105,102,105,101,100,1,122,0,0,0,0,102,76,102,32,1,229,212,189,183,1,0,161,235,139,213,209,3,1,4,1,222,172,192,75,0,161,244,197,233,193,9,5,2,18,229,212,189,183,1,1,0,4,231,233,173,168,9,1,0,40,235,139,213,209,3,1,0,2,171,249,223,240,13,1,0,2,237,203,168,145,4,8,0,1,2,8,17,1,21,1,25,1,31,1,37,1,40,4,238,246,246,209,14,1,0,6,206,211,220,252,6,1,0,34,176,198,177,177,14,1,0,2,241,188,132,177,11,1,0,5,242,233,195,179,14,1,0,2,243,207,130,177,3,1,0,9,244,197,233,193,9,1,0,6,147,233,229,181,2,1,0,10,145,187,128,129,2,5,8,1,11,7,20,4,26,4,39,1,176,157,175,239,9,1,0,2,189,169,216,163,13,1,0,2,222,139,223,157,3,1,0,31,222,172,192,75,1,0,2]} \ No newline at end of file diff --git a/frontend/appflowy_web_app/cypress/fixtures/editor/blocks/paragraph.json b/frontend/appflowy_web_app/cypress/fixtures/editor/blocks/paragraph.json deleted file mode 100644 index 044d21a342..0000000000 --- a/frontend/appflowy_web_app/cypress/fixtures/editor/blocks/paragraph.json +++ /dev/null @@ -1,104 +0,0 @@ -[ - { - "type": "paragraph", - "data": {}, - "children": [], - "text": [ - { - "insert": "This is a paragraph block with multiple lines.", - "attributes": { - "bold": true - } - }, - { - "insert": "It has multiple lines of text.", - "attributes": { - "italic": true, - "underline": true, - "strikethrough": true, - "font_color": "#ff0000", - "bg_color": "#00ff00" - } - } - ] - }, - { - "type": "paragraph", - "data": {}, - "children": [], - "text": [ - { - "insert": "inline code", - "attributes": { - "code": true - } - }, - { - "insert": "link", - "attributes": { - "href": "https://example.com" - } - }, - { - "insert": "diff font", - "attributes": { - "font_family": "monospace" - } - } - ] - }, - { - "type": "paragraph", - "data": {}, - "text": [ - { - "insert": "This is a nested block." - } - ], - "children": [ - { - "type": "paragraph", - "data": {}, - "text": [ - { - "insert": "This is a nested block." - } - ], - "children": [ - { - "type": "paragraph", - "data": {}, - "text": [ - { - "insert": "This is a nested block." - } - ], - "children": [ - { - "type": "paragraph", - "data": {}, - "text": [ - { - "insert": "This is a nested block." - } - ], - "children": [ - { - "type": "paragraph", - "data": {}, - "text": [ - { - "insert": "This is a nested block." - } - ], - "children": [] - } - ] - } - ] - } - ] - } - ] - } -] \ No newline at end of file diff --git a/frontend/appflowy_web_app/cypress/fixtures/full_doc.json b/frontend/appflowy_web_app/cypress/fixtures/full_doc.json deleted file mode 100644 index c4eabdadc4..0000000000 --- a/frontend/appflowy_web_app/cypress/fixtures/full_doc.json +++ /dev/null @@ -1 +0,0 @@ -{"data":{"state_vector":[74,131,182,180,202,12,53,132,236,218,251,9,14,131,159,159,151,1,72,131,128,202,229,9,1,135,182,134,178,8,51,136,172,186,168,4,182,6,136,199,176,231,9,40,133,181,204,218,3,50,140,167,201,161,14,10,141,151,160,163,4,24,142,211,188,164,13,15,141,178,210,127,3,145,224,235,133,7,3,146,209,153,247,13,186,1,146,216,250,133,2,180,1,146,175,139,236,2,199,1,150,152,188,203,6,20,151,234,142,238,11,27,150,216,171,142,3,188,8,153,236,182,220,1,4,151,254,242,152,9,145,1,155,213,159,176,1,10,161,234,157,145,5,7,164,202,219,213,10,122,165,131,171,211,15,20,168,215,223,235,2,56,171,236,222,251,5,252,4,172,254,181,239,1,15,174,203,157,214,7,6,176,238,158,139,14,175,2,177,239,218,225,4,3,178,187,245,161,14,11,180,189,170,253,8,12,181,150,190,222,14,95,181,156,253,158,6,5,183,182,135,14,227,2,184,146,243,216,14,7,185,164,169,62,90,183,213,134,255,8,28,190,183,139,210,2,110,192,246,139,213,2,35,192,187,174,206,8,223,5,194,228,144,71,76,195,254,251,180,11,58,197,205,192,233,12,9,198,223,206,159,1,145,2,198,234,131,228,11,50,199,130,209,189,2,141,8,204,195,206,156,1,153,9,206,214,243,86,178,1,207,210,187,205,12,8,208,203,223,226,9,81,207,231,154,196,9,3,217,168,198,159,4,7,218,255,204,32,21,219,200,174,197,9,25,220,225,223,240,3,60,223,215,172,155,15,5,224,159,166,178,15,30,226,167,254,250,5,13,227,211,144,195,8,12,228,242,134,215,15,12,229,154,194,35,178,1,226,235,133,189,11,8,236,158,128,159,2,4,237,140,187,206,2,21,236,253,128,205,3,9,239,239,208,251,10,17,240,179,157,219,7,4,241,147,239,232,6,4,238,153,239,204,9,49,243,138,171,183,10,252,1,245,181,155,135,2,23,247,212,219,208,10,46],"doc_state":[74,9,228,242,134,215,15,0,39,0,204,195,206,156,1,4,6,109,86,80,71,80,99,2,4,0,228,242,134,215,15,0,4,104,106,107,100,161,172,254,181,239,1,14,1,132,228,242,134,215,15,4,1,56,161,228,242,134,215,15,5,1,132,228,242,134,215,15,6,1,56,161,228,242,134,215,15,7,1,132,228,242,134,215,15,8,1,56,161,228,242,134,215,15,9,1,18,165,131,171,211,15,0,129,155,213,159,176,1,6,2,161,155,213,159,176,1,7,1,161,155,213,159,176,1,8,1,161,155,213,159,176,1,9,1,161,165,131,171,211,15,2,1,161,165,131,171,211,15,3,1,161,165,131,171,211,15,4,1,161,165,131,171,211,15,5,1,161,165,131,171,211,15,6,1,161,165,131,171,211,15,7,1,129,165,131,171,211,15,1,2,161,165,131,171,211,15,8,1,161,165,131,171,211,15,9,1,161,165,131,171,211,15,10,1,129,165,131,171,211,15,12,1,161,165,131,171,211,15,13,1,161,165,131,171,211,15,14,1,161,165,131,171,211,15,15,1,28,224,159,166,178,15,0,129,165,131,171,211,15,16,1,161,197,205,192,233,12,6,1,161,197,205,192,233,12,7,1,161,197,205,192,233,12,8,1,161,224,159,166,178,15,1,1,161,224,159,166,178,15,2,1,161,224,159,166,178,15,3,1,129,224,159,166,178,15,0,1,161,224,159,166,178,15,4,1,161,224,159,166,178,15,5,1,161,224,159,166,178,15,6,1,129,224,159,166,178,15,7,2,161,224,159,166,178,15,8,1,161,224,159,166,178,15,9,1,161,224,159,166,178,15,10,1,161,224,159,166,178,15,13,1,161,224,159,166,178,15,14,1,161,224,159,166,178,15,15,1,161,224,159,166,178,15,16,1,161,224,159,166,178,15,17,1,161,224,159,166,178,15,18,1,161,224,159,166,178,15,19,1,161,224,159,166,178,15,20,1,161,224,159,166,178,15,21,1,129,224,159,166,178,15,12,2,161,224,159,166,178,15,22,1,161,224,159,166,178,15,23,1,161,224,159,166,178,15,24,1,1,223,215,172,155,15,0,161,185,164,169,62,89,5,71,181,150,190,222,14,0,39,0,204,195,206,156,1,4,6,68,81,108,56,102,54,2,1,0,181,150,190,222,14,0,2,0,6,39,0,204,195,206,156,1,4,6,110,114,88,86,119,98,2,33,0,204,195,206,156,1,1,6,114,119,110,108,70,75,1,0,7,33,0,204,195,206,156,1,3,6,54,105,119,67,105,57,1,193,199,130,209,189,2,191,5,199,130,209,189,2,176,6,1,39,0,204,195,206,156,1,4,6,76,95,120,101,104,45,2,33,0,204,195,206,156,1,1,6,118,52,75,115,74,51,1,0,7,33,0,204,195,206,156,1,3,6,77,54,85,88,53,66,1,193,199,130,209,189,2,191,5,181,150,190,222,14,19,1,39,0,204,195,206,156,1,4,6,105,82,99,102,107,49,2,33,0,204,195,206,156,1,1,6,70,101,106,82,116,48,1,0,7,33,0,204,195,206,156,1,3,6,108,75,113,56,70,69,1,129,199,130,209,189,2,156,6,1,39,0,204,195,206,156,1,4,6,69,114,74,53,80,51,2,39,0,204,195,206,156,1,1,6,115,115,117,107,51,70,1,40,0,181,150,190,222,14,43,2,105,100,1,119,6,115,115,117,107,51,70,40,0,181,150,190,222,14,43,2,116,121,1,119,13,110,117,109,98,101,114,101,100,95,108,105,115,116,40,0,181,150,190,222,14,43,6,112,97,114,101,110,116,1,119,6,78,99,104,45,81,78,40,0,181,150,190,222,14,43,8,99,104,105,108,100,114,101,110,1,119,6,118,108,89,79,54,57,40,0,181,150,190,222,14,43,4,100,97,116,97,1,119,12,123,34,100,101,108,116,97,34,58,91,93,125,40,0,181,150,190,222,14,43,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,181,150,190,222,14,43,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,118,108,89,79,54,57,0,136,199,130,209,189,2,176,6,1,119,6,115,115,117,107,51,70,39,0,204,195,206,156,1,4,6,98,66,87,54,98,51,2,39,0,204,195,206,156,1,1,6,80,71,48,76,73,113,1,40,0,181,150,190,222,14,54,2,105,100,1,119,6,80,71,48,76,73,113,40,0,181,150,190,222,14,54,2,116,121,1,119,11,116,111,103,103,108,101,95,108,105,115,116,40,0,181,150,190,222,14,54,6,112,97,114,101,110,116,1,119,6,78,99,104,45,81,78,40,0,181,150,190,222,14,54,8,99,104,105,108,100,114,101,110,1,119,6,79,69,102,100,51,114,33,0,181,150,190,222,14,54,4,100,97,116,97,1,40,0,181,150,190,222,14,54,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,181,150,190,222,14,54,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,79,69,102,100,51,114,0,136,181,150,190,222,14,52,1,119,6,80,71,48,76,73,113,4,0,181,150,190,222,14,53,1,49,161,181,150,190,222,14,59,1,132,181,150,190,222,14,64,1,49,161,181,150,190,222,14,65,1,132,181,150,190,222,14,66,1,49,161,181,150,190,222,14,67,1,132,181,150,190,222,14,68,1,49,161,181,150,190,222,14,69,1,132,181,150,190,222,14,70,1,49,161,181,150,190,222,14,71,1,39,0,204,195,206,156,1,4,6,75,77,105,49,106,114,2,39,0,204,195,206,156,1,1,6,49,116,120,121,68,99,1,40,0,181,150,190,222,14,75,2,105,100,1,119,6,49,116,120,121,68,99,40,0,181,150,190,222,14,75,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,181,150,190,222,14,75,6,112,97,114,101,110,116,1,119,6,80,71,48,76,73,113,40,0,181,150,190,222,14,75,8,99,104,105,108,100,114,101,110,1,119,6,111,67,65,71,120,67,33,0,181,150,190,222,14,75,4,100,97,116,97,1,40,0,181,150,190,222,14,75,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,181,150,190,222,14,75,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,111,67,65,71,120,67,0,8,0,181,150,190,222,14,62,1,119,6,49,116,120,121,68,99,161,181,150,190,222,14,73,1,4,0,181,150,190,222,14,74,1,54,161,181,150,190,222,14,80,1,132,181,150,190,222,14,86,1,54,161,181,150,190,222,14,87,1,132,181,150,190,222,14,88,1,54,161,181,150,190,222,14,89,1,132,181,150,190,222,14,90,1,54,168,181,150,190,222,14,91,1,119,29,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,54,54,54,54,34,125,93,125,168,181,150,190,222,14,85,1,119,47,123,34,99,111,108,108,97,112,115,101,100,34,58,116,114,117,101,44,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,49,49,49,49,49,34,125,93,125,7,184,146,243,216,14,0,129,217,168,198,159,4,3,1,161,142,211,188,164,13,12,1,161,142,211,188,164,13,13,1,161,142,211,188,164,13,14,1,161,184,146,243,216,14,1,1,161,184,146,243,216,14,2,1,161,184,146,243,216,14,3,1,5,178,187,245,161,14,0,39,0,204,195,206,156,1,4,6,95,104,88,73,115,119,2,4,0,178,187,245,161,14,0,13,229,144,140,228,184,128,228,184,170,106,106,106,57,161,198,223,206,159,1,124,1,132,178,187,245,161,14,7,1,57,161,178,187,245,161,14,8,1,5,140,167,201,161,14,0,0,4,129,204,195,206,156,1,245,5,3,161,198,223,206,159,1,89,1,161,198,223,206,159,1,90,1,161,198,223,206,159,1,91,1,1,176,238,158,139,14,0,161,206,214,243,86,177,1,175,2,1,146,209,153,247,13,0,161,131,159,159,151,1,67,186,1,15,142,211,188,164,13,0,161,217,168,198,159,4,4,1,161,217,168,198,159,4,5,1,161,217,168,198,159,4,6,1,161,142,211,188,164,13,0,1,161,142,211,188,164,13,1,1,161,142,211,188,164,13,2,1,161,142,211,188,164,13,3,1,161,142,211,188,164,13,4,1,161,142,211,188,164,13,5,1,161,142,211,188,164,13,6,1,161,142,211,188,164,13,7,1,161,142,211,188,164,13,8,1,161,142,211,188,164,13,9,1,161,142,211,188,164,13,10,1,161,142,211,188,164,13,11,1,9,197,205,192,233,12,0,161,165,131,171,211,15,17,1,161,165,131,171,211,15,18,1,161,165,131,171,211,15,19,1,161,197,205,192,233,12,0,1,161,197,205,192,233,12,1,1,161,197,205,192,233,12,2,1,161,197,205,192,233,12,3,1,161,197,205,192,233,12,4,1,161,197,205,192,233,12,5,1,1,207,210,187,205,12,0,161,208,203,223,226,9,76,8,47,131,182,180,202,12,0,129,184,146,243,216,14,0,1,161,184,146,243,216,14,4,1,161,184,146,243,216,14,5,1,161,184,146,243,216,14,6,1,129,131,182,180,202,12,0,2,161,131,182,180,202,12,1,1,161,131,182,180,202,12,2,1,161,131,182,180,202,12,3,1,161,131,182,180,202,12,6,1,161,131,182,180,202,12,7,1,161,131,182,180,202,12,8,1,161,131,182,180,202,12,9,1,161,131,182,180,202,12,10,1,161,131,182,180,202,12,11,1,161,131,182,180,202,12,12,1,161,131,182,180,202,12,13,1,161,131,182,180,202,12,14,1,129,131,182,180,202,12,5,3,161,131,182,180,202,12,15,1,161,131,182,180,202,12,16,1,161,131,182,180,202,12,17,1,161,131,182,180,202,12,21,1,161,131,182,180,202,12,22,1,161,131,182,180,202,12,23,1,161,131,182,180,202,12,24,1,161,131,182,180,202,12,25,1,161,131,182,180,202,12,26,1,161,131,182,180,202,12,27,1,161,131,182,180,202,12,28,1,161,131,182,180,202,12,29,1,129,131,182,180,202,12,20,4,161,131,182,180,202,12,30,1,161,131,182,180,202,12,31,1,161,131,182,180,202,12,32,1,161,131,182,180,202,12,37,1,161,131,182,180,202,12,38,1,161,131,182,180,202,12,39,1,129,131,182,180,202,12,36,1,161,131,182,180,202,12,40,1,161,131,182,180,202,12,41,1,161,131,182,180,202,12,42,1,161,131,182,180,202,12,44,1,161,131,182,180,202,12,45,1,161,131,182,180,202,12,46,1,161,131,182,180,202,12,47,1,161,131,182,180,202,12,48,1,161,131,182,180,202,12,49,1,1,151,234,142,238,11,0,161,229,154,194,35,177,1,27,1,198,234,131,228,11,0,161,236,253,128,205,3,8,50,2,226,235,133,189,11,0,161,145,224,235,133,7,2,7,168,226,235,133,189,11,6,1,122,0,0,0,0,102,88,73,73,1,195,254,251,180,11,0,161,183,182,135,14,224,2,58,1,239,239,208,251,10,0,33,1,4,109,101,116,97,12,108,97,115,116,95,115,121,110,99,95,97,116,17,81,164,202,219,213,10,0,39,0,204,195,206,156,1,4,6,103,67,116,99,89,115,2,39,0,204,195,206,156,1,4,6,102,105,108,83,57,100,2,4,0,164,202,219,213,10,1,10,116,111,100,111,32,108,105,115,116,32,134,164,202,219,213,10,11,7,109,101,110,116,105,111,110,51,123,34,116,121,112,101,34,58,34,100,97,116,101,34,44,34,100,97,116,101,34,58,34,50,48,50,52,45,48,52,45,49,56,84,49,52,58,50,53,58,51,50,46,52,53,55,50,55,55,34,125,132,164,202,219,213,10,12,1,36,134,164,202,219,213,10,13,7,109,101,110,116,105,111,110,4,110,117,108,108,132,164,202,219,213,10,14,4,109,101,110,116,33,0,204,195,206,156,1,1,6,88,55,78,102,76,50,1,0,7,33,0,204,195,206,156,1,3,6,112,56,66,76,122,103,1,193,198,223,206,159,1,135,1,199,130,209,189,2,60,1,168,199,130,209,189,2,140,8,1,119,161,1,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,116,111,100,111,32,108,105,115,116,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,109,101,110,116,105,111,110,34,58,123,34,100,97,116,101,34,58,34,50,48,50,52,45,48,52,45,49,56,84,49,52,58,50,53,58,51,50,46,52,53,55,50,55,55,34,44,34,116,121,112,101,34,58,34,100,97,116,101,34,125,125,44,34,105,110,115,101,114,116,34,58,34,36,34,125,44,123,34,105,110,115,101,114,116,34,58,34,109,101,110,116,34,125,93,44,34,99,104,101,99,107,101,100,34,58,102,97,108,115,101,125,39,0,204,195,206,156,1,4,6,66,120,115,95,114,76,2,33,0,204,195,206,156,1,1,6,111,56,54,77,119,121,1,0,7,33,0,204,195,206,156,1,3,6,67,89,84,109,67,89,1,193,198,223,206,159,1,135,1,164,202,219,213,10,28,1,4,0,164,202,219,213,10,30,1,35,0,1,39,0,204,195,206,156,1,4,6,109,113,102,117,86,95,2,33,0,204,195,206,156,1,1,6,84,100,115,87,90,75,1,0,7,33,0,204,195,206,156,1,3,6,49,115,106,52,120,74,1,193,198,223,206,159,1,135,1,164,202,219,213,10,40,1,4,0,164,202,219,213,10,43,1,49,0,1,132,164,202,219,213,10,54,1,50,0,1,132,164,202,219,213,10,56,1,51,0,1,132,164,202,219,213,10,58,1,32,0,1,129,164,202,219,213,10,60,1,0,1,134,164,202,219,213,10,62,7,109,101,110,116,105,111,110,51,123,34,100,97,116,101,34,58,34,50,48,50,52,45,48,52,45,49,57,84,49,54,58,49,51,58,52,57,46,52,49,49,49,54,53,34,44,34,116,121,112,101,34,58,34,100,97,116,101,34,125,132,164,202,219,213,10,64,1,36,134,164,202,219,213,10,65,7,109,101,110,116,105,111,110,4,110,117,108,108,0,1,39,0,204,195,206,156,1,4,6,103,83,52,80,113,73,2,4,0,164,202,219,213,10,68,4,49,50,51,32,134,164,202,219,213,10,72,7,109,101,110,116,105,111,110,51,123,34,116,121,112,101,34,58,34,100,97,116,101,34,44,34,100,97,116,101,34,58,34,50,48,50,52,45,48,52,45,49,57,84,49,54,58,49,51,58,52,57,46,52,49,49,49,54,53,34,125,132,164,202,219,213,10,73,1,36,134,164,202,219,213,10,74,7,109,101,110,116,105,111,110,4,110,117,108,108,132,164,202,219,213,10,75,1,32,0,1,129,164,202,219,213,10,76,1,0,1,161,204,195,206,156,1,155,1,1,161,204,195,206,156,1,156,1,1,161,204,195,206,156,1,157,1,1,0,1,132,164,202,219,213,10,78,1,32,0,1,132,164,202,219,213,10,84,1,101,0,1,132,164,202,219,213,10,86,1,114,0,1,132,164,202,219,213,10,88,1,32,0,1,132,164,202,219,213,10,90,1,32,0,1,68,164,202,219,213,10,69,1,35,0,1,68,164,202,219,213,10,94,1,35,0,1,39,0,204,195,206,156,1,4,6,120,115,71,80,56,122,2,4,0,164,202,219,213,10,98,4,49,50,51,32,134,164,202,219,213,10,102,7,109,101,110,116,105,111,110,51,123,34,100,97,116,101,34,58,34,50,48,50,52,45,48,52,45,49,57,84,49,54,58,49,51,58,52,57,46,52,49,49,49,54,53,34,44,34,116,121,112,101,34,58,34,100,97,116,101,34,125,132,164,202,219,213,10,103,1,36,134,164,202,219,213,10,104,7,109,101,110,116,105,111,110,4,110,117,108,108,132,164,202,219,213,10,105,6,32,32,101,114,32,32,39,0,204,195,206,156,1,1,6,106,97,80,87,115,68,1,40,0,164,202,219,213,10,112,2,105,100,1,119,6,106,97,80,87,115,68,40,0,164,202,219,213,10,112,2,116,121,1,119,7,104,101,97,100,105,110,103,40,0,164,202,219,213,10,112,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,164,202,219,213,10,112,8,99,104,105,108,100,114,101,110,1,119,6,106,75,88,90,122,73,33,0,164,202,219,213,10,112,4,100,97,116,97,1,40,0,164,202,219,213,10,112,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,164,202,219,213,10,112,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,106,75,88,90,122,73,0,200,198,223,206,159,1,135,1,164,202,219,213,10,53,1,119,6,106,97,80,87,115,68,1,247,212,219,208,10,0,161,132,236,218,251,9,13,46,240,1,243,138,171,183,10,0,161,150,216,171,142,3,178,5,1,161,150,216,171,142,3,190,5,1,161,150,216,171,142,3,200,5,1,161,150,216,171,142,3,187,6,1,161,150,216,171,142,3,188,6,1,161,150,216,171,142,3,189,6,1,161,150,216,171,142,3,190,6,2,161,150,216,171,142,3,251,5,1,161,150,216,171,142,3,144,6,1,161,150,216,171,142,3,164,6,1,161,243,138,171,183,10,7,1,161,243,138,171,183,10,0,1,161,243,138,171,183,10,1,1,161,243,138,171,183,10,2,1,161,243,138,171,183,10,8,1,161,243,138,171,183,10,9,1,161,243,138,171,183,10,10,1,161,243,138,171,183,10,11,1,161,243,138,171,183,10,3,1,161,243,138,171,183,10,4,1,161,243,138,171,183,10,5,1,161,243,138,171,183,10,18,2,161,243,138,171,183,10,12,1,161,243,138,171,183,10,13,1,161,243,138,171,183,10,14,1,161,243,138,171,183,10,19,1,161,243,138,171,183,10,20,1,161,243,138,171,183,10,21,1,161,243,138,171,183,10,23,1,161,243,138,171,183,10,15,1,161,243,138,171,183,10,16,1,161,243,138,171,183,10,17,1,161,243,138,171,183,10,30,2,161,243,138,171,183,10,24,1,161,243,138,171,183,10,25,1,161,243,138,171,183,10,26,1,161,243,138,171,183,10,27,1,161,243,138,171,183,10,28,1,161,243,138,171,183,10,29,1,161,243,138,171,183,10,35,1,161,243,138,171,183,10,31,1,161,243,138,171,183,10,32,1,161,243,138,171,183,10,33,1,161,243,138,171,183,10,42,2,161,243,138,171,183,10,36,1,161,243,138,171,183,10,37,1,161,243,138,171,183,10,38,1,161,243,138,171,183,10,43,1,161,243,138,171,183,10,44,1,161,243,138,171,183,10,45,1,161,243,138,171,183,10,47,1,161,243,138,171,183,10,39,1,161,243,138,171,183,10,40,1,161,243,138,171,183,10,41,1,161,243,138,171,183,10,54,2,161,243,138,171,183,10,48,1,161,243,138,171,183,10,49,1,161,243,138,171,183,10,50,1,161,243,138,171,183,10,55,1,161,243,138,171,183,10,56,1,161,243,138,171,183,10,57,1,161,243,138,171,183,10,59,1,161,243,138,171,183,10,51,1,161,243,138,171,183,10,52,1,161,243,138,171,183,10,53,1,161,243,138,171,183,10,66,2,161,243,138,171,183,10,60,1,161,243,138,171,183,10,61,1,161,243,138,171,183,10,62,1,161,243,138,171,183,10,63,1,161,243,138,171,183,10,64,1,161,243,138,171,183,10,65,1,161,243,138,171,183,10,71,2,161,243,138,171,183,10,67,1,161,243,138,171,183,10,68,1,161,243,138,171,183,10,69,1,161,243,138,171,183,10,79,1,161,243,138,171,183,10,72,1,161,243,138,171,183,10,73,1,161,243,138,171,183,10,74,1,161,243,138,171,183,10,75,1,161,243,138,171,183,10,76,1,161,243,138,171,183,10,77,1,161,243,138,171,183,10,83,1,161,243,138,171,183,10,80,1,161,243,138,171,183,10,81,1,161,243,138,171,183,10,82,1,161,243,138,171,183,10,90,2,161,146,216,250,133,2,12,1,161,146,216,250,133,2,13,1,161,146,216,250,133,2,14,1,161,146,216,250,133,2,17,1,161,146,216,250,133,2,21,1,161,146,216,250,133,2,22,1,161,146,216,250,133,2,23,1,161,243,138,171,183,10,99,1,161,146,216,250,133,2,18,1,161,146,216,250,133,2,19,1,161,146,216,250,133,2,20,1,161,243,138,171,183,10,103,1,161,146,216,250,133,2,24,1,161,146,216,250,133,2,25,1,161,146,216,250,133,2,26,1,161,146,216,250,133,2,35,1,161,146,216,250,133,2,28,1,161,146,216,250,133,2,29,1,161,146,216,250,133,2,30,1,161,243,138,171,183,10,111,1,161,146,216,250,133,2,32,1,161,146,216,250,133,2,33,1,161,146,216,250,133,2,34,1,161,243,138,171,183,10,115,1,161,146,216,250,133,2,36,1,161,146,216,250,133,2,37,1,161,146,216,250,133,2,38,1,161,146,216,250,133,2,40,1,161,146,216,250,133,2,41,1,161,146,216,250,133,2,42,1,161,146,216,250,133,2,47,1,161,146,216,250,133,2,44,1,161,146,216,250,133,2,45,1,161,146,216,250,133,2,46,1,161,243,138,171,183,10,126,2,161,146,216,250,133,2,48,1,161,146,216,250,133,2,49,1,161,146,216,250,133,2,50,1,161,146,216,250,133,2,59,1,161,146,216,250,133,2,51,1,161,146,216,250,133,2,52,1,161,146,216,250,133,2,53,1,161,243,138,171,183,10,135,1,1,161,146,216,250,133,2,56,1,161,146,216,250,133,2,57,1,161,146,216,250,133,2,58,1,161,243,138,171,183,10,139,1,1,161,146,216,250,133,2,60,1,161,146,216,250,133,2,61,1,161,146,216,250,133,2,62,1,161,146,216,250,133,2,71,1,161,146,216,250,133,2,64,1,161,146,216,250,133,2,65,1,161,146,216,250,133,2,66,1,161,243,138,171,183,10,147,1,1,161,146,216,250,133,2,68,1,161,146,216,250,133,2,69,1,161,146,216,250,133,2,70,1,161,243,138,171,183,10,151,1,1,161,146,216,250,133,2,72,1,161,146,216,250,133,2,73,1,161,146,216,250,133,2,74,1,161,146,216,250,133,2,83,1,161,146,216,250,133,2,76,1,161,146,216,250,133,2,77,1,161,146,216,250,133,2,78,1,161,243,138,171,183,10,159,1,1,161,146,216,250,133,2,80,1,161,146,216,250,133,2,81,1,161,146,216,250,133,2,82,1,161,243,138,171,183,10,163,1,1,161,146,216,250,133,2,84,1,161,146,216,250,133,2,85,1,161,146,216,250,133,2,86,1,161,146,216,250,133,2,95,1,161,146,216,250,133,2,92,1,161,146,216,250,133,2,93,1,161,146,216,250,133,2,94,1,161,243,138,171,183,10,171,1,1,161,146,216,250,133,2,88,1,161,146,216,250,133,2,89,1,161,146,216,250,133,2,90,1,161,243,138,171,183,10,175,1,1,161,146,216,250,133,2,96,1,161,146,216,250,133,2,97,1,161,146,216,250,133,2,98,1,161,146,216,250,133,2,107,1,161,146,216,250,133,2,104,1,161,146,216,250,133,2,105,1,161,146,216,250,133,2,106,1,161,243,138,171,183,10,183,1,1,161,146,216,250,133,2,100,1,161,146,216,250,133,2,101,1,161,146,216,250,133,2,102,1,161,243,138,171,183,10,187,1,1,161,146,216,250,133,2,108,1,161,146,216,250,133,2,109,1,161,146,216,250,133,2,110,1,161,146,216,250,133,2,119,1,161,146,216,250,133,2,112,1,161,146,216,250,133,2,113,1,161,146,216,250,133,2,114,1,161,243,138,171,183,10,195,1,1,161,146,216,250,133,2,116,1,161,146,216,250,133,2,117,1,161,146,216,250,133,2,118,1,161,243,138,171,183,10,199,1,1,161,146,216,250,133,2,120,1,161,146,216,250,133,2,121,1,161,146,216,250,133,2,122,1,161,146,216,250,133,2,131,1,2,161,146,216,250,133,2,124,1,161,146,216,250,133,2,125,1,161,146,216,250,133,2,126,1,161,146,216,250,133,2,128,1,1,161,146,216,250,133,2,129,1,1,161,146,216,250,133,2,130,1,1,161,243,138,171,183,10,208,1,1,161,146,216,250,133,2,132,1,1,161,146,216,250,133,2,133,1,1,161,146,216,250,133,2,134,1,1,161,146,216,250,133,2,143,1,1,161,146,216,250,133,2,136,1,1,161,146,216,250,133,2,137,1,1,161,146,216,250,133,2,138,1,1,161,243,138,171,183,10,219,1,1,161,146,216,250,133,2,140,1,1,161,146,216,250,133,2,141,1,1,161,146,216,250,133,2,142,1,1,161,243,138,171,183,10,223,1,1,161,146,216,250,133,2,144,1,1,161,146,216,250,133,2,145,1,1,161,146,216,250,133,2,146,1,1,161,146,216,250,133,2,155,1,1,161,146,216,250,133,2,148,1,1,161,146,216,250,133,2,149,1,1,161,146,216,250,133,2,150,1,1,161,243,138,171,183,10,231,1,1,161,146,216,250,133,2,152,1,1,161,146,216,250,133,2,153,1,1,161,146,216,250,133,2,154,1,1,161,243,138,171,183,10,235,1,1,161,146,216,250,133,2,156,1,1,161,146,216,250,133,2,157,1,1,161,146,216,250,133,2,158,1,1,161,146,216,250,133,2,167,1,3,161,146,216,250,133,2,160,1,1,161,146,216,250,133,2,161,1,1,161,146,216,250,133,2,162,1,1,161,146,216,250,133,2,164,1,1,161,146,216,250,133,2,165,1,1,161,146,216,250,133,2,166,1,1,1,132,236,218,251,9,0,161,218,255,204,32,20,14,34,136,199,176,231,9,0,39,0,204,195,206,156,1,1,6,74,52,82,97,73,114,1,40,0,136,199,176,231,9,0,2,105,100,1,119,6,74,52,82,97,73,114,40,0,136,199,176,231,9,0,2,116,121,1,119,4,103,114,105,100,40,0,136,199,176,231,9,0,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,136,199,176,231,9,0,8,99,104,105,108,100,114,101,110,1,119,6,86,95,76,83,51,101,40,0,136,199,176,231,9,0,4,100,97,116,97,1,119,101,123,34,118,105,101,119,95,105,100,34,58,34,49,51,53,54,49,53,102,97,45,54,54,102,55,45,52,52,53,49,45,57,98,53,52,45,100,55,101,57,57,52,52,53,102,99,97,52,34,44,34,112,97,114,101,110,116,95,105,100,34,58,34,55,100,50,49,52,56,102,99,45,99,97,99,101,45,52,52,53,50,45,57,99,53,99,45,57,54,101,53,50,101,54,98,102,56,98,53,34,125,40,0,136,199,176,231,9,0,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,136,199,176,231,9,0,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,86,95,76,83,51,101,0,200,204,195,206,156,1,252,1,204,195,206,156,1,253,1,1,119,6,74,52,82,97,73,114,39,0,204,195,206,156,1,1,6,115,74,113,109,112,57,1,40,0,136,199,176,231,9,10,2,105,100,1,119,6,115,74,113,109,112,57,40,0,136,199,176,231,9,10,2,116,121,1,119,5,98,111,97,114,100,40,0,136,199,176,231,9,10,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,136,199,176,231,9,10,8,99,104,105,108,100,114,101,110,1,119,6,87,71,71,122,72,118,40,0,136,199,176,231,9,10,4,100,97,116,97,1,119,101,123,34,112,97,114,101,110,116,95,105,100,34,58,34,97,53,53,54,54,101,52,57,45,102,49,53,54,45,52,49,54,56,45,57,98,50,100,45,49,55,57,50,54,99,53,100,97,51,50,57,34,44,34,118,105,101,119,95,105,100,34,58,34,98,52,101,55,55,50,48,51,45,53,99,56,98,45,52,56,100,102,45,98,98,99,53,45,50,101,49,49,52,51,101,98,48,101,54,49,34,125,40,0,136,199,176,231,9,10,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,136,199,176,231,9,10,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,87,71,71,122,72,118,0,200,136,199,176,231,9,9,204,195,206,156,1,253,1,1,119,6,115,74,113,109,112,57,33,0,204,195,206,156,1,1,6,98,118,111,52,85,121,1,0,7,33,0,204,195,206,156,1,3,6,81,122,68,56,119,121,1,193,136,199,176,231,9,19,204,195,206,156,1,253,1,1,39,0,204,195,206,156,1,1,6,71,57,106,76,66,79,1,40,0,136,199,176,231,9,30,2,105,100,1,119,6,71,57,106,76,66,79,40,0,136,199,176,231,9,30,2,116,121,1,119,8,99,97,108,101,110,100,97,114,40,0,136,199,176,231,9,30,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,136,199,176,231,9,30,8,99,104,105,108,100,114,101,110,1,119,6,122,51,54,102,102,100,40,0,136,199,176,231,9,30,4,100,97,116,97,1,119,101,123,34,118,105,101,119,95,105,100,34,58,34,50,98,102,53,48,99,48,51,45,102,52,49,102,45,52,51,54,51,45,98,53,98,49,45,49,48,49,50,49,54,97,54,99,53,99,99,34,44,34,112,97,114,101,110,116,95,105,100,34,58,34,101,101,51,97,101,56,99,101,45,57,53,57,97,45,52,100,102,51,45,56,55,51,52,45,52,48,98,53,51,53,102,102,56,56,101,51,34,125,40,0,136,199,176,231,9,30,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,136,199,176,231,9,30,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,122,51,54,102,102,100,0,200,136,199,176,231,9,29,204,195,206,156,1,253,1,1,119,6,71,57,106,76,66,79,1,131,128,202,229,9,0,161,243,138,171,183,10,245,1,1,1,208,203,223,226,9,0,161,146,209,153,247,13,185,1,81,31,238,153,239,204,9,0,161,183,213,134,255,8,25,1,161,183,213,134,255,8,26,1,161,183,213,134,255,8,27,1,132,183,213,134,255,8,24,1,100,161,238,153,239,204,9,0,1,161,238,153,239,204,9,1,1,161,238,153,239,204,9,2,1,132,238,153,239,204,9,3,1,55,161,238,153,239,204,9,4,1,161,238,153,239,204,9,5,1,161,238,153,239,204,9,6,1,132,238,153,239,204,9,7,1,55,168,238,153,239,204,9,8,1,119,133,1,123,34,100,101,108,116,97,34,58,91,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,109,101,110,116,105,111,110,34,58,123,34,112,97,103,101,95,105,100,34,58,34,100,48,52,57,54,51,50,52,45,53,53,55,48,45,52,48,48,54,45,98,52,101,97,45,100,98,55,53,49,54,100,50,49,50,102,100,34,44,34,116,121,112,101,34,58,34,112,97,103,101,34,125,125,44,34,105,110,115,101,114,116,34,58,34,36,34,125,44,123,34,105,110,115,101,114,116,34,58,34,100,55,55,34,125,93,125,168,238,153,239,204,9,9,1,119,10,107,106,48,68,49,121,121,88,78,119,168,238,153,239,204,9,10,1,119,4,116,101,120,116,39,0,204,195,206,156,1,4,6,111,97,103,82,55,77,2,6,0,238,153,239,204,9,15,4,104,114,101,102,13,34,97,112,112,102,108,111,119,121,46,105,111,34,132,238,153,239,204,9,16,11,97,112,112,102,108,111,119,121,46,105,111,134,238,153,239,204,9,27,4,104,114,101,102,4,110,117,108,108,132,238,153,239,204,9,28,1,32,161,151,254,242,152,9,90,1,132,238,153,239,204,9,29,1,49,161,238,153,239,204,9,30,1,39,0,204,195,206,156,1,4,6,53,101,83,117,83,45,2,6,0,238,153,239,204,9,33,4,104,114,101,102,13,34,49,57,50,46,49,54,56,46,49,46,50,34,132,238,153,239,204,9,34,9,99,111,110,116,101,110,116,32,49,134,238,153,239,204,9,43,4,104,114,101,102,4,110,117,108,108,132,238,153,239,204,9,44,1,32,161,151,254,242,152,9,134,1,1,132,238,153,239,204,9,45,1,50,161,238,153,239,204,9,46,1,1,219,200,174,197,9,0,161,161,234,157,145,5,6,25,3,207,231,154,196,9,0,161,204,195,206,156,1,209,1,1,161,204,195,206,156,1,210,1,1,161,204,195,206,156,1,211,1,1,118,151,254,242,152,9,0,39,0,204,195,206,156,1,4,6,65,119,80,77,53,56,2,6,0,151,254,242,152,9,0,7,109,101,110,116,105,111,110,64,123,34,116,121,112,101,34,58,34,112,97,103,101,34,44,34,112,97,103,101,95,105,100,34,58,34,100,100,98,57,51,98,97,55,45,48,54,99,55,45,52,49,55,54,45,57,56,50,97,45,100,55,52,50,51,101,48,57,98,52,52,49,34,125,132,151,254,242,152,9,1,1,36,134,151,254,242,152,9,2,7,109,101,110,116,105,111,110,4,110,117,108,108,132,151,254,242,152,9,3,1,104,161,220,225,223,240,3,59,1,129,151,254,242,152,9,4,2,161,151,254,242,152,9,5,1,129,151,254,242,152,9,7,2,161,151,254,242,152,9,8,1,129,151,254,242,152,9,10,1,132,151,254,242,152,9,12,1,104,161,151,254,242,152,9,11,1,196,151,254,242,152,9,4,151,254,242,152,9,6,2,104,104,161,151,254,242,152,9,14,1,132,151,254,242,152,9,13,1,32,161,151,254,242,152,9,17,1,129,151,254,242,152,9,18,1,161,151,254,242,152,9,19,1,134,151,254,242,152,9,20,7,109,101,110,116,105,111,110,123,123,34,100,97,116,101,34,58,34,50,48,50,52,45,48,52,45,50,53,84,49,55,58,49,50,58,52,51,46,52,51,57,57,48,57,34,44,34,114,101,109,105,110,100,101,114,95,105,100,34,58,34,118,108,95,45,105,57,52,99,82,103,69,85,115,112,84,111,81,95,115,68,86,34,44,34,116,121,112,101,34,58,34,100,97,116,101,34,44,34,114,101,109,105,110,100,101,114,95,111,112,116,105,111,110,34,58,34,97,116,84,105,109,101,79,102,69,118,101,110,116,34,125,132,151,254,242,152,9,22,1,36,134,151,254,242,152,9,23,7,109,101,110,116,105,111,110,4,110,117,108,108,168,151,254,242,152,9,21,1,119,171,2,123,34,100,101,108,116,97,34,58,91,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,109,101,110,116,105,111,110,34,58,123,34,112,97,103,101,95,105,100,34,58,34,100,100,98,57,51,98,97,55,45,48,54,99,55,45,52,49,55,54,45,57,56,50,97,45,100,55,52,50,51,101,48,57,98,52,52,49,34,44,34,116,121,112,101,34,58,34,112,97,103,101,34,125,125,44,34,105,110,115,101,114,116,34,58,34,36,34,125,44,123,34,105,110,115,101,114,116,34,58,34,104,104,104,104,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,109,101,110,116,105,111,110,34,58,123,34,100,97,116,101,34,58,34,50,48,50,52,45,48,52,45,50,53,84,49,55,58,49,50,58,52,51,46,52,51,57,57,48,57,34,44,34,114,101,109,105,110,100,101,114,95,105,100,34,58,34,118,108,95,45,105,57,52,99,82,103,69,85,115,112,84,111,81,95,115,68,86,34,44,34,114,101,109,105,110,100,101,114,95,111,112,116,105,111,110,34,58,34,97,116,84,105,109,101,79,102,69,118,101,110,116,34,44,34,116,121,112,101,34,58,34,100,97,116,101,34,125,125,44,34,105,110,115,101,114,116,34,58,34,36,34,125,93,125,39,0,204,195,206,156,1,4,6,107,74,118,98,69,107,2,39,0,204,195,206,156,1,1,6,112,71,75,102,71,113,1,40,0,151,254,242,152,9,27,2,105,100,1,119,6,112,71,75,102,71,113,40,0,151,254,242,152,9,27,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,151,254,242,152,9,27,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,151,254,242,152,9,27,8,99,104,105,108,100,114,101,110,1,119,6,54,97,84,68,85,107,33,0,151,254,242,152,9,27,4,100,97,116,97,1,40,0,151,254,242,152,9,27,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,151,254,242,152,9,27,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,54,97,84,68,85,107,0,200,220,225,223,240,3,45,204,195,206,156,1,251,1,1,119,6,112,71,75,102,71,113,1,0,151,254,242,152,9,26,1,161,151,254,242,152,9,32,1,129,151,254,242,152,9,37,1,161,151,254,242,152,9,38,1,129,151,254,242,152,9,39,1,161,151,254,242,152,9,40,1,129,151,254,242,152,9,41,1,161,151,254,242,152,9,42,1,129,151,254,242,152,9,43,1,161,151,254,242,152,9,44,1,65,151,254,242,152,9,37,6,198,151,254,242,152,9,52,151,254,242,152,9,37,4,104,114,101,102,4,110,117,108,108,161,151,254,242,152,9,46,1,65,151,254,242,152,9,47,1,161,151,254,242,152,9,54,1,193,151,254,242,152,9,55,151,254,242,152,9,47,1,161,151,254,242,152,9,56,1,193,151,254,242,152,9,57,151,254,242,152,9,47,1,161,151,254,242,152,9,58,1,193,151,254,242,152,9,59,151,254,242,152,9,47,1,161,151,254,242,152,9,60,1,193,151,254,242,152,9,61,151,254,242,152,9,47,1,161,151,254,242,152,9,62,1,193,151,254,242,152,9,63,151,254,242,152,9,47,1,161,151,254,242,152,9,64,1,193,151,254,242,152,9,65,151,254,242,152,9,47,1,161,151,254,242,152,9,66,1,193,151,254,242,152,9,67,151,254,242,152,9,47,1,161,151,254,242,152,9,68,1,193,151,254,242,152,9,69,151,254,242,152,9,47,1,161,151,254,242,152,9,70,1,193,151,254,242,152,9,71,151,254,242,152,9,47,1,161,151,254,242,152,9,72,1,193,151,254,242,152,9,73,151,254,242,152,9,47,1,161,151,254,242,152,9,74,1,70,151,254,242,152,9,55,4,104,114,101,102,13,34,97,112,112,102,108,111,119,121,46,105,111,34,196,151,254,242,152,9,77,151,254,242,152,9,55,11,97,112,112,102,108,111,119,121,46,105,111,193,151,254,242,152,9,88,151,254,242,152,9,55,1,161,151,254,242,152,9,76,1,39,0,204,195,206,156,1,4,6,88,82,74,89,90,53,2,39,0,204,195,206,156,1,1,6,72,77,49,70,106,86,1,40,0,151,254,242,152,9,92,2,105,100,1,119,6,72,77,49,70,106,86,40,0,151,254,242,152,9,92,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,151,254,242,152,9,92,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,151,254,242,152,9,92,8,99,104,105,108,100,114,101,110,1,119,6,95,45,107,102,121,108,33,0,151,254,242,152,9,92,4,100,97,116,97,1,40,0,151,254,242,152,9,92,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,151,254,242,152,9,92,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,95,45,107,102,121,108,0,200,151,254,242,152,9,36,204,195,206,156,1,251,1,1,119,6,72,77,49,70,106,86,1,0,151,254,242,152,9,91,1,161,151,254,242,152,9,97,2,129,151,254,242,152,9,102,1,161,151,254,242,152,9,104,1,129,151,254,242,152,9,105,1,161,151,254,242,152,9,106,1,129,151,254,242,152,9,107,1,161,151,254,242,152,9,108,1,129,151,254,242,152,9,109,1,161,151,254,242,152,9,110,1,129,151,254,242,152,9,111,1,161,151,254,242,152,9,112,1,129,151,254,242,152,9,113,1,161,151,254,242,152,9,114,1,129,151,254,242,152,9,115,1,161,151,254,242,152,9,116,1,129,151,254,242,152,9,117,1,161,151,254,242,152,9,118,1,129,151,254,242,152,9,119,1,161,151,254,242,152,9,120,1,198,151,254,242,152,9,102,151,254,242,152,9,105,4,104,114,101,102,13,34,49,57,50,46,49,54,56,46,49,46,50,34,196,151,254,242,152,9,123,151,254,242,152,9,105,9,99,111,110,116,101,110,116,32,49,198,151,254,242,152,9,132,1,151,254,242,152,9,105,4,104,114,101,102,4,110,117,108,108,161,151,254,242,152,9,122,1,129,204,195,206,156,1,131,4,1,161,204,195,206,156,1,56,1,161,204,195,206,156,1,57,1,161,204,195,206,156,1,58,1,161,151,254,242,152,9,136,1,1,161,151,254,242,152,9,137,1,1,161,151,254,242,152,9,138,1,1,168,151,254,242,152,9,139,1,1,119,128,1,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,67,108,105,99,107,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,99,111,100,101,34,58,116,114,117,101,125,44,34,105,110,115,101,114,116,34,58,34,63,34,125,44,123,34,105,110,115,101,114,116,34,58,34,32,97,116,32,116,104,101,32,98,111,116,116,111,109,32,114,105,103,104,116,32,102,111,114,32,104,101,108,112,32,97,110,100,32,115,117,112,112,111,114,116,34,125,93,125,168,151,254,242,152,9,140,1,1,119,10,119,86,82,81,117,71,111,121,116,48,168,151,254,242,152,9,141,1,1,119,4,116,101,120,116,28,183,213,134,255,8,0,129,220,225,223,240,3,6,1,161,220,225,223,240,3,7,1,161,220,225,223,240,3,8,1,161,220,225,223,240,3,9,1,129,183,213,134,255,8,0,1,161,183,213,134,255,8,1,1,161,183,213,134,255,8,2,1,161,183,213,134,255,8,3,1,129,183,213,134,255,8,4,1,161,183,213,134,255,8,5,1,161,183,213,134,255,8,6,1,161,183,213,134,255,8,7,1,129,183,213,134,255,8,8,1,161,183,213,134,255,8,9,1,161,183,213,134,255,8,10,1,161,183,213,134,255,8,11,1,129,183,213,134,255,8,12,1,161,183,213,134,255,8,13,1,161,183,213,134,255,8,14,1,161,183,213,134,255,8,15,1,129,183,213,134,255,8,16,1,161,183,213,134,255,8,17,1,161,183,213,134,255,8,18,1,161,183,213,134,255,8,19,1,129,183,213,134,255,8,20,1,161,183,213,134,255,8,21,1,161,183,213,134,255,8,22,1,161,183,213,134,255,8,23,1,12,180,189,170,253,8,0,168,146,175,139,236,2,0,1,119,68,123,34,119,105,100,116,104,34,58,49,53,48,46,56,53,53,52,54,56,55,53,44,34,104,101,105,103,104,116,34,58,52,54,46,48,44,34,114,111,119,80,111,115,105,116,105,111,110,34,58,48,44,34,99,111,108,80,111,115,105,116,105,111,110,34,58,48,125,168,146,175,139,236,2,1,1,119,68,123,34,99,111,108,80,111,115,105,116,105,111,110,34,58,49,44,34,119,105,100,116,104,34,58,49,48,53,46,48,53,48,55,56,49,50,53,44,34,114,111,119,80,111,115,105,116,105,111,110,34,58,48,44,34,104,101,105,103,104,116,34,58,52,54,46,48,125,168,146,175,139,236,2,2,1,119,60,123,34,99,111,108,80,111,115,105,116,105,111,110,34,58,50,44,34,104,101,105,103,104,116,34,58,52,54,46,48,44,34,114,111,119,80,111,115,105,116,105,111,110,34,58,48,44,34,119,105,100,116,104,34,58,56,48,46,48,125,168,146,175,139,236,2,3,1,119,68,123,34,119,105,100,116,104,34,58,49,53,48,46,56,53,53,52,54,56,55,53,44,34,104,101,105,103,104,116,34,58,52,54,46,48,44,34,114,111,119,80,111,115,105,116,105,111,110,34,58,49,44,34,99,111,108,80,111,115,105,116,105,111,110,34,58,48,125,168,146,175,139,236,2,4,1,119,68,123,34,114,111,119,80,111,115,105,116,105,111,110,34,58,49,44,34,99,111,108,80,111,115,105,116,105,111,110,34,58,49,44,34,119,105,100,116,104,34,58,49,48,53,46,48,53,48,55,56,49,50,53,44,34,104,101,105,103,104,116,34,58,52,54,46,48,125,168,146,175,139,236,2,5,1,119,60,123,34,119,105,100,116,104,34,58,56,48,46,48,44,34,104,101,105,103,104,116,34,58,52,54,46,48,44,34,99,111,108,80,111,115,105,116,105,111,110,34,58,50,44,34,114,111,119,80,111,115,105,116,105,111,110,34,58,49,125,161,146,175,139,236,2,11,1,168,146,175,139,236,2,7,1,119,68,123,34,99,111,108,80,111,115,105,116,105,111,110,34,58,48,44,34,104,101,105,103,104,116,34,58,52,54,46,48,44,34,114,111,119,80,111,115,105,116,105,111,110,34,58,50,44,34,119,105,100,116,104,34,58,49,53,48,46,56,53,53,52,54,56,55,53,125,168,146,175,139,236,2,8,1,119,68,123,34,104,101,105,103,104,116,34,58,52,54,46,48,44,34,114,111,119,80,111,115,105,116,105,111,110,34,58,50,44,34,119,105,100,116,104,34,58,49,48,53,46,48,53,48,55,56,49,50,53,44,34,99,111,108,80,111,115,105,116,105,111,110,34,58,49,125,168,146,175,139,236,2,9,1,119,60,123,34,119,105,100,116,104,34,58,56,48,46,48,44,34,114,111,119,80,111,115,105,116,105,111,110,34,58,50,44,34,104,101,105,103,104,116,34,58,52,54,46,48,44,34,99,111,108,80,111,115,105,116,105,111,110,34,58,50,125,161,180,189,170,253,8,6,1,168,180,189,170,253,8,10,1,119,114,123,34,99,111,108,77,105,110,105,109,117,109,87,105,100,116,104,34,58,52,48,46,48,44,34,114,111,119,68,101,102,97,117,108,116,72,101,105,103,104,116,34,58,52,48,46,48,44,34,99,111,108,115,76,101,110,34,58,51,44,34,114,111,119,115,76,101,110,34,58,51,44,34,99,111,108,68,101,102,97,117,108,116,87,105,100,116,104,34,58,56,48,46,48,44,34,99,111,108,115,72,101,105,103,104,116,34,58,49,52,54,46,48,125,21,192,187,174,206,8,0,39,0,204,195,206,156,1,4,6,72,101,110,107,82,107,2,161,150,216,171,142,3,187,8,1,1,0,192,187,174,206,8,0,3,129,192,187,174,206,8,4,15,129,192,187,174,206,8,19,45,129,192,187,174,206,8,64,10,134,192,187,174,206,8,74,11,102,111,110,116,95,102,97,109,105,108,121,4,110,117,108,108,129,192,187,174,206,8,75,110,161,192,187,174,206,8,1,1,65,192,187,174,206,8,2,5,193,192,187,174,206,8,4,192,187,174,206,8,5,14,193,192,187,174,206,8,19,192,187,174,206,8,20,44,193,192,187,174,206,8,64,192,187,174,206,8,65,9,193,192,187,174,206,8,74,192,187,174,206,8,75,110,161,192,187,174,206,8,186,1,2,193,192,187,174,206,8,240,2,192,187,174,206,8,75,180,1,161,192,187,174,206,8,242,2,1,70,192,187,174,206,8,187,1,11,102,111,110,116,95,102,97,109,105,108,121,15,34,65,68,76,97,77,32,68,105,115,112,108,97,121,34,196,192,187,174,206,8,168,4,192,187,174,206,8,187,1,180,1,108,111,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,76,101,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,198,192,187,174,206,8,220,5,192,187,174,206,8,187,1,11,102,111,110,116,95,102,97,109,105,108,121,4,110,117,108,108,161,192,187,174,206,8,167,4,1,11,227,211,144,195,8,0,161,243,138,171,183,10,240,1,1,161,243,138,171,183,10,241,1,1,161,243,138,171,183,10,242,1,1,161,194,228,144,71,4,1,161,194,228,144,71,5,1,161,194,228,144,71,6,1,161,220,225,223,240,3,34,1,161,220,225,223,240,3,30,1,161,220,225,223,240,3,31,1,161,220,225,223,240,3,32,1,161,227,211,144,195,8,6,2,9,135,182,134,178,8,0,168,141,151,160,163,4,21,1,119,147,2,123,34,99,111,118,101,114,95,115,101,108,101,99,116,105,111,110,95,116,121,112,101,34,58,34,67,111,118,101,114,84,121,112,101,46,102,105,108,101,34,44,34,99,111,118,101,114,95,115,101,108,101,99,116,105,111,110,34,58,34,104,116,116,112,115,58,47,47,105,109,97,103,101,115,46,117,110,115,112,108,97,115,104,46,99,111,109,47,112,104,111,116,111,45,49,55,49,52,53,48,56,56,54,50,55,56,56,45,52,52,101,52,53,99,52,51,49,53,100,48,63,99,114,111,112,61,101,110,116,114,111,112,121,38,99,115,61,116,105,110,121,115,114,103,98,38,102,105,116,61,109,97,120,38,102,109,61,106,112,103,38,105,120,105,100,61,77,51,119,49,77,84,69,49,77,122,100,56,77,72,119,120,102,72,74,104,98,109,82,118,98,88,120,56,102,72,120,56,102,72,120,56,102,68,69,51,77,84,89,51,78,122,103,121,77,84,108,56,38,105,120,108,105,98,61,114,98,45,52,46,48,46,51,38,113,61,56,48,38,119,61,49,48,56,48,34,44,34,105,109,97,103,101,95,116,121,112,101,34,58,34,49,34,44,34,100,101,108,116,97,34,58,91,93,125,168,141,151,160,163,4,22,1,119,10,112,70,113,76,55,45,79,83,121,86,168,141,151,160,163,4,23,1,119,4,116,101,120,116,70,204,195,206,156,1,209,5,11,102,111,110,116,95,102,97,109,105,108,121,15,34,65,68,76,97,32,77,68,105,115,112,108,97,121,34,196,135,182,134,178,8,3,204,195,206,156,1,209,5,55,67,108,105,99,107,32,97,110,121,119,104,101,114,101,32,97,110,100,32,106,117,115,116,32,115,116,97,114,116,32,116,121,112,105,110,103,229,147,136,229,147,136,229,147,136,46,229,176,177,229,135,160,229,174,182,198,135,182,134,178,8,46,204,195,206,156,1,209,5,11,102,111,110,116,95,102,97,109,105,108,121,4,110,117,108,108,168,140,167,201,161,14,7,1,119,141,1,123,34,100,101,108,116,97,34,58,91,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,102,111,110,116,95,102,97,109,105,108,121,34,58,34,65,68,76,97,32,77,68,105,115,112,108,97,121,34,125,44,34,105,110,115,101,114,116,34,58,34,67,108,105,99,107,32,97,110,121,119,104,101,114,101,32,97,110,100,32,106,117,115,116,32,115,116,97,114,116,32,116,121,112,105,110,103,229,147,136,229,147,136,229,147,136,46,229,176,177,229,135,160,229,174,182,34,125,93,44,34,99,104,101,99,107,101,100,34,58,102,97,108,115,101,125,168,140,167,201,161,14,8,1,119,10,119,79,108,117,99,85,55,51,73,76,168,140,167,201,161,14,9,1,119,4,116,101,120,116,1,240,179,157,219,7,0,161,174,203,157,214,7,5,4,1,174,203,157,214,7,0,161,153,236,182,220,1,3,6,1,145,224,235,133,7,0,161,223,215,172,155,15,4,3,1,241,147,239,232,6,0,161,245,181,155,135,2,22,4,1,150,152,188,203,6,0,161,192,246,139,213,2,34,20,2,181,156,253,158,6,0,161,198,223,206,159,1,175,1,4,168,181,156,253,158,6,3,1,119,231,1,123,34,117,114,108,34,58,34,104,116,116,112,115,58,47,47,105,109,97,103,101,115,46,117,110,115,112,108,97,115,104,46,99,111,109,47,112,104,111,116,111,45,49,55,49,50,51,48,51,55,48,48,56,51,50,45,53,55,100,50,98,50,98,57,49,54,98,56,63,99,114,111,112,61,101,110,116,114,111,112,121,38,99,115,61,116,105,110,121,115,114,103,98,38,102,105,116,61,109,97,120,38,102,109,61,106,112,103,38,105,120,105,100,61,77,51,119,49,77,84,69,49,77,122,100,56,77,72,119,120,102,72,74,104,98,109,82,118,98,88,120,56,102,72,120,56,102,72,120,56,102,68,69,51,77,84,77,121,78,84,107,122,78,84,100,56,38,105,120,108,105,98,61,114,98,45,52,46,48,46,51,38,113,61,56,48,38,119,61,49,48,56,48,34,44,34,119,105,100,116,104,34,58,52,50,56,46,49,57,53,51,49,50,53,44,34,97,108,105,103,110,34,58,34,114,105,103,104,116,34,125,220,2,171,236,222,251,5,0,198,204,195,206,156,1,205,4,204,195,206,156,1,206,4,4,98,111,108,100,4,116,114,117,101,196,171,236,222,251,5,0,204,195,206,156,1,206,4,4,112,97,103,101,198,171,236,222,251,5,4,204,195,206,156,1,206,4,4,98,111,108,100,4,110,117,108,108,168,204,195,206,156,1,119,1,119,222,1,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,67,108,105,99,107,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,99,111,100,101,34,58,116,114,117,101,125,44,34,105,110,115,101,114,116,34,58,34,43,32,78,101,119,32,80,97,103,101,32,34,125,44,123,34,105,110,115,101,114,116,34,58,34,98,117,116,116,111,110,32,97,116,32,116,104,101,32,98,111,116,116,111,109,32,111,102,32,121,111,117,114,32,115,105,100,101,98,97,114,32,116,111,32,97,100,100,32,97,32,110,101,119,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,98,111,108,100,34,58,116,114,117,101,125,44,34,105,110,115,101,114,116,34,58,34,112,97,103,101,34,125,44,123,34,105,110,115,101,114,116,34,58,34,46,34,125,93,44,34,99,104,101,99,107,101,100,34,58,116,114,117,101,125,168,204,195,206,156,1,120,1,119,10,122,77,121,109,67,97,118,83,107,102,168,204,195,206,156,1,121,1,119,4,116,101,120,116,193,204,195,206,156,1,129,6,204,195,206,156,1,130,6,5,198,171,236,222,251,5,13,204,195,206,156,1,130,6,10,102,111,110,116,95,99,111,108,111,114,4,110,117,108,108,161,204,195,206,156,1,11,1,161,204,195,206,156,1,12,1,161,204,195,206,156,1,13,1,161,171,236,222,251,5,15,1,161,171,236,222,251,5,16,1,161,171,236,222,251,5,17,1,193,204,195,206,156,1,129,6,171,236,222,251,5,9,5,198,171,236,222,251,5,25,171,236,222,251,5,9,10,102,111,110,116,95,99,111,108,111,114,4,110,117,108,108,161,171,236,222,251,5,18,1,161,171,236,222,251,5,19,1,161,171,236,222,251,5,20,1,193,204,195,206,156,1,129,6,171,236,222,251,5,21,5,198,171,236,222,251,5,34,171,236,222,251,5,21,10,102,111,110,116,95,99,111,108,111,114,4,110,117,108,108,161,171,236,222,251,5,27,1,161,171,236,222,251,5,28,1,161,171,236,222,251,5,29,1,198,204,195,206,156,1,129,6,171,236,222,251,5,30,10,102,111,110,116,95,99,111,108,111,114,12,34,48,120,102,102,100,98,51,54,51,54,34,193,171,236,222,251,5,39,171,236,222,251,5,30,4,198,171,236,222,251,5,43,171,236,222,251,5,30,10,102,111,110,116,95,99,111,108,111,114,4,110,117,108,108,161,171,236,222,251,5,36,1,161,171,236,222,251,5,37,1,161,171,236,222,251,5,38,1,193,171,236,222,251,5,39,171,236,222,251,5,40,5,198,171,236,222,251,5,52,171,236,222,251,5,40,8,98,103,95,99,111,108,111,114,4,110,117,108,108,161,171,236,222,251,5,45,1,161,171,236,222,251,5,46,1,161,171,236,222,251,5,47,1,193,171,236,222,251,5,39,171,236,222,251,5,48,5,198,171,236,222,251,5,61,171,236,222,251,5,48,8,98,103,95,99,111,108,111,114,4,110,117,108,108,161,171,236,222,251,5,54,1,161,171,236,222,251,5,55,1,161,171,236,222,251,5,56,1,198,171,236,222,251,5,39,171,236,222,251,5,57,8,98,103,95,99,111,108,111,114,12,34,48,120,102,102,102,102,100,97,101,54,34,196,171,236,222,251,5,66,171,236,222,251,5,57,4,110,101,120,116,198,171,236,222,251,5,70,171,236,222,251,5,57,8,98,103,95,99,111,108,111,114,4,110,117,108,108,161,171,236,222,251,5,63,1,161,171,236,222,251,5,64,1,161,171,236,222,251,5,65,1,198,204,195,206,156,1,180,6,204,195,206,156,1,181,6,9,117,110,100,101,114,108,105,110,101,4,116,114,117,101,193,171,236,222,251,5,75,204,195,206,156,1,181,6,3,198,171,236,222,251,5,78,204,195,206,156,1,181,6,9,117,110,100,101,114,108,105,110,101,4,110,117,108,108,161,171,236,222,251,5,72,1,161,171,236,222,251,5,73,1,161,171,236,222,251,5,74,1,198,171,236,222,251,5,75,171,236,222,251,5,76,13,115,116,114,105,107,101,116,104,114,111,117,103,104,4,116,114,117,101,193,171,236,222,251,5,83,171,236,222,251,5,76,3,198,171,236,222,251,5,86,171,236,222,251,5,76,13,115,116,114,105,107,101,116,104,114,111,117,103,104,4,110,117,108,108,161,171,236,222,251,5,80,1,161,171,236,222,251,5,81,1,161,171,236,222,251,5,82,1,198,171,236,222,251,5,83,171,236,222,251,5,84,6,105,116,97,108,105,99,4,116,114,117,101,193,171,236,222,251,5,91,171,236,222,251,5,84,3,198,171,236,222,251,5,94,171,236,222,251,5,84,6,105,116,97,108,105,99,4,110,117,108,108,161,171,236,222,251,5,88,1,161,171,236,222,251,5,89,1,161,171,236,222,251,5,90,1,196,171,236,222,251,5,94,171,236,222,251,5,95,9,230,140,168,233,161,191,230,137,147,161,171,236,222,251,5,96,1,161,171,236,222,251,5,97,1,161,171,236,222,251,5,98,1,39,0,204,195,206,156,1,4,6,68,89,98,118,73,66,2,33,0,204,195,206,156,1,1,6,109,57,74,107,49,75,1,0,7,33,0,204,195,206,156,1,3,6,78,76,50,70,103,95,1,193,204,195,206,156,1,238,1,204,195,206,156,1,239,1,1,168,171,236,222,251,5,102,1,119,204,5,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,67,108,105,99,107,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,99,111,100,101,34,58,116,114,117,101,125,44,34,105,110,115,101,114,116,34,58,34,43,34,125,44,123,34,105,110,115,101,114,116,34,58,34,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,98,103,95,99,111,108,111,114,34,58,34,48,120,102,102,102,102,100,97,101,54,34,44,34,102,111,110,116,95,99,111,108,111,114,34,58,34,48,120,102,102,100,98,51,54,51,54,34,125,44,34,105,110,115,101,114,116,34,58,34,110,101,120,116,34,125,44,123,34,105,110,115,101,114,116,34,58,34,32,116,111,32,97,110,121,32,112,97,103,101,32,116,105,116,108,101,32,105,110,32,116,104,101,32,115,105,100,101,98,97,114,32,116,111,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,102,111,110,116,95,99,111,108,111,114,34,58,34,48,120,102,102,56,52,50,55,101,48,34,125,44,34,105,110,115,101,114,116,34,58,34,113,117,105,99,107,108,121,34,125,44,123,34,105,110,115,101,114,116,34,58,34,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,105,116,97,108,105,99,34,58,116,114,117,101,44,34,115,116,114,105,107,101,116,104,114,111,117,103,104,34,58,116,114,117,101,44,34,117,110,100,101,114,108,105,110,101,34,58,116,114,117,101,125,44,34,105,110,115,101,114,116,34,58,34,230,140,168,233,161,191,230,137,147,34,125,44,123,34,105,110,115,101,114,116,34,58,34,32,97,32,110,101,119,32,115,117,98,112,97,103,101,44,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,99,111,100,101,34,58,116,114,117,101,125,44,34,105,110,115,101,114,116,34,58,34,68,111,99,117,109,101,110,116,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,99,111,100,101,34,58,102,97,108,115,101,125,44,34,105,110,115,101,114,116,34,58,34,44,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,99,111,100,101,34,58,116,114,117,101,125,44,34,105,110,115,101,114,116,34,58,34,71,114,105,100,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,99,111,100,101,34,58,102,97,108,115,101,125,44,34,105,110,115,101,114,116,34,58,34,44,32,111,114,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,99,111,100,101,34,58,116,114,117,101,125,44,34,105,110,115,101,114,116,34,58,34,75,97,110,98,97,110,32,66,111,97,114,100,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,99,111,100,101,34,58,102,97,108,115,101,125,44,34,105,110,115,101,114,116,34,58,34,46,34,125,93,44,34,99,104,101,99,107,101,100,34,58,102,97,108,115,101,125,168,171,236,222,251,5,103,1,119,10,98,113,76,109,98,57,111,45,109,109,168,171,236,222,251,5,104,1,119,4,116,101,120,116,39,0,204,195,206,156,1,4,6,68,53,45,65,82,65,2,33,0,204,195,206,156,1,1,6,89,87,119,55,87,53,1,0,7,33,0,204,195,206,156,1,3,6,85,68,79,77,112,98,1,1,0,204,195,206,156,1,14,1,39,0,204,195,206,156,1,4,6,107,90,52,97,119,69,2,33,0,204,195,206,156,1,1,6,108,79,120,55,95,83,1,0,7,33,0,204,195,206,156,1,3,6,50,109,115,116,117,104,1,193,171,236,222,251,5,115,204,195,206,156,1,239,1,1,39,0,204,195,206,156,1,4,6,52,98,104,66,88,113,2,33,0,204,195,206,156,1,1,6,121,105,115,116,115,72,1,0,7,33,0,204,195,206,156,1,3,6,53,67,71,119,50,122,1,129,171,236,222,251,5,129,1,1,39,0,204,195,206,156,1,4,6,87,105,113,49,48,95,2,33,0,204,195,206,156,1,1,6,71,121,98,79,49,81,1,0,7,33,0,204,195,206,156,1,3,6,66,90,69,117,90,106,1,193,171,236,222,251,5,129,1,171,236,222,251,5,151,1,1,39,0,204,195,206,156,1,4,6,106,101,65,53,90,85,2,39,0,204,195,206,156,1,1,6,102,67,65,65,81,117,1,40,0,171,236,222,251,5,164,1,2,105,100,1,119,6,102,67,65,65,81,117,40,0,171,236,222,251,5,164,1,2,116,121,1,119,13,110,117,109,98,101,114,101,100,95,108,105,115,116,40,0,171,236,222,251,5,164,1,6,112,97,114,101,110,116,1,119,10,77,48,104,84,99,67,120,66,88,82,40,0,171,236,222,251,5,164,1,8,99,104,105,108,100,114,101,110,1,119,6,52,74,88,112,120,108,33,0,171,236,222,251,5,164,1,4,100,97,116,97,1,40,0,171,236,222,251,5,164,1,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,171,236,222,251,5,164,1,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,52,74,88,112,120,108,0,200,171,236,222,251,5,129,1,171,236,222,251,5,162,1,1,119,6,102,67,65,65,81,117,4,0,171,236,222,251,5,163,1,6,228,189,147,233,170,140,129,171,236,222,251,5,175,1,1,161,171,236,222,251,5,169,1,1,198,171,236,222,251,5,175,1,171,236,222,251,5,176,1,9,117,110,100,101,114,108,105,110,101,4,116,114,117,101,193,171,236,222,251,5,178,1,171,236,222,251,5,176,1,1,198,171,236,222,251,5,179,1,171,236,222,251,5,176,1,9,117,110,100,101,114,108,105,110,101,4,110,117,108,108,161,171,236,222,251,5,177,1,1,198,171,236,222,251,5,178,1,171,236,222,251,5,179,1,13,115,116,114,105,107,101,116,104,114,111,117,103,104,4,116,114,117,101,196,171,236,222,251,5,182,1,171,236,222,251,5,179,1,3,228,184,128,198,171,236,222,251,5,183,1,171,236,222,251,5,179,1,13,115,116,114,105,107,101,116,104,114,111,117,103,104,4,110,117,108,108,168,171,236,222,251,5,181,1,1,119,101,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,228,189,147,233,170,140,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,115,116,114,105,107,101,116,104,114,111,117,103,104,34,58,116,114,117,101,44,34,117,110,100,101,114,108,105,110,101,34,58,116,114,117,101,125,44,34,105,110,115,101,114,116,34,58,34,228,184,128,34,125,93,125,39,0,204,195,206,156,1,4,6,82,74,108,100,72,67,2,33,0,204,195,206,156,1,1,6,53,106,100,78,87,117,1,0,7,33,0,204,195,206,156,1,3,6,67,106,107,76,57,108,1,129,171,236,222,251,5,151,1,1,4,0,171,236,222,251,5,186,1,4,103,104,104,104,0,1,39,0,204,195,206,156,1,4,6,70,117,117,52,113,66,2,4,0,171,236,222,251,5,202,1,4,103,104,104,104,33,0,204,195,206,156,1,1,6,121,112,70,119,50,69,1,0,7,33,0,204,195,206,156,1,3,6,71,77,54,72,55,119,1,193,171,236,222,251,5,151,1,171,236,222,251,5,196,1,1,39,0,204,195,206,156,1,4,6,118,113,76,104,110,70,2,4,0,171,236,222,251,5,217,1,4,103,104,104,104,33,0,204,195,206,156,1,1,6,73,57,73,75,116,115,1,0,7,33,0,204,195,206,156,1,3,6,77,114,102,81,106,87,1,193,171,236,222,251,5,140,1,204,195,206,156,1,239,1,1,39,0,204,195,206,156,1,4,6,89,50,52,104,77,55,2,4,0,171,236,222,251,5,232,1,4,103,104,104,104,33,0,204,195,206,156,1,1,6,107,110,74,87,104,48,1,0,7,33,0,204,195,206,156,1,3,6,55,99,67,68,120,77,1,129,171,236,222,251,5,196,1,1,0,7,39,0,204,195,206,156,1,4,6,109,98,56,104,95,45,2,4,0,171,236,222,251,5,254,1,4,103,104,104,104,33,0,204,195,206,156,1,1,6,103,75,73,116,90,101,1,0,7,33,0,204,195,206,156,1,3,6,118,115,54,50,82,105,1,193,171,236,222,251,5,196,1,171,236,222,251,5,246,1,1,39,0,204,195,206,156,1,4,6,105,67,98,56,102,56,2,4,0,171,236,222,251,5,141,2,4,103,104,104,104,33,0,204,195,206,156,1,1,6,88,113,72,53,122,82,1,0,7,33,0,204,195,206,156,1,3,6,73,111,48,108,119,67,1,193,171,236,222,251,5,196,1,171,236,222,251,5,140,2,1,39,0,204,195,206,156,1,4,6,82,89,116,67,111,86,2,4,0,171,236,222,251,5,156,2,4,103,104,104,104,39,0,204,195,206,156,1,1,6,49,120,78,111,50,76,1,40,0,171,236,222,251,5,161,2,2,105,100,1,119,6,49,120,78,111,50,76,40,0,171,236,222,251,5,161,2,2,116,121,1,119,5,113,117,111,116,101,40,0,171,236,222,251,5,161,2,6,112,97,114,101,110,116,1,119,10,77,48,104,84,99,67,120,66,88,82,40,0,171,236,222,251,5,161,2,8,99,104,105,108,100,114,101,110,1,119,6,67,85,68,115,45,70,33,0,171,236,222,251,5,161,2,4,100,97,116,97,1,40,0,171,236,222,251,5,161,2,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,171,236,222,251,5,161,2,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,67,85,68,115,45,70,0,200,171,236,222,251,5,196,1,171,236,222,251,5,155,2,1,119,6,49,120,78,111,50,76,39,0,204,195,206,156,1,4,6,122,112,90,112,49,101,2,33,0,204,195,206,156,1,1,6,109,65,112,48,84,75,1,0,7,33,0,204,195,206,156,1,3,6,117,95,113,85,121,95,1,129,171,236,222,251,5,246,1,1,4,0,171,236,222,251,5,171,2,4,104,106,106,106,0,1,39,0,204,195,206,156,1,4,6,79,88,120,110,65,84,2,4,0,171,236,222,251,5,187,2,4,104,106,106,106,33,0,204,195,206,156,1,1,6,109,70,99,75,110,81,1,0,7,33,0,204,195,206,156,1,3,6,72,52,122,104,56,95,1,193,171,236,222,251,5,246,1,171,236,222,251,5,181,2,1,39,0,204,195,206,156,1,4,6,90,97,54,99,87,113,2,4,0,171,236,222,251,5,202,2,4,104,106,106,106,33,0,204,195,206,156,1,1,6,50,110,113,71,98,75,1,0,7,33,0,204,195,206,156,1,3,6,95,69,69,76,53,109,1,193,171,236,222,251,5,246,1,171,236,222,251,5,201,2,1,39,0,204,195,206,156,1,4,6,95,88,107,52,56,116,2,4,0,171,236,222,251,5,217,2,4,104,106,106,106,33,0,204,195,206,156,1,1,6,68,99,97,85,109,79,1,0,7,33,0,204,195,206,156,1,3,6,120,69,54,111,108,48,1,193,171,236,222,251,5,231,1,204,195,206,156,1,239,1,1,39,0,204,195,206,156,1,4,6,82,106,87,98,56,111,2,4,0,171,236,222,251,5,232,2,4,104,106,106,106,33,0,204,195,206,156,1,1,6,87,98,82,107,87,69,1,0,7,33,0,204,195,206,156,1,3,6,89,103,114,112,81,55,1,129,171,236,222,251,5,181,2,1,39,0,204,195,206,156,1,4,6,112,107,78,57,112,83,2,4,0,171,236,222,251,5,247,2,4,104,106,106,106,33,0,204,195,206,156,1,1,6,79,98,74,118,76,57,1,0,7,33,0,204,195,206,156,1,3,6,97,79,100,49,121,82,1,193,171,236,222,251,5,181,2,171,236,222,251,5,246,2,1,39,0,204,195,206,156,1,4,6,99,51,100,115,103,56,2,4,0,171,236,222,251,5,134,3,4,104,106,106,106,33,0,204,195,206,156,1,1,6,109,119,79,85,85,87,1,0,7,33,0,204,195,206,156,1,3,6,98,70,74,53,121,122,1,193,171,236,222,251,5,181,2,171,236,222,251,5,133,3,1,39,0,204,195,206,156,1,4,6,104,82,53,106,71,79,2,1,0,171,236,222,251,5,149,3,4,33,0,204,195,206,156,1,1,6,73,77,51,71,72,50,1,0,7,33,0,204,195,206,156,1,3,6,119,85,111,112,97,102,1,193,171,236,222,251,5,181,2,171,236,222,251,5,148,3,1,0,4,39,0,204,195,206,156,1,4,6,74,66,108,114,84,51,2,33,0,204,195,206,156,1,1,6,76,105,86,56,56,104,1,0,7,33,0,204,195,206,156,1,3,6,112,106,107,107,53,97,1,193,171,236,222,251,5,181,2,171,236,222,251,5,163,3,1,1,0,171,236,222,251,5,168,3,1,0,2,129,171,236,222,251,5,179,3,1,0,1,196,171,236,222,251,5,179,3,171,236,222,251,5,182,3,3,226,128,148,0,1,39,0,204,195,206,156,1,4,6,89,108,67,77,99,111,2,39,0,204,195,206,156,1,1,6,112,69,80,105,117,115,1,40,0,171,236,222,251,5,187,3,2,105,100,1,119,6,112,69,80,105,117,115,40,0,171,236,222,251,5,187,3,2,116,121,1,119,7,100,105,118,105,100,101,114,40,0,171,236,222,251,5,187,3,6,112,97,114,101,110,116,1,119,10,77,48,104,84,99,67,120,66,88,82,40,0,171,236,222,251,5,187,3,8,99,104,105,108,100,114,101,110,1,119,6,49,45,49,107,111,85,40,0,171,236,222,251,5,187,3,4,100,97,116,97,1,119,2,123,125,40,0,171,236,222,251,5,187,3,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,171,236,222,251,5,187,3,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,49,45,49,107,111,85,0,200,171,236,222,251,5,181,2,171,236,222,251,5,178,3,1,119,6,112,69,80,105,117,115,39,0,204,195,206,156,1,1,6,95,65,78,99,110,51,1,40,0,171,236,222,251,5,197,3,2,105,100,1,119,6,95,65,78,99,110,51,40,0,171,236,222,251,5,197,3,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,171,236,222,251,5,197,3,6,112,97,114,101,110,116,1,119,10,77,48,104,84,99,67,120,66,88,82,40,0,171,236,222,251,5,197,3,8,99,104,105,108,100,114,101,110,1,119,6,84,45,117,87,109,83,33,0,171,236,222,251,5,197,3,4,100,97,116,97,1,40,0,171,236,222,251,5,197,3,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,171,236,222,251,5,197,3,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,84,45,117,87,109,83,0,136,171,236,222,251,5,246,2,1,119,6,95,65,78,99,110,51,4,0,171,236,222,251,5,186,3,1,54,161,171,236,222,251,5,202,3,1,132,171,236,222,251,5,207,3,1,54,161,171,236,222,251,5,208,3,1,132,171,236,222,251,5,209,3,1,54,161,171,236,222,251,5,210,3,1,132,171,236,222,251,5,211,3,1,57,168,171,236,222,251,5,212,3,1,119,29,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,54,54,54,57,34,125,93,125,39,0,204,195,206,156,1,1,6,79,77,79,95,52,106,1,40,0,171,236,222,251,5,215,3,2,105,100,1,119,6,79,77,79,95,52,106,40,0,171,236,222,251,5,215,3,2,116,121,1,119,7,100,105,118,105,100,101,114,40,0,171,236,222,251,5,215,3,6,112,97,114,101,110,116,1,119,10,77,48,104,84,99,67,120,66,88,82,40,0,171,236,222,251,5,215,3,8,99,104,105,108,100,114,101,110,1,119,6,99,107,78,65,119,105,40,0,171,236,222,251,5,215,3,4,100,97,116,97,1,119,2,123,125,40,0,171,236,222,251,5,215,3,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,171,236,222,251,5,215,3,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,99,107,78,65,119,105,0,200,171,236,222,251,5,246,2,171,236,222,251,5,206,3,1,119,6,79,77,79,95,52,106,39,0,204,195,206,156,1,4,6,45,80,118,95,90,87,2,4,0,171,236,222,251,5,225,3,4,103,104,104,104,39,0,204,195,206,156,1,4,6,87,105,54,69,77,70,2,4,0,171,236,222,251,5,230,3,4,54,54,54,57,39,0,204,195,206,156,1,1,6,122,78,116,118,66,84,1,40,0,171,236,222,251,5,235,3,2,105,100,1,119,6,122,78,116,118,66,84,40,0,171,236,222,251,5,235,3,2,116,121,1,119,5,113,117,111,116,101,40,0,171,236,222,251,5,235,3,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,171,236,222,251,5,235,3,8,99,104,105,108,100,114,101,110,1,119,6,105,85,75,111,98,79,33,0,171,236,222,251,5,235,3,4,100,97,116,97,1,40,0,171,236,222,251,5,235,3,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,171,236,222,251,5,235,3,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,105,85,75,111,98,79,0,200,171,236,222,251,5,231,2,204,195,206,156,1,239,1,1,119,6,122,78,116,118,66,84,33,0,204,195,206,156,1,1,6,57,104,76,90,119,104,1,0,7,33,0,204,195,206,156,1,3,6,120,113,118,100,85,73,1,193,171,236,222,251,5,244,3,204,195,206,156,1,239,1,1,39,0,204,195,206,156,1,4,6,75,122,88,45,65,53,2,1,0,171,236,222,251,5,255,3,4,39,0,204,195,206,156,1,1,6,49,51,100,105,49,69,1,40,0,171,236,222,251,5,132,4,2,105,100,1,119,6,49,51,100,105,49,69,40,0,171,236,222,251,5,132,4,2,116,121,1,119,9,116,111,100,111,95,108,105,115,116,40,0,171,236,222,251,5,132,4,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,171,236,222,251,5,132,4,8,99,104,105,108,100,114,101,110,1,119,6,106,54,115,52,103,109,33,0,171,236,222,251,5,132,4,4,100,97,116,97,1,40,0,171,236,222,251,5,132,4,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,171,236,222,251,5,132,4,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,106,54,115,52,103,109,0,200,171,236,222,251,5,244,3,171,236,222,251,5,254,3,1,119,6,49,51,100,105,49,69,161,171,236,222,251,5,137,4,2,65,171,236,222,251,5,128,4,5,198,171,236,222,251,5,148,4,171,236,222,251,5,128,4,8,98,103,95,99,111,108,111,114,4,110,117,108,108,161,171,236,222,251,5,143,4,1,65,171,236,222,251,5,144,4,5,198,171,236,222,251,5,155,4,171,236,222,251,5,144,4,8,98,103,95,99,111,108,111,114,4,110,117,108,108,161,171,236,222,251,5,150,4,1,65,171,236,222,251,5,151,4,5,198,171,236,222,251,5,162,4,171,236,222,251,5,151,4,8,98,103,95,99,111,108,111,114,4,110,117,108,108,161,171,236,222,251,5,157,4,1,65,171,236,222,251,5,158,4,5,198,171,236,222,251,5,169,4,171,236,222,251,5,158,4,8,98,103,95,99,111,108,111,114,4,110,117,108,108,161,171,236,222,251,5,164,4,1,65,171,236,222,251,5,165,4,5,198,171,236,222,251,5,176,4,171,236,222,251,5,165,4,8,98,103,95,99,111,108,111,114,4,110,117,108,108,161,171,236,222,251,5,171,4,1,65,171,236,222,251,5,172,4,5,198,171,236,222,251,5,183,4,171,236,222,251,5,172,4,8,98,103,95,99,111,108,111,114,4,110,117,108,108,161,171,236,222,251,5,178,4,1,65,171,236,222,251,5,179,4,5,198,171,236,222,251,5,190,4,171,236,222,251,5,179,4,8,98,103,95,99,111,108,111,114,4,110,117,108,108,161,171,236,222,251,5,185,4,1,65,171,236,222,251,5,186,4,5,198,171,236,222,251,5,197,4,171,236,222,251,5,186,4,8,98,103,95,99,111,108,111,114,4,110,117,108,108,161,171,236,222,251,5,192,4,1,70,171,236,222,251,5,193,4,8,98,103,95,99,111,108,111,114,12,34,48,120,102,102,102,102,100,97,101,54,34,193,171,236,222,251,5,200,4,171,236,222,251,5,193,4,4,198,171,236,222,251,5,204,4,171,236,222,251,5,193,4,8,98,103,95,99,111,108,111,114,4,110,117,108,108,161,171,236,222,251,5,199,4,2,193,171,236,222,251,5,200,4,171,236,222,251,5,201,4,5,198,171,236,222,251,5,212,4,171,236,222,251,5,201,4,10,102,111,110,116,95,99,111,108,111,114,4,110,117,108,108,161,171,236,222,251,5,207,4,1,193,171,236,222,251,5,200,4,171,236,222,251,5,208,4,5,198,171,236,222,251,5,219,4,171,236,222,251,5,208,4,10,102,111,110,116,95,99,111,108,111,114,4,110,117,108,108,161,171,236,222,251,5,214,4,1,193,171,236,222,251,5,200,4,171,236,222,251,5,215,4,5,198,171,236,222,251,5,226,4,171,236,222,251,5,215,4,10,102,111,110,116,95,99,111,108,111,114,4,110,117,108,108,161,171,236,222,251,5,221,4,1,193,171,236,222,251,5,200,4,171,236,222,251,5,222,4,5,198,171,236,222,251,5,233,4,171,236,222,251,5,222,4,10,102,111,110,116,95,99,111,108,111,114,4,110,117,108,108,161,171,236,222,251,5,228,4,1,193,171,236,222,251,5,200,4,171,236,222,251,5,229,4,5,198,171,236,222,251,5,240,4,171,236,222,251,5,229,4,10,102,111,110,116,95,99,111,108,111,114,4,110,117,108,108,161,171,236,222,251,5,235,4,1,70,171,236,222,251,5,200,4,10,102,111,110,116,95,99,111,108,111,114,12,34,48,120,102,102,101,97,56,102,48,54,34,198,171,236,222,251,5,243,4,171,236,222,251,5,200,4,8,98,103,95,99,111,108,111,114,12,34,48,120,102,102,97,55,100,102,52,97,34,196,171,236,222,251,5,244,4,171,236,222,251,5,200,4,4,54,54,54,57,193,171,236,222,251,5,248,4,171,236,222,251,5,200,4,1,198,171,236,222,251,5,249,4,171,236,222,251,5,200,4,10,102,111,110,116,95,99,111,108,111,114,4,110,117,108,108,168,171,236,222,251,5,242,4,1,119,110,123,34,99,104,101,99,107,101,100,34,58,102,97,108,115,101,44,34,100,101,108,116,97,34,58,91,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,98,103,95,99,111,108,111,114,34,58,34,48,120,102,102,97,55,100,102,52,97,34,44,34,102,111,110,116,95,99,111,108,111,114,34,58,34,48,120,102,102,101,97,56,102,48,54,34,125,44,34,105,110,115,101,114,116,34,58,34,54,54,54,57,34,125,93,125,7,226,167,254,250,5,0,39,0,204,195,206,156,1,4,6,72,97,87,55,95,104,2,4,0,226,167,254,250,5,0,13,229,144,140,228,184,128,228,184,170,106,106,106,56,161,198,223,206,159,1,124,1,132,226,167,254,250,5,7,1,56,161,226,167,254,250,5,8,1,132,226,167,254,250,5,9,1,56,161,226,167,254,250,5,10,1,1,161,234,157,145,5,0,161,247,212,219,208,10,45,7,3,177,239,218,225,4,0,161,207,231,154,196,9,0,1,161,207,231,154,196,9,1,1,161,207,231,154,196,9,2,1,1,136,172,186,168,4,0,161,176,238,158,139,14,174,2,182,6,24,141,151,160,163,4,0,161,177,239,218,225,4,0,1,161,177,239,218,225,4,1,1,161,177,239,218,225,4,2,1,161,141,151,160,163,4,0,1,161,141,151,160,163,4,1,1,161,141,151,160,163,4,2,1,161,141,151,160,163,4,3,1,161,141,151,160,163,4,4,1,161,141,151,160,163,4,5,1,161,141,151,160,163,4,6,1,161,141,151,160,163,4,7,1,161,141,151,160,163,4,8,1,161,141,151,160,163,4,9,1,161,141,151,160,163,4,10,1,161,141,151,160,163,4,11,1,161,141,151,160,163,4,12,1,161,141,151,160,163,4,13,1,161,141,151,160,163,4,14,1,161,141,151,160,163,4,15,1,161,141,151,160,163,4,16,1,161,141,151,160,163,4,17,1,161,141,151,160,163,4,18,1,161,141,151,160,163,4,19,1,161,141,151,160,163,4,20,1,4,217,168,198,159,4,0,129,204,195,206,156,1,154,7,4,161,204,195,206,156,1,227,1,1,161,204,195,206,156,1,228,1,1,161,204,195,206,156,1,229,1,1,51,220,225,223,240,3,0,1,0,204,195,206,156,1,132,4,1,161,204,195,206,156,1,83,1,161,204,195,206,156,1,84,1,161,204,195,206,156,1,85,1,134,220,225,223,240,3,0,7,109,101,110,116,105,111,110,64,123,34,112,97,103,101,95,105,100,34,58,34,100,48,52,57,54,51,50,52,45,53,53,55,48,45,52,48,48,54,45,98,52,101,97,45,100,98,55,53,49,54,100,50,49,50,102,100,34,44,34,116,121,112,101,34,58,34,112,97,103,101,34,125,132,220,225,223,240,3,4,1,36,134,220,225,223,240,3,5,7,109,101,110,116,105,111,110,4,110,117,108,108,161,220,225,223,240,3,1,1,161,220,225,223,240,3,2,1,161,220,225,223,240,3,3,1,39,0,204,195,206,156,1,4,6,50,108,103,80,119,50,2,33,0,204,195,206,156,1,1,6,115,120,112,66,109,100,1,0,7,33,0,204,195,206,156,1,3,6,52,97,66,55,99,78,1,193,204,195,206,156,1,250,1,204,195,206,156,1,251,1,1,1,0,220,225,223,240,3,10,1,0,2,129,220,225,223,240,3,21,1,0,2,161,243,138,171,183,10,249,1,1,161,243,138,171,183,10,250,1,1,161,243,138,171,183,10,251,1,1,161,220,225,223,240,3,27,1,161,220,225,223,240,3,28,1,161,220,225,223,240,3,29,1,161,194,228,144,71,7,2,39,0,204,195,206,156,1,4,6,48,102,55,71,122,95,2,39,0,204,195,206,156,1,1,6,54,118,84,69,79,115,1,40,0,220,225,223,240,3,36,2,105,100,1,119,6,54,118,84,69,79,115,40,0,220,225,223,240,3,36,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,220,225,223,240,3,36,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,220,225,223,240,3,36,8,99,104,105,108,100,114,101,110,1,119,6,106,107,73,81,115,57,33,0,220,225,223,240,3,36,4,100,97,116,97,1,40,0,220,225,223,240,3,36,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,220,225,223,240,3,36,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,106,107,73,81,115,57,0,200,220,225,223,240,3,20,204,195,206,156,1,251,1,1,119,6,54,118,84,69,79,115,1,0,220,225,223,240,3,35,1,161,220,225,223,240,3,41,1,134,220,225,223,240,3,46,7,109,101,110,116,105,111,110,64,123,34,112,97,103,101,95,105,100,34,58,34,52,52,51,53,101,53,55,98,45,99,50,54,51,45,52,101,55,102,45,97,52,51,53,45,50,48,56,55,57,97,54,50,101,54,100,97,34,44,34,116,121,112,101,34,58,34,112,97,103,101,34,125,132,220,225,223,240,3,48,1,36,134,220,225,223,240,3,49,7,109,101,110,116,105,111,110,4,110,117,108,108,161,220,225,223,240,3,47,1,39,0,204,195,206,156,1,4,6,111,89,48,54,98,73,2,161,220,225,223,240,3,51,1,1,0,220,225,223,240,3,52,1,161,220,225,223,240,3,53,1,134,220,225,223,240,3,54,7,109,101,110,116,105,111,110,64,123,34,112,97,103,101,95,105,100,34,58,34,100,100,98,57,51,98,97,55,45,48,54,99,55,45,52,49,55,54,45,57,56,50,97,45,100,55,52,50,51,101,48,57,98,52,52,49,34,44,34,116,121,112,101,34,58,34,112,97,103,101,34,125,132,220,225,223,240,3,56,1,36,134,220,225,223,240,3,57,7,109,101,110,116,105,111,110,4,110,117,108,108,161,220,225,223,240,3,55,1,29,133,181,204,218,3,0,39,0,204,195,206,156,1,4,6,118,122,72,105,108,97,2,6,0,133,181,204,218,3,0,4,104,114,101,102,13,34,49,57,50,46,49,54,56,46,49,46,50,34,132,133,181,204,218,3,1,9,99,111,110,116,101,110,116,32,49,134,133,181,204,218,3,10,4,104,114,101,102,4,110,117,108,108,132,133,181,204,218,3,11,3,32,50,32,161,238,153,239,204,9,48,1,132,133,181,204,218,3,14,1,97,161,133,181,204,218,3,15,1,129,133,181,204,218,3,16,1,132,133,181,204,218,3,18,1,112,161,133,181,204,218,3,17,1,196,133,181,204,218,3,16,133,181,204,218,3,18,1,112,161,133,181,204,218,3,20,1,132,133,181,204,218,3,19,1,102,161,133,181,204,218,3,22,1,132,133,181,204,218,3,23,1,108,161,133,181,204,218,3,24,1,132,133,181,204,218,3,25,1,111,161,133,181,204,218,3,26,1,132,133,181,204,218,3,27,1,119,161,133,181,204,218,3,28,1,132,133,181,204,218,3,29,1,121,168,133,181,204,218,3,30,1,119,95,123,34,100,101,108,116,97,34,58,91,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,104,114,101,102,34,58,34,49,57,50,46,49,54,56,46,49,46,50,34,125,44,34,105,110,115,101,114,116,34,58,34,99,111,110,116,101,110,116,32,49,34,125,44,123,34,105,110,115,101,114,116,34,58,34,32,50,32,97,112,112,102,108,111,119,121,34,125,93,125,39,0,204,195,206,156,1,4,6,68,50,72,75,72,71,2,6,0,133,181,204,218,3,33,4,104,114,101,102,22,34,97,112,112,102,108,111,119,121,46,105,111,47,100,111,119,110,108,111,97,100,34,132,133,181,204,218,3,34,11,97,112,112,102,108,111,119,121,46,105,111,134,133,181,204,218,3,45,4,104,114,101,102,4,110,117,108,108,132,133,181,204,218,3,46,2,32,49,168,238,153,239,204,9,32,1,119,97,123,34,100,101,108,116,97,34,58,91,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,104,114,101,102,34,58,34,97,112,112,102,108,111,119,121,46,105,111,47,100,111,119,110,108,111,97,100,34,125,44,34,105,110,115,101,114,116,34,58,34,97,112,112,102,108,111,119,121,46,105,111,34,125,44,123,34,105,110,115,101,114,116,34,58,34,32,49,34,125,93,125,1,236,253,128,205,3,0,161,240,179,157,219,7,3,9,215,6,150,216,171,142,3,0,161,199,130,209,189,2,177,3,6,161,164,202,219,213,10,80,1,161,164,202,219,213,10,81,1,161,164,202,219,213,10,82,1,161,150,216,171,142,3,6,1,161,150,216,171,142,3,7,1,161,150,216,171,142,3,8,1,129,204,195,206,156,1,207,5,1,161,150,216,171,142,3,9,1,161,150,216,171,142,3,10,1,161,150,216,171,142,3,11,1,129,150,216,171,142,3,12,1,161,150,216,171,142,3,13,1,161,150,216,171,142,3,14,1,161,150,216,171,142,3,15,1,161,150,216,171,142,3,17,1,161,150,216,171,142,3,18,1,161,150,216,171,142,3,19,1,161,150,216,171,142,3,20,1,161,150,216,171,142,3,21,1,161,150,216,171,142,3,22,1,129,150,216,171,142,3,16,1,161,150,216,171,142,3,23,1,161,150,216,171,142,3,24,1,161,150,216,171,142,3,25,1,129,150,216,171,142,3,26,1,161,150,216,171,142,3,27,1,161,150,216,171,142,3,28,1,161,150,216,171,142,3,29,1,129,150,216,171,142,3,30,1,161,150,216,171,142,3,31,1,161,150,216,171,142,3,32,1,161,150,216,171,142,3,33,1,129,150,216,171,142,3,34,1,161,150,216,171,142,3,35,1,161,150,216,171,142,3,36,1,161,150,216,171,142,3,37,1,129,150,216,171,142,3,38,1,161,150,216,171,142,3,39,1,161,150,216,171,142,3,40,1,161,150,216,171,142,3,41,1,129,150,216,171,142,3,42,1,161,150,216,171,142,3,43,1,161,150,216,171,142,3,44,1,161,150,216,171,142,3,45,1,129,150,216,171,142,3,46,1,161,150,216,171,142,3,47,1,161,150,216,171,142,3,48,1,161,150,216,171,142,3,49,1,129,150,216,171,142,3,50,1,161,150,216,171,142,3,51,1,161,150,216,171,142,3,52,1,161,150,216,171,142,3,53,1,129,150,216,171,142,3,54,1,161,150,216,171,142,3,55,1,161,150,216,171,142,3,56,1,161,150,216,171,142,3,57,1,129,150,216,171,142,3,58,1,161,150,216,171,142,3,59,1,161,150,216,171,142,3,60,1,161,150,216,171,142,3,61,1,129,150,216,171,142,3,62,1,161,150,216,171,142,3,63,1,161,150,216,171,142,3,64,1,161,150,216,171,142,3,65,1,129,150,216,171,142,3,66,1,161,150,216,171,142,3,67,1,161,150,216,171,142,3,68,1,161,150,216,171,142,3,69,1,129,150,216,171,142,3,70,1,161,150,216,171,142,3,71,1,161,150,216,171,142,3,72,1,161,150,216,171,142,3,73,1,129,150,216,171,142,3,74,1,161,150,216,171,142,3,75,1,161,150,216,171,142,3,76,1,161,150,216,171,142,3,77,1,129,150,216,171,142,3,78,1,161,150,216,171,142,3,79,1,161,150,216,171,142,3,80,1,161,150,216,171,142,3,81,1,129,150,216,171,142,3,82,1,161,150,216,171,142,3,83,1,161,150,216,171,142,3,84,1,161,150,216,171,142,3,85,1,129,150,216,171,142,3,86,1,161,150,216,171,142,3,87,1,161,150,216,171,142,3,88,1,161,150,216,171,142,3,89,1,129,150,216,171,142,3,90,1,161,150,216,171,142,3,91,1,161,150,216,171,142,3,92,1,161,150,216,171,142,3,93,1,129,150,216,171,142,3,94,1,161,150,216,171,142,3,95,1,161,150,216,171,142,3,96,1,161,150,216,171,142,3,97,1,129,150,216,171,142,3,98,1,161,150,216,171,142,3,99,1,161,150,216,171,142,3,100,1,161,150,216,171,142,3,101,1,129,150,216,171,142,3,102,1,161,150,216,171,142,3,103,1,161,150,216,171,142,3,104,1,161,150,216,171,142,3,105,1,129,150,216,171,142,3,106,1,161,150,216,171,142,3,107,1,161,150,216,171,142,3,108,1,161,150,216,171,142,3,109,1,129,150,216,171,142,3,110,1,161,150,216,171,142,3,111,1,161,150,216,171,142,3,112,1,161,150,216,171,142,3,113,1,129,150,216,171,142,3,114,1,161,150,216,171,142,3,115,1,161,150,216,171,142,3,116,1,161,150,216,171,142,3,117,1,129,150,216,171,142,3,118,1,161,150,216,171,142,3,119,1,161,150,216,171,142,3,120,1,161,150,216,171,142,3,121,1,129,150,216,171,142,3,122,1,161,150,216,171,142,3,123,1,161,150,216,171,142,3,124,1,161,150,216,171,142,3,125,1,129,150,216,171,142,3,126,1,161,150,216,171,142,3,127,1,161,150,216,171,142,3,128,1,1,161,150,216,171,142,3,129,1,1,129,150,216,171,142,3,130,1,1,161,150,216,171,142,3,131,1,1,161,150,216,171,142,3,132,1,1,161,150,216,171,142,3,133,1,1,129,150,216,171,142,3,134,1,1,161,150,216,171,142,3,135,1,1,161,150,216,171,142,3,136,1,1,161,150,216,171,142,3,137,1,1,193,150,216,171,142,3,134,1,150,216,171,142,3,138,1,1,161,150,216,171,142,3,139,1,1,161,150,216,171,142,3,140,1,1,161,150,216,171,142,3,141,1,1,193,150,216,171,142,3,142,1,150,216,171,142,3,138,1,1,161,150,216,171,142,3,143,1,1,161,150,216,171,142,3,144,1,1,161,150,216,171,142,3,145,1,1,193,150,216,171,142,3,146,1,150,216,171,142,3,138,1,1,161,150,216,171,142,3,147,1,1,161,150,216,171,142,3,148,1,1,161,150,216,171,142,3,149,1,1,193,150,216,171,142,3,150,1,150,216,171,142,3,138,1,1,161,150,216,171,142,3,151,1,1,161,150,216,171,142,3,152,1,1,161,150,216,171,142,3,153,1,1,193,150,216,171,142,3,154,1,150,216,171,142,3,138,1,1,161,150,216,171,142,3,155,1,1,161,150,216,171,142,3,156,1,1,161,150,216,171,142,3,157,1,1,193,150,216,171,142,3,158,1,150,216,171,142,3,138,1,1,161,150,216,171,142,3,159,1,1,161,150,216,171,142,3,160,1,1,161,150,216,171,142,3,161,1,1,193,150,216,171,142,3,162,1,150,216,171,142,3,138,1,1,161,150,216,171,142,3,163,1,1,161,150,216,171,142,3,164,1,1,161,150,216,171,142,3,165,1,1,193,150,216,171,142,3,166,1,150,216,171,142,3,138,1,1,161,150,216,171,142,3,167,1,1,161,150,216,171,142,3,168,1,1,161,150,216,171,142,3,169,1,1,193,150,216,171,142,3,170,1,150,216,171,142,3,138,1,1,161,150,216,171,142,3,171,1,1,161,150,216,171,142,3,172,1,1,161,150,216,171,142,3,173,1,1,193,150,216,171,142,3,174,1,150,216,171,142,3,138,1,1,161,150,216,171,142,3,175,1,1,161,150,216,171,142,3,176,1,1,161,150,216,171,142,3,177,1,1,193,150,216,171,142,3,178,1,150,216,171,142,3,138,1,1,161,150,216,171,142,3,179,1,1,161,150,216,171,142,3,180,1,1,161,150,216,171,142,3,181,1,1,193,150,216,171,142,3,182,1,150,216,171,142,3,138,1,1,161,150,216,171,142,3,183,1,1,161,150,216,171,142,3,184,1,1,161,150,216,171,142,3,185,1,1,129,150,216,171,142,3,138,1,4,161,150,216,171,142,3,187,1,1,161,150,216,171,142,3,188,1,1,161,150,216,171,142,3,189,1,1,129,150,216,171,142,3,193,1,1,161,150,216,171,142,3,194,1,1,161,150,216,171,142,3,195,1,1,161,150,216,171,142,3,196,1,1,161,150,216,171,142,3,198,1,1,161,150,216,171,142,3,199,1,1,161,150,216,171,142,3,200,1,1,161,150,216,171,142,3,201,1,1,161,150,216,171,142,3,202,1,1,161,150,216,171,142,3,203,1,1,161,150,216,171,142,3,204,1,1,161,150,216,171,142,3,205,1,1,161,150,216,171,142,3,206,1,1,129,150,216,171,142,3,197,1,1,161,150,216,171,142,3,207,1,1,161,150,216,171,142,3,208,1,1,161,150,216,171,142,3,209,1,1,129,150,216,171,142,3,210,1,1,161,150,216,171,142,3,211,1,1,161,150,216,171,142,3,212,1,1,161,150,216,171,142,3,213,1,1,129,150,216,171,142,3,214,1,1,161,150,216,171,142,3,215,1,1,161,150,216,171,142,3,216,1,1,161,150,216,171,142,3,217,1,1,129,150,216,171,142,3,218,1,1,161,150,216,171,142,3,219,1,1,161,150,216,171,142,3,220,1,1,161,150,216,171,142,3,221,1,1,129,150,216,171,142,3,222,1,1,161,150,216,171,142,3,223,1,1,161,150,216,171,142,3,224,1,1,161,150,216,171,142,3,225,1,1,129,150,216,171,142,3,226,1,1,161,150,216,171,142,3,227,1,1,161,150,216,171,142,3,228,1,1,161,150,216,171,142,3,229,1,1,129,150,216,171,142,3,230,1,1,161,150,216,171,142,3,231,1,1,161,150,216,171,142,3,232,1,1,161,150,216,171,142,3,233,1,1,129,150,216,171,142,3,234,1,1,161,150,216,171,142,3,235,1,1,161,150,216,171,142,3,236,1,1,161,150,216,171,142,3,237,1,1,129,150,216,171,142,3,238,1,1,161,150,216,171,142,3,239,1,1,161,150,216,171,142,3,240,1,1,161,150,216,171,142,3,241,1,1,129,150,216,171,142,3,242,1,1,161,150,216,171,142,3,243,1,1,161,150,216,171,142,3,244,1,1,161,150,216,171,142,3,245,1,1,129,150,216,171,142,3,246,1,1,161,150,216,171,142,3,247,1,1,161,150,216,171,142,3,248,1,1,161,150,216,171,142,3,249,1,1,129,150,216,171,142,3,250,1,1,161,150,216,171,142,3,251,1,1,161,150,216,171,142,3,252,1,1,161,150,216,171,142,3,253,1,1,129,150,216,171,142,3,254,1,1,161,150,216,171,142,3,255,1,1,161,150,216,171,142,3,128,2,1,161,150,216,171,142,3,129,2,1,129,150,216,171,142,3,130,2,1,161,150,216,171,142,3,131,2,1,161,150,216,171,142,3,132,2,1,161,150,216,171,142,3,133,2,1,129,150,216,171,142,3,134,2,1,161,150,216,171,142,3,135,2,1,161,150,216,171,142,3,136,2,1,161,150,216,171,142,3,137,2,1,129,150,216,171,142,3,138,2,1,161,150,216,171,142,3,139,2,1,161,150,216,171,142,3,140,2,1,161,150,216,171,142,3,141,2,1,161,150,216,171,142,3,143,2,1,161,150,216,171,142,3,144,2,1,161,150,216,171,142,3,145,2,1,129,150,216,171,142,3,142,2,1,161,150,216,171,142,3,146,2,1,161,150,216,171,142,3,147,2,1,161,150,216,171,142,3,148,2,1,161,150,216,171,142,3,150,2,1,161,150,216,171,142,3,151,2,1,161,150,216,171,142,3,152,2,1,161,150,216,171,142,3,153,2,1,161,150,216,171,142,3,154,2,1,161,150,216,171,142,3,155,2,1,161,150,216,171,142,3,156,2,1,161,150,216,171,142,3,157,2,1,161,150,216,171,142,3,158,2,1,129,150,216,171,142,3,149,2,1,161,150,216,171,142,3,159,2,1,161,150,216,171,142,3,160,2,1,161,150,216,171,142,3,161,2,1,129,150,216,171,142,3,162,2,1,161,150,216,171,142,3,163,2,1,161,150,216,171,142,3,164,2,1,161,150,216,171,142,3,165,2,1,129,150,216,171,142,3,166,2,1,161,150,216,171,142,3,167,2,1,161,150,216,171,142,3,168,2,1,161,150,216,171,142,3,169,2,1,129,150,216,171,142,3,170,2,1,161,150,216,171,142,3,171,2,1,161,150,216,171,142,3,172,2,1,161,150,216,171,142,3,173,2,1,129,150,216,171,142,3,174,2,1,161,150,216,171,142,3,175,2,1,161,150,216,171,142,3,176,2,1,161,150,216,171,142,3,177,2,1,129,150,216,171,142,3,178,2,1,161,150,216,171,142,3,179,2,1,161,150,216,171,142,3,180,2,1,161,150,216,171,142,3,181,2,1,129,150,216,171,142,3,182,2,1,161,150,216,171,142,3,183,2,1,161,150,216,171,142,3,184,2,1,161,150,216,171,142,3,185,2,1,129,150,216,171,142,3,186,2,1,161,150,216,171,142,3,187,2,1,161,150,216,171,142,3,188,2,1,161,150,216,171,142,3,189,2,1,129,150,216,171,142,3,190,2,1,161,150,216,171,142,3,191,2,1,161,150,216,171,142,3,192,2,1,161,150,216,171,142,3,193,2,1,129,150,216,171,142,3,194,2,1,161,150,216,171,142,3,195,2,1,161,150,216,171,142,3,196,2,1,161,150,216,171,142,3,197,2,1,129,150,216,171,142,3,198,2,1,161,150,216,171,142,3,199,2,1,161,150,216,171,142,3,200,2,1,161,150,216,171,142,3,201,2,1,193,150,216,171,142,3,198,2,150,216,171,142,3,202,2,1,161,150,216,171,142,3,203,2,1,161,150,216,171,142,3,204,2,1,161,150,216,171,142,3,205,2,1,193,150,216,171,142,3,206,2,150,216,171,142,3,202,2,1,161,150,216,171,142,3,207,2,1,161,150,216,171,142,3,208,2,1,161,150,216,171,142,3,209,2,1,193,150,216,171,142,3,206,2,150,216,171,142,3,210,2,2,161,150,216,171,142,3,211,2,1,161,150,216,171,142,3,212,2,1,161,150,216,171,142,3,213,2,1,193,150,216,171,142,3,215,2,150,216,171,142,3,210,2,1,161,150,216,171,142,3,216,2,1,161,150,216,171,142,3,217,2,1,161,150,216,171,142,3,218,2,1,193,150,216,171,142,3,219,2,150,216,171,142,3,210,2,1,161,150,216,171,142,3,220,2,1,161,150,216,171,142,3,221,2,1,161,150,216,171,142,3,222,2,1,193,150,216,171,142,3,223,2,150,216,171,142,3,210,2,1,161,150,216,171,142,3,224,2,1,161,150,216,171,142,3,225,2,1,161,150,216,171,142,3,226,2,1,193,150,216,171,142,3,227,2,150,216,171,142,3,210,2,1,161,150,216,171,142,3,228,2,1,161,150,216,171,142,3,229,2,1,161,150,216,171,142,3,230,2,1,193,150,216,171,142,3,231,2,150,216,171,142,3,210,2,1,161,150,216,171,142,3,232,2,1,161,150,216,171,142,3,233,2,1,161,150,216,171,142,3,234,2,1,193,150,216,171,142,3,235,2,150,216,171,142,3,210,2,1,161,150,216,171,142,3,236,2,1,161,150,216,171,142,3,237,2,1,161,150,216,171,142,3,238,2,1,193,150,216,171,142,3,239,2,150,216,171,142,3,210,2,1,161,150,216,171,142,3,240,2,1,161,150,216,171,142,3,241,2,1,161,150,216,171,142,3,242,2,1,193,150,216,171,142,3,243,2,150,216,171,142,3,210,2,1,161,150,216,171,142,3,244,2,1,161,150,216,171,142,3,245,2,1,161,150,216,171,142,3,246,2,1,193,150,216,171,142,3,247,2,150,216,171,142,3,210,2,1,161,150,216,171,142,3,248,2,1,161,150,216,171,142,3,249,2,1,161,150,216,171,142,3,250,2,1,193,150,216,171,142,3,247,2,150,216,171,142,3,251,2,1,161,150,216,171,142,3,252,2,1,161,150,216,171,142,3,253,2,1,161,150,216,171,142,3,254,2,1,193,150,216,171,142,3,255,2,150,216,171,142,3,251,2,1,161,150,216,171,142,3,128,3,1,161,150,216,171,142,3,129,3,1,161,150,216,171,142,3,130,3,1,193,150,216,171,142,3,255,2,150,216,171,142,3,131,3,1,161,150,216,171,142,3,132,3,1,161,150,216,171,142,3,133,3,1,161,150,216,171,142,3,134,3,1,193,150,216,171,142,3,135,3,150,216,171,142,3,131,3,1,161,150,216,171,142,3,136,3,1,161,150,216,171,142,3,137,3,1,161,150,216,171,142,3,138,3,1,193,150,216,171,142,3,139,3,150,216,171,142,3,131,3,1,161,150,216,171,142,3,140,3,1,161,150,216,171,142,3,141,3,1,161,150,216,171,142,3,142,3,1,161,150,216,171,142,3,144,3,1,161,150,216,171,142,3,145,3,1,161,150,216,171,142,3,146,3,1,193,204,195,206,156,1,130,5,204,195,206,156,1,131,5,1,161,150,216,171,142,3,147,3,1,161,150,216,171,142,3,148,3,1,161,150,216,171,142,3,149,3,1,129,150,216,171,142,3,202,2,1,161,150,216,171,142,3,151,3,1,161,150,216,171,142,3,152,3,1,161,150,216,171,142,3,153,3,1,129,150,216,171,142,3,154,3,1,161,150,216,171,142,3,155,3,1,161,150,216,171,142,3,156,3,1,161,150,216,171,142,3,157,3,1,161,150,216,171,142,3,159,3,1,161,150,216,171,142,3,160,3,1,161,150,216,171,142,3,161,3,1,161,150,216,171,142,3,162,3,1,161,150,216,171,142,3,163,3,1,161,150,216,171,142,3,164,3,1,161,150,216,171,142,3,165,3,1,161,150,216,171,142,3,166,3,1,161,150,216,171,142,3,167,3,1,132,150,216,171,142,3,158,3,1,102,161,150,216,171,142,3,168,3,1,161,150,216,171,142,3,169,3,1,161,150,216,171,142,3,170,3,1,132,150,216,171,142,3,171,3,1,117,161,150,216,171,142,3,172,3,1,161,150,216,171,142,3,173,3,1,161,150,216,171,142,3,174,3,1,132,150,216,171,142,3,175,3,1,110,161,150,216,171,142,3,176,3,1,161,150,216,171,142,3,177,3,1,161,150,216,171,142,3,178,3,1,132,150,216,171,142,3,179,3,1,99,161,150,216,171,142,3,180,3,1,161,150,216,171,142,3,181,3,1,161,150,216,171,142,3,182,3,1,132,150,216,171,142,3,183,3,1,116,161,150,216,171,142,3,184,3,1,161,150,216,171,142,3,185,3,1,161,150,216,171,142,3,186,3,1,132,150,216,171,142,3,187,3,1,105,161,150,216,171,142,3,188,3,1,161,150,216,171,142,3,189,3,1,161,150,216,171,142,3,190,3,1,132,150,216,171,142,3,191,3,1,111,161,150,216,171,142,3,192,3,1,161,150,216,171,142,3,193,3,1,161,150,216,171,142,3,194,3,1,132,150,216,171,142,3,195,3,1,110,161,150,216,171,142,3,196,3,1,161,150,216,171,142,3,197,3,1,161,150,216,171,142,3,198,3,1,132,150,216,171,142,3,199,3,1,32,161,150,216,171,142,3,200,3,1,161,150,216,171,142,3,201,3,1,161,150,216,171,142,3,202,3,1,132,150,216,171,142,3,203,3,1,109,161,150,216,171,142,3,204,3,1,161,150,216,171,142,3,205,3,1,161,150,216,171,142,3,206,3,1,132,150,216,171,142,3,207,3,1,97,161,150,216,171,142,3,208,3,1,161,150,216,171,142,3,209,3,1,161,150,216,171,142,3,210,3,1,132,150,216,171,142,3,211,3,1,105,161,150,216,171,142,3,212,3,1,161,150,216,171,142,3,213,3,1,161,150,216,171,142,3,214,3,1,132,150,216,171,142,3,215,3,1,110,161,150,216,171,142,3,216,3,1,161,150,216,171,142,3,217,3,1,161,150,216,171,142,3,218,3,1,132,150,216,171,142,3,219,3,1,40,161,150,216,171,142,3,220,3,1,161,150,216,171,142,3,221,3,1,161,150,216,171,142,3,222,3,1,132,150,216,171,142,3,223,3,1,41,161,150,216,171,142,3,224,3,1,161,150,216,171,142,3,225,3,1,161,150,216,171,142,3,226,3,1,132,150,216,171,142,3,227,3,1,32,161,150,216,171,142,3,228,3,1,161,150,216,171,142,3,229,3,1,161,150,216,171,142,3,230,3,1,132,150,216,171,142,3,231,3,1,123,161,150,216,171,142,3,232,3,1,161,150,216,171,142,3,233,3,1,161,150,216,171,142,3,234,3,1,132,150,216,171,142,3,235,3,1,125,161,150,216,171,142,3,236,3,1,161,150,216,171,142,3,237,3,1,161,150,216,171,142,3,238,3,1,196,150,216,171,142,3,235,3,150,216,171,142,3,239,3,1,10,161,150,216,171,142,3,240,3,1,161,150,216,171,142,3,241,3,1,161,150,216,171,142,3,242,3,1,196,150,216,171,142,3,243,3,150,216,171,142,3,239,3,1,10,161,150,216,171,142,3,244,3,1,161,150,216,171,142,3,245,3,1,161,150,216,171,142,3,246,3,1,196,150,216,171,142,3,243,3,150,216,171,142,3,247,3,2,32,32,161,150,216,171,142,3,248,3,1,161,150,216,171,142,3,249,3,1,161,150,216,171,142,3,250,3,1,196,150,216,171,142,3,252,3,150,216,171,142,3,247,3,1,99,161,150,216,171,142,3,253,3,1,161,150,216,171,142,3,254,3,1,161,150,216,171,142,3,255,3,1,196,150,216,171,142,3,128,4,150,216,171,142,3,247,3,1,111,161,150,216,171,142,3,129,4,1,161,150,216,171,142,3,130,4,1,161,150,216,171,142,3,131,4,1,196,150,216,171,142,3,132,4,150,216,171,142,3,247,3,1,110,161,150,216,171,142,3,133,4,1,161,150,216,171,142,3,134,4,1,161,150,216,171,142,3,135,4,1,196,150,216,171,142,3,136,4,150,216,171,142,3,247,3,1,115,161,150,216,171,142,3,137,4,1,161,150,216,171,142,3,138,4,1,161,150,216,171,142,3,139,4,1,196,150,216,171,142,3,140,4,150,216,171,142,3,247,3,1,111,161,150,216,171,142,3,141,4,1,161,150,216,171,142,3,142,4,1,161,150,216,171,142,3,143,4,1,196,150,216,171,142,3,144,4,150,216,171,142,3,247,3,1,108,161,150,216,171,142,3,145,4,1,161,150,216,171,142,3,146,4,1,161,150,216,171,142,3,147,4,1,196,150,216,171,142,3,148,4,150,216,171,142,3,247,3,1,101,161,150,216,171,142,3,149,4,1,161,150,216,171,142,3,150,4,1,161,150,216,171,142,3,151,4,1,196,150,216,171,142,3,152,4,150,216,171,142,3,247,3,1,46,161,150,216,171,142,3,153,4,1,161,150,216,171,142,3,154,4,1,161,150,216,171,142,3,155,4,1,196,150,216,171,142,3,156,4,150,216,171,142,3,247,3,1,108,161,150,216,171,142,3,157,4,1,161,150,216,171,142,3,158,4,1,161,150,216,171,142,3,159,4,1,196,150,216,171,142,3,160,4,150,216,171,142,3,247,3,1,111,161,150,216,171,142,3,161,4,1,161,150,216,171,142,3,162,4,1,161,150,216,171,142,3,163,4,1,196,150,216,171,142,3,164,4,150,216,171,142,3,247,3,1,103,161,150,216,171,142,3,165,4,1,161,150,216,171,142,3,166,4,1,161,150,216,171,142,3,167,4,1,196,150,216,171,142,3,168,4,150,216,171,142,3,247,3,1,40,161,150,216,171,142,3,169,4,1,161,150,216,171,142,3,170,4,1,161,150,216,171,142,3,171,4,1,196,150,216,171,142,3,172,4,150,216,171,142,3,247,3,1,41,161,150,216,171,142,3,173,4,1,161,150,216,171,142,3,174,4,1,161,150,216,171,142,3,175,4,1,196,150,216,171,142,3,172,4,150,216,171,142,3,176,4,1,34,161,150,216,171,142,3,177,4,1,161,150,216,171,142,3,178,4,1,161,150,216,171,142,3,179,4,1,196,150,216,171,142,3,180,4,150,216,171,142,3,176,4,1,34,161,150,216,171,142,3,181,4,1,161,150,216,171,142,3,182,4,1,161,150,216,171,142,3,183,4,1,196,150,216,171,142,3,180,4,150,216,171,142,3,184,4,1,72,161,150,216,171,142,3,185,4,1,161,150,216,171,142,3,186,4,1,161,150,216,171,142,3,187,4,1,196,150,216,171,142,3,188,4,150,216,171,142,3,184,4,1,101,161,150,216,171,142,3,189,4,1,161,150,216,171,142,3,190,4,1,161,150,216,171,142,3,191,4,1,196,150,216,171,142,3,192,4,150,216,171,142,3,184,4,1,108,161,150,216,171,142,3,193,4,1,161,150,216,171,142,3,194,4,1,161,150,216,171,142,3,195,4,1,196,150,216,171,142,3,196,4,150,216,171,142,3,184,4,1,108,161,150,216,171,142,3,197,4,1,161,150,216,171,142,3,198,4,1,161,150,216,171,142,3,199,4,1,196,150,216,171,142,3,200,4,150,216,171,142,3,184,4,1,111,161,150,216,171,142,3,201,4,1,161,150,216,171,142,3,202,4,1,161,150,216,171,142,3,203,4,1,196,150,216,171,142,3,204,4,150,216,171,142,3,184,4,1,32,161,150,216,171,142,3,205,4,1,161,150,216,171,142,3,206,4,1,161,150,216,171,142,3,207,4,1,196,150,216,171,142,3,208,4,150,216,171,142,3,184,4,1,87,161,150,216,171,142,3,209,4,1,161,150,216,171,142,3,210,4,1,161,150,216,171,142,3,211,4,1,196,150,216,171,142,3,212,4,150,216,171,142,3,184,4,1,111,161,150,216,171,142,3,213,4,1,161,150,216,171,142,3,214,4,1,161,150,216,171,142,3,215,4,1,196,150,216,171,142,3,216,4,150,216,171,142,3,184,4,1,114,161,150,216,171,142,3,217,4,1,161,150,216,171,142,3,218,4,1,161,150,216,171,142,3,219,4,1,196,150,216,171,142,3,220,4,150,216,171,142,3,184,4,1,108,161,150,216,171,142,3,221,4,1,161,150,216,171,142,3,222,4,1,161,150,216,171,142,3,223,4,1,196,150,216,171,142,3,224,4,150,216,171,142,3,184,4,1,100,161,150,216,171,142,3,225,4,1,161,150,216,171,142,3,226,4,1,161,150,216,171,142,3,227,4,1,193,150,216,171,142,3,228,4,150,216,171,142,3,184,4,1,161,150,216,171,142,3,229,4,1,161,150,216,171,142,3,230,4,1,161,150,216,171,142,3,231,4,1,168,150,216,171,142,3,233,4,1,119,132,1,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,47,47,32,84,104,105,115,32,105,115,32,116,104,101,32,109,97,105,110,32,102,117,110,99,116,105,111,110,46,92,110,102,117,110,99,116,105,111,110,32,109,97,105,110,40,41,32,123,92,110,32,32,99,111,110,115,111,108,101,46,108,111,103,40,92,34,72,101,108,108,111,32,87,111,114,108,100,92,34,41,92,110,125,34,125,93,44,34,108,97,110,103,117,97,103,101,34,58,34,74,97,118,97,115,99,114,105,112,116,34,125,168,150,216,171,142,3,234,4,1,119,10,49,112,115,100,67,122,97,87,104,49,168,150,216,171,142,3,235,4,1,119,4,116,101,120,116,39,0,204,195,206,156,1,4,6,48,100,51,52,82,50,2,39,0,204,195,206,156,1,4,6,45,71,115,81,49,95,2,4,0,150,216,171,142,3,240,4,4,49,50,51,32,134,150,216,171,142,3,244,4,7,109,101,110,116,105,111,110,51,123,34,100,97,116,101,34,58,34,50,48,50,52,45,48,52,45,49,57,84,49,54,58,49,51,58,52,57,46,52,49,49,49,54,53,34,44,34,116,121,112,101,34,58,34,100,97,116,101,34,125,132,150,216,171,142,3,245,4,1,36,134,150,216,171,142,3,246,4,7,109,101,110,116,105,111,110,4,110,117,108,108,132,150,216,171,142,3,247,4,6,32,32,101,114,32,32,33,0,204,195,206,156,1,1,6,49,71,114,87,76,99,1,0,7,33,0,204,195,206,156,1,3,6,72,105,104,101,52,114,1,193,164,202,219,213,10,28,199,130,209,189,2,60,1,168,164,202,219,213,10,117,1,119,151,1,123,34,108,101,118,101,108,34,58,50,44,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,49,50,51,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,109,101,110,116,105,111,110,34,58,123,34,100,97,116,101,34,58,34,50,48,50,52,45,48,52,45,49,57,84,49,54,58,49,51,58,52,57,46,52,49,49,49,54,53,34,44,34,116,121,112,101,34,58,34,100,97,116,101,34,125,125,44,34,105,110,115,101,114,116,34,58,34,36,34,125,44,123,34,105,110,115,101,114,116,34,58,34,32,32,101,114,32,32,34,125,93,125,4,0,150,216,171,142,3,239,4,1,35,0,1,132,150,216,171,142,3,137,5,1,35,0,1,132,150,216,171,142,3,139,5,1,35,0,1,39,0,204,195,206,156,1,4,6,115,99,68,45,119,114,2,39,0,204,195,206,156,1,1,6,67,72,115,77,79,98,1,40,0,150,216,171,142,3,144,5,2,105,100,1,119,6,67,72,115,77,79,98,40,0,150,216,171,142,3,144,5,2,116,121,1,119,7,104,101,97,100,105,110,103,40,0,150,216,171,142,3,144,5,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,150,216,171,142,3,144,5,8,99,104,105,108,100,114,101,110,1,119,6,97,107,121,80,104,45,33,0,150,216,171,142,3,144,5,4,100,97,116,97,1,40,0,150,216,171,142,3,144,5,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,150,216,171,142,3,144,5,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,97,107,121,80,104,45,0,200,164,202,219,213,10,28,150,216,171,142,3,135,5,1,119,6,67,72,115,77,79,98,4,0,150,216,171,142,3,143,5,1,49,161,150,216,171,142,3,149,5,1,132,150,216,171,142,3,154,5,1,50,161,150,216,171,142,3,155,5,1,132,150,216,171,142,3,156,5,1,51,161,150,216,171,142,3,157,5,1,168,150,216,171,142,3,5,1,119,11,123,34,100,101,112,116,104,34,58,54,125,39,0,204,195,206,156,1,4,6,112,79,69,118,75,110,2,39,0,204,195,206,156,1,4,6,80,49,49,121,116,108,2,4,0,150,216,171,142,3,162,5,3,49,50,51,33,0,204,195,206,156,1,1,6,97,79,72,54,79,66,1,0,7,33,0,204,195,206,156,1,3,6,105,89,77,80,45,116,1,193,150,216,171,142,3,135,5,199,130,209,189,2,60,1,161,150,216,171,142,3,159,5,1,168,150,216,171,142,3,176,5,1,119,38,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,49,50,51,34,125,93,44,34,108,101,118,101,108,34,58,51,125,161,199,130,209,189,2,212,3,1,161,199,130,209,189,2,232,3,1,161,199,130,209,189,2,159,4,1,161,150,216,171,142,3,179,5,1,161,199,130,209,189,2,144,4,1,161,150,216,171,142,3,181,5,1,161,150,216,171,142,3,182,5,1,161,150,216,171,142,3,180,5,2,161,150,216,171,142,3,183,5,1,161,150,216,171,142,3,184,5,1,161,150,216,171,142,3,186,5,1,161,199,130,209,189,2,252,3,1,161,150,216,171,142,3,188,5,1,161,150,216,171,142,3,189,5,1,39,0,204,195,206,156,1,4,6,70,76,85,90,75,54,2,39,0,204,195,206,156,1,4,6,69,53,84,66,120,118,2,39,0,204,195,206,156,1,1,6,103,79,106,51,90,68,1,40,0,150,216,171,142,3,195,5,2,105,100,1,119,6,103,79,106,51,90,68,40,0,150,216,171,142,3,195,5,2,116,121,1,119,10,116,97,98,108,101,47,99,101,108,108,40,0,150,216,171,142,3,195,5,6,112,97,114,101,110,116,1,119,6,120,49,100,100,111,87,40,0,150,216,171,142,3,195,5,8,99,104,105,108,100,114,101,110,1,119,6,116,51,112,87,101,56,33,0,150,216,171,142,3,195,5,4,100,97,116,97,1,40,0,150,216,171,142,3,195,5,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,150,216,171,142,3,195,5,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,116,51,112,87,101,56,0,136,199,130,209,189,2,148,4,1,119,6,103,79,106,51,90,68,39,0,204,195,206,156,1,1,6,88,56,80,113,67,120,1,40,0,150,216,171,142,3,205,5,2,105,100,1,119,6,88,56,80,113,67,120,40,0,150,216,171,142,3,205,5,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,150,216,171,142,3,205,5,6,112,97,114,101,110,116,1,119,6,103,79,106,51,90,68,40,0,150,216,171,142,3,205,5,8,99,104,105,108,100,114,101,110,1,119,6,76,55,82,84,78,106,33,0,150,216,171,142,3,205,5,4,100,97,116,97,1,40,0,150,216,171,142,3,205,5,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,150,216,171,142,3,205,5,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,76,55,82,84,78,106,0,8,0,150,216,171,142,3,203,5,1,119,6,88,56,80,113,67,120,39,0,204,195,206,156,1,1,6,74,48,69,48,67,66,1,40,0,150,216,171,142,3,215,5,2,105,100,1,119,6,74,48,69,48,67,66,40,0,150,216,171,142,3,215,5,2,116,121,1,119,10,116,97,98,108,101,47,99,101,108,108,40,0,150,216,171,142,3,215,5,6,112,97,114,101,110,116,1,119,6,120,49,100,100,111,87,40,0,150,216,171,142,3,215,5,8,99,104,105,108,100,114,101,110,1,119,6,73,120,120,49,82,88,33,0,150,216,171,142,3,215,5,4,100,97,116,97,1,40,0,150,216,171,142,3,215,5,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,150,216,171,142,3,215,5,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,73,120,120,49,82,88,0,136,150,216,171,142,3,204,5,1,119,6,74,48,69,48,67,66,39,0,204,195,206,156,1,1,6,75,78,45,115,87,88,1,40,0,150,216,171,142,3,225,5,2,105,100,1,119,6,75,78,45,115,87,88,40,0,150,216,171,142,3,225,5,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,150,216,171,142,3,225,5,6,112,97,114,101,110,116,1,119,6,74,48,69,48,67,66,40,0,150,216,171,142,3,225,5,8,99,104,105,108,100,114,101,110,1,119,6,75,115,100,83,116,74,33,0,150,216,171,142,3,225,5,4,100,97,116,97,1,40,0,150,216,171,142,3,225,5,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,150,216,171,142,3,225,5,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,75,115,100,83,116,74,0,8,0,150,216,171,142,3,223,5,1,119,6,75,78,45,115,87,88,161,150,216,171,142,3,192,5,1,4,0,150,216,171,142,3,193,5,1,55,161,150,216,171,142,3,210,5,1,132,150,216,171,142,3,236,5,1,55,161,150,216,171,142,3,237,5,1,132,150,216,171,142,3,238,5,1,55,161,150,216,171,142,3,239,5,1,39,0,204,195,206,156,1,4,6,71,52,107,110,49,95,2,39,0,204,195,206,156,1,4,6,98,52,97,80,80,53,2,39,0,204,195,206,156,1,4,6,80,111,114,82,81,79,2,161,150,216,171,142,3,235,5,1,39,0,204,195,206,156,1,1,6,80,53,88,89,98,115,1,40,0,150,216,171,142,3,246,5,2,105,100,1,119,6,80,53,88,89,98,115,40,0,150,216,171,142,3,246,5,2,116,121,1,119,10,116,97,98,108,101,47,99,101,108,108,40,0,150,216,171,142,3,246,5,6,112,97,114,101,110,116,1,119,6,120,49,100,100,111,87,40,0,150,216,171,142,3,246,5,8,99,104,105,108,100,114,101,110,1,119,6,89,55,81,88,109,84,33,0,150,216,171,142,3,246,5,4,100,97,116,97,1,40,0,150,216,171,142,3,246,5,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,150,216,171,142,3,246,5,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,89,55,81,88,109,84,0,200,199,130,209,189,2,236,3,199,130,209,189,2,128,4,1,119,6,80,53,88,89,98,115,39,0,204,195,206,156,1,1,6,57,49,85,122,51,51,1,40,0,150,216,171,142,3,128,6,2,105,100,1,119,6,57,49,85,122,51,51,40,0,150,216,171,142,3,128,6,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,150,216,171,142,3,128,6,6,112,97,114,101,110,116,1,119,6,80,53,88,89,98,115,40,0,150,216,171,142,3,128,6,8,99,104,105,108,100,114,101,110,1,119,6,88,73,86,101,114,105,33,0,150,216,171,142,3,128,6,4,100,97,116,97,1,40,0,150,216,171,142,3,128,6,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,150,216,171,142,3,128,6,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,88,73,86,101,114,105,0,8,0,150,216,171,142,3,254,5,1,119,6,57,49,85,122,51,51,161,150,216,171,142,3,245,5,1,39,0,204,195,206,156,1,1,6,97,105,73,55,114,78,1,40,0,150,216,171,142,3,139,6,2,105,100,1,119,6,97,105,73,55,114,78,40,0,150,216,171,142,3,139,6,2,116,121,1,119,10,116,97,98,108,101,47,99,101,108,108,40,0,150,216,171,142,3,139,6,6,112,97,114,101,110,116,1,119,6,120,49,100,100,111,87,40,0,150,216,171,142,3,139,6,8,99,104,105,108,100,114,101,110,1,119,6,70,83,77,57,101,119,33,0,150,216,171,142,3,139,6,4,100,97,116,97,1,40,0,150,216,171,142,3,139,6,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,150,216,171,142,3,139,6,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,70,83,77,57,101,119,0,200,199,130,209,189,2,148,4,150,216,171,142,3,204,5,1,119,6,97,105,73,55,114,78,39,0,204,195,206,156,1,1,6,48,117,114,80,56,120,1,40,0,150,216,171,142,3,149,6,2,105,100,1,119,6,48,117,114,80,56,120,40,0,150,216,171,142,3,149,6,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,150,216,171,142,3,149,6,6,112,97,114,101,110,116,1,119,6,97,105,73,55,114,78,40,0,150,216,171,142,3,149,6,8,99,104,105,108,100,114,101,110,1,119,6,98,90,114,57,106,101,33,0,150,216,171,142,3,149,6,4,100,97,116,97,1,40,0,150,216,171,142,3,149,6,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,150,216,171,142,3,149,6,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,98,90,114,57,106,101,0,8,0,150,216,171,142,3,147,6,1,119,6,48,117,114,80,56,120,39,0,204,195,206,156,1,1,6,100,114,110,97,115,68,1,40,0,150,216,171,142,3,159,6,2,105,100,1,119,6,100,114,110,97,115,68,40,0,150,216,171,142,3,159,6,2,116,121,1,119,10,116,97,98,108,101,47,99,101,108,108,40,0,150,216,171,142,3,159,6,6,112,97,114,101,110,116,1,119,6,120,49,100,100,111,87,40,0,150,216,171,142,3,159,6,8,99,104,105,108,100,114,101,110,1,119,6,76,114,45,49,81,54,33,0,150,216,171,142,3,159,6,4,100,97,116,97,1,40,0,150,216,171,142,3,159,6,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,150,216,171,142,3,159,6,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,76,114,45,49,81,54,0,136,150,216,171,142,3,224,5,1,119,6,100,114,110,97,115,68,39,0,204,195,206,156,1,1,6,72,69,79,54,86,72,1,40,0,150,216,171,142,3,169,6,2,105,100,1,119,6,72,69,79,54,86,72,40,0,150,216,171,142,3,169,6,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,150,216,171,142,3,169,6,6,112,97,114,101,110,116,1,119,6,100,114,110,97,115,68,40,0,150,216,171,142,3,169,6,8,99,104,105,108,100,114,101,110,1,119,6,71,118,57,87,108,76,33,0,150,216,171,142,3,169,6,4,100,97,116,97,1,40,0,150,216,171,142,3,169,6,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,150,216,171,142,3,169,6,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,71,118,57,87,108,76,0,8,0,150,216,171,142,3,167,6,1,119,6,72,69,79,54,86,72,4,0,150,216,171,142,3,242,5,1,49,161,150,216,171,142,3,133,6,1,132,150,216,171,142,3,179,6,1,48,161,150,216,171,142,3,180,6,1,132,150,216,171,142,3,181,6,1,49,161,150,216,171,142,3,182,6,1,132,150,216,171,142,3,183,6,1,48,161,150,216,171,142,3,184,6,1,161,150,216,171,142,3,187,5,1,161,150,216,171,142,3,191,5,1,161,150,216,171,142,3,220,5,1,161,150,216,171,142,3,138,6,1,39,0,204,195,206,156,1,4,6,54,73,68,55,103,118,2,4,0,150,216,171,142,3,191,6,1,49,168,199,130,209,189,2,169,4,1,119,26,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,49,34,125,93,125,39,0,204,195,206,156,1,4,6,77,84,68,68,110,107,2,4,0,150,216,171,142,3,194,6,1,50,168,199,130,209,189,2,242,3,1,119,26,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,50,34,125,93,125,39,0,204,195,206,156,1,4,6,88,108,87,102,45,70,2,4,0,150,216,171,142,3,197,6,1,51,168,150,216,171,142,3,186,6,1,119,26,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,51,34,125,93,125,39,0,204,195,206,156,1,4,6,57,98,65,107,106,109,2,4,0,150,216,171,142,3,200,6,1,52,168,199,130,209,189,2,134,4,1,119,26,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,52,34,125,93,125,39,0,204,195,206,156,1,4,6,99,112,85,122,107,121,2,4,0,150,216,171,142,3,203,6,1,53,168,199,130,209,189,2,177,4,1,119,26,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,53,34,125,93,125,39,0,204,195,206,156,1,4,6,81,102,72,107,77,77,2,4,0,150,216,171,142,3,206,6,1,54,168,150,216,171,142,3,154,6,1,119,26,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,54,34,125,93,125,39,0,204,195,206,156,1,4,6,119,113,82,74,112,76,2,4,0,150,216,171,142,3,209,6,1,55,168,150,216,171,142,3,241,5,1,119,26,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,55,34,125,93,125,39,0,204,195,206,156,1,4,6,69,70,82,71,121,80,2,4,0,150,216,171,142,3,212,6,1,56,168,150,216,171,142,3,230,5,1,119,26,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,56,34,125,93,125,39,0,204,195,206,156,1,4,6,104,109,90,99,72,101,2,1,0,150,216,171,142,3,215,6,1,161,150,216,171,142,3,174,6,2,132,150,216,171,142,3,216,6,1,57,168,150,216,171,142,3,218,6,1,119,26,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,57,34,125,93,125,39,0,204,195,206,156,1,4,6,56,117,111,87,49,119,2,4,0,150,216,171,142,3,221,6,4,119,105,116,104,168,199,130,209,189,2,146,3,1,119,61,123,34,97,108,105,103,110,34,58,34,114,105,103,104,116,34,44,34,99,104,101,99,107,101,100,34,58,102,97,108,115,101,44,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,119,105,116,104,34,125,93,125,39,0,204,195,206,156,1,4,6,109,55,80,110,81,54,2,4,0,150,216,171,142,3,227,6,3,108,111,110,129,150,216,171,142,3,230,6,14,132,150,216,171,142,3,244,6,44,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,134,150,216,171,142,3,160,7,11,102,111,110,116,95,102,97,109,105,108,121,22,34,65,68,76,97,77,68,105,115,112,108,97,121,95,114,101,103,117,108,97,114,34,132,150,216,171,142,3,161,7,9,116,101,120,116,110,103,32,116,101,134,150,216,171,142,3,170,7,11,102,111,110,116,95,102,97,109,105,108,121,4,110,117,108,108,132,150,216,171,142,3,171,7,16,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,129,150,216,171,142,3,187,7,6,132,150,216,171,142,3,193,7,92,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,161,199,130,209,189,2,213,2,1,196,150,216,171,142,3,187,7,150,216,171,142,3,188,7,1,76,161,150,216,171,142,3,158,8,1,196,150,216,171,142,3,193,7,150,216,171,142,3,194,7,1,101,161,150,216,171,142,3,160,8,1,70,204,195,206,156,1,135,7,11,102,111,110,116,95,102,97,109,105,108,121,22,34,65,98,114,105,108,70,97,116,102,97,99,101,95,114,101,103,117,108,97,114,34,193,150,216,171,142,3,163,8,204,195,206,156,1,135,7,3,198,150,216,171,142,3,166,8,204,195,206,156,1,135,7,11,102,111,110,116,95,102,97,109,105,108,121,4,110,117,108,108,161,199,130,209,189,2,25,1,161,199,130,209,189,2,26,1,161,199,130,209,189,2,27,1,198,150,216,171,142,3,230,6,150,216,171,142,3,231,6,11,102,111,110,116,95,102,97,109,105,108,121,22,34,65,68,76,97,77,68,105,115,112,108,97,121,95,114,101,103,117,108,97,114,34,196,150,216,171,142,3,171,8,150,216,171,142,3,231,6,14,103,32,116,101,120,116,110,103,32,116,101,120,116,110,198,150,216,171,142,3,185,8,150,216,171,142,3,231,6,11,102,111,110,116,95,102,97,109,105,108,121,4,110,117,108,108,161,150,216,171,142,3,162,8,1,16,146,175,139,236,2,0,161,227,211,144,195,8,0,1,161,227,211,144,195,8,1,1,161,227,211,144,195,8,2,1,161,227,211,144,195,8,3,1,161,227,211,144,195,8,4,1,161,227,211,144,195,8,5,1,161,227,211,144,195,8,11,1,161,227,211,144,195,8,7,1,161,227,211,144,195,8,8,1,161,227,211,144,195,8,9,1,161,146,175,139,236,2,6,2,39,0,204,195,206,156,1,4,6,66,66,65,103,65,56,2,6,0,146,175,139,236,2,12,11,102,111,110,116,95,102,97,109,105,108,121,15,34,65,68,76,97,77,32,68,105,115,112,108,97,121,34,132,146,175,139,236,2,13,183,1,108,111,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,76,101,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,107,107,109,134,146,175,139,236,2,196,1,11,102,111,110,116,95,102,97,109,105,108,121,4,110,117,108,108,168,192,187,174,206,8,222,5,1,119,141,2,123,34,99,104,101,99,107,101,100,34,58,102,97,108,115,101,44,34,100,101,108,116,97,34,58,91,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,102,111,110,116,95,102,97,109,105,108,121,34,58,34,65,68,76,97,77,32,68,105,115,112,108,97,121,34,125,44,34,105,110,115,101,114,116,34,58,34,108,111,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,76,101,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,107,107,109,34,125,93,125,20,168,215,223,235,2,0,161,150,216,171,142,3,168,8,1,161,150,216,171,142,3,169,8,1,161,150,216,171,142,3,170,8,1,196,150,216,171,142,3,166,8,150,216,171,142,3,167,8,3,87,101,108,132,224,159,166,178,15,26,16,99,111,109,101,32,116,111,32,65,112,112,70,108,111,119,121,39,0,204,195,206,156,1,4,6,74,98,104,77,53,50,2,4,0,168,215,223,235,2,22,20,72,101,114,101,32,97,114,101,32,116,104,101,32,98,97,115,105,99,115,32,168,168,215,223,235,2,0,1,119,120,123,34,100,101,108,116,97,34,58,91,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,102,111,110,116,95,102,97,109,105,108,121,34,58,34,65,98,114,105,108,70,97,116,102,97,99,101,95,114,101,103,117,108,97,114,34,125,44,34,105,110,115,101,114,116,34,58,34,87,101,108,34,125,44,123,34,105,110,115,101,114,116,34,58,34,99,111,109,101,32,116,111,32,65,112,112,70,108,111,119,121,34,125,93,44,34,108,101,118,101,108,34,58,49,125,168,168,215,223,235,2,1,1,119,10,119,88,107,79,72,81,49,50,99,111,168,168,215,223,235,2,2,1,119,4,116,101,120,116,167,204,195,206,156,1,213,1,1,40,0,168,215,223,235,2,46,2,105,100,1,119,10,97,115,74,118,54,70,114,65,82,97,40,0,168,215,223,235,2,46,2,116,121,1,119,7,104,101,97,100,105,110,103,40,0,168,215,223,235,2,46,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,168,215,223,235,2,46,8,99,104,105,108,100,114,101,110,1,119,6,51,107,67,87,106,70,40,0,168,215,223,235,2,46,4,100,97,116,97,1,119,55,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,72,101,114,101,32,97,114,101,32,116,104,101,32,98,97,115,105,99,115,32,34,125,93,44,34,108,101,118,101,108,34,58,50,125,40,0,168,215,223,235,2,46,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,168,215,223,235,2,46,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,51,107,67,87,106,70,0,200,204,195,206,156,1,232,1,199,130,209,189,2,181,3,1,119,10,97,115,74,118,54,70,114,65,82,97,1,192,246,139,213,2,0,161,239,239,208,251,10,16,35,1,190,183,139,210,2,0,161,241,147,239,232,6,3,110,2,237,140,187,206,2,0,161,190,183,139,210,2,109,17,161,237,140,187,206,2,16,4,137,6,199,130,209,189,2,0,39,0,204,195,206,156,1,4,6,88,116,53,112,118,55,2,4,0,199,130,209,189,2,0,9,229,144,140,228,184,128,228,184,170,129,199,130,209,189,2,3,5,161,178,187,245,161,14,10,6,132,199,130,209,189,2,8,1,110,161,199,130,209,189,2,14,1,132,199,130,209,189,2,15,1,105,168,199,130,209,189,2,16,1,119,36,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,229,144,140,228,184,128,228,184,170,110,105,34,125,93,125,161,224,159,166,178,15,27,1,161,224,159,166,178,15,28,1,161,224,159,166,178,15,29,1,161,199,130,209,189,2,19,1,161,199,130,209,189,2,20,1,161,199,130,209,189,2,21,1,161,199,130,209,189,2,22,1,161,199,130,209,189,2,23,1,161,199,130,209,189,2,24,1,0,3,39,0,204,195,206,156,1,4,6,82,74,97,73,54,107,2,4,0,199,130,209,189,2,31,1,116,161,228,242,134,215,15,11,1,132,199,130,209,189,2,32,1,111,161,199,130,209,189,2,33,1,132,199,130,209,189,2,34,1,100,161,199,130,209,189,2,35,1,132,199,130,209,189,2,36,1,111,161,199,130,209,189,2,37,1,132,199,130,209,189,2,38,1,32,161,199,130,209,189,2,39,1,132,199,130,209,189,2,40,1,108,161,199,130,209,189,2,41,1,132,199,130,209,189,2,42,1,105,161,199,130,209,189,2,43,1,132,199,130,209,189,2,44,1,115,161,199,130,209,189,2,45,1,132,199,130,209,189,2,46,1,116,161,199,130,209,189,2,47,1,39,0,204,195,206,156,1,4,6,55,80,118,106,121,81,2,39,0,204,195,206,156,1,1,6,71,118,88,50,102,110,1,40,0,199,130,209,189,2,51,2,105,100,1,119,6,71,118,88,50,102,110,40,0,199,130,209,189,2,51,2,116,121,1,119,9,116,111,100,111,95,108,105,115,116,40,0,199,130,209,189,2,51,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,199,130,209,189,2,51,8,99,104,105,108,100,114,101,110,1,119,6,111,112,68,102,54,95,33,0,199,130,209,189,2,51,4,100,97,116,97,1,40,0,199,130,209,189,2,51,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,199,130,209,189,2,51,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,111,112,68,102,54,95,0,200,198,223,206,159,1,135,1,198,223,206,159,1,116,1,119,6,71,118,88,50,102,110,161,199,130,209,189,2,49,1,4,0,199,130,209,189,2,50,1,99,161,199,130,209,189,2,56,1,132,199,130,209,189,2,62,1,104,161,199,130,209,189,2,63,1,132,199,130,209,189,2,64,1,101,161,199,130,209,189,2,65,1,132,199,130,209,189,2,66,1,99,161,199,130,209,189,2,67,1,132,199,130,209,189,2,68,1,107,161,199,130,209,189,2,69,1,132,199,130,209,189,2,70,1,101,161,199,130,209,189,2,71,1,132,199,130,209,189,2,72,1,100,161,199,130,209,189,2,73,1,132,199,130,209,189,2,74,1,32,161,199,130,209,189,2,75,1,132,199,130,209,189,2,76,1,116,161,199,130,209,189,2,77,1,132,199,130,209,189,2,78,1,111,161,199,130,209,189,2,79,1,132,199,130,209,189,2,80,1,100,161,199,130,209,189,2,81,1,132,199,130,209,189,2,82,1,111,161,199,130,209,189,2,83,1,132,199,130,209,189,2,84,1,32,161,199,130,209,189,2,85,1,132,199,130,209,189,2,86,1,108,161,199,130,209,189,2,87,1,132,199,130,209,189,2,88,1,105,161,199,130,209,189,2,89,1,132,199,130,209,189,2,90,1,115,161,199,130,209,189,2,91,1,132,199,130,209,189,2,92,1,116,161,199,130,209,189,2,93,1,39,0,204,195,206,156,1,4,6,117,115,117,45,118,111,2,39,0,204,195,206,156,1,1,6,86,90,80,95,77,113,1,40,0,199,130,209,189,2,97,2,105,100,1,119,6,86,90,80,95,77,113,40,0,199,130,209,189,2,97,2,116,121,1,119,9,116,111,100,111,95,108,105,115,116,40,0,199,130,209,189,2,97,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,199,130,209,189,2,97,8,99,104,105,108,100,114,101,110,1,119,6,54,55,76,56,102,50,33,0,199,130,209,189,2,97,4,100,97,116,97,1,40,0,199,130,209,189,2,97,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,199,130,209,189,2,97,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,54,55,76,56,102,50,0,200,199,130,209,189,2,60,198,223,206,159,1,116,1,119,6,86,90,80,95,77,113,161,199,130,209,189,2,95,1,4,0,199,130,209,189,2,96,1,108,161,199,130,209,189,2,102,1,132,199,130,209,189,2,108,1,111,161,199,130,209,189,2,109,1,132,199,130,209,189,2,110,1,110,161,199,130,209,189,2,111,1,132,199,130,209,189,2,112,1,103,161,199,130,209,189,2,113,1,132,199,130,209,189,2,114,1,32,161,199,130,209,189,2,115,1,132,199,130,209,189,2,116,1,116,161,199,130,209,189,2,117,1,132,199,130,209,189,2,118,1,101,161,199,130,209,189,2,119,1,132,199,130,209,189,2,120,1,120,161,199,130,209,189,2,121,1,132,199,130,209,189,2,122,1,116,161,199,130,209,189,2,123,1,129,199,130,209,189,2,124,1,161,199,130,209,189,2,125,2,132,199,130,209,189,2,126,7,110,103,32,116,101,120,116,161,199,130,209,189,2,128,1,1,132,199,130,209,189,2,135,1,7,110,103,32,116,101,120,116,161,199,130,209,189,2,136,1,1,132,199,130,209,189,2,143,1,7,110,103,32,116,101,120,116,161,199,130,209,189,2,144,1,1,132,199,130,209,189,2,151,1,7,110,103,32,116,101,120,116,161,199,130,209,189,2,152,1,1,132,199,130,209,189,2,159,1,7,110,103,32,116,101,120,116,161,199,130,209,189,2,160,1,1,132,199,130,209,189,2,167,1,7,110,103,32,116,101,120,116,161,199,130,209,189,2,168,1,1,132,199,130,209,189,2,175,1,7,110,103,32,116,101,120,116,161,199,130,209,189,2,176,1,1,132,199,130,209,189,2,183,1,7,110,103,32,116,101,120,116,161,199,130,209,189,2,184,1,1,132,199,130,209,189,2,191,1,7,110,103,32,116,101,120,116,161,199,130,209,189,2,192,1,1,132,199,130,209,189,2,199,1,7,110,103,32,116,101,120,116,161,199,130,209,189,2,200,1,1,132,199,130,209,189,2,207,1,7,110,103,32,116,101,120,116,161,199,130,209,189,2,208,1,1,132,199,130,209,189,2,215,1,7,110,103,32,116,101,120,116,161,199,130,209,189,2,216,1,1,132,199,130,209,189,2,223,1,7,110,103,32,116,101,120,116,161,199,130,209,189,2,224,1,1,132,199,130,209,189,2,231,1,7,110,103,32,116,101,120,116,161,199,130,209,189,2,232,1,1,132,199,130,209,189,2,239,1,7,110,103,32,116,101,120,116,161,199,130,209,189,2,240,1,1,132,199,130,209,189,2,247,1,7,110,103,32,116,101,120,116,161,199,130,209,189,2,248,1,1,132,199,130,209,189,2,255,1,7,110,103,32,116,101,120,116,161,199,130,209,189,2,128,2,1,132,199,130,209,189,2,135,2,7,110,103,32,116,101,120,116,161,199,130,209,189,2,136,2,1,132,199,130,209,189,2,143,2,7,110,103,32,116,101,120,116,161,199,130,209,189,2,144,2,1,132,199,130,209,189,2,151,2,7,110,103,32,116,101,120,116,161,199,130,209,189,2,152,2,1,132,199,130,209,189,2,159,2,7,110,103,32,116,101,120,116,161,199,130,209,189,2,160,2,1,132,199,130,209,189,2,167,2,7,110,103,32,116,101,120,116,161,199,130,209,189,2,168,2,1,132,199,130,209,189,2,175,2,7,110,103,32,116,101,120,116,161,199,130,209,189,2,176,2,1,132,199,130,209,189,2,183,2,7,110,103,32,116,101,120,116,161,199,130,209,189,2,184,2,1,132,199,130,209,189,2,191,2,7,110,103,32,116,101,120,116,161,199,130,209,189,2,192,2,1,161,199,130,209,189,2,107,1,39,0,204,195,206,156,1,4,6,55,74,97,87,111,56,2,39,0,204,195,206,156,1,1,6,54,87,56,99,101,88,1,40,0,199,130,209,189,2,203,2,2,105,100,1,119,6,54,87,56,99,101,88,40,0,199,130,209,189,2,203,2,2,116,121,1,119,9,116,111,100,111,95,108,105,115,116,40,0,199,130,209,189,2,203,2,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,199,130,209,189,2,203,2,8,99,104,105,108,100,114,101,110,1,119,6,105,55,111,99,51,56,33,0,199,130,209,189,2,203,2,4,100,97,116,97,1,40,0,199,130,209,189,2,203,2,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,199,130,209,189,2,203,2,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,105,55,111,99,51,56,0,200,199,130,209,189,2,106,198,223,206,159,1,116,1,119,6,54,87,56,99,101,88,161,199,130,209,189,2,200,2,1,132,199,130,209,189,2,94,1,32,161,199,130,209,189,2,201,2,1,129,199,130,209,189,2,214,2,1,161,199,130,209,189,2,215,2,1,129,199,130,209,189,2,216,2,1,161,199,130,209,189,2,217,2,1,129,199,130,209,189,2,218,2,1,161,199,130,209,189,2,219,2,1,129,199,130,209,189,2,220,2,1,161,199,130,209,189,2,221,2,5,129,199,130,209,189,2,222,2,1,161,199,130,209,189,2,227,2,1,129,199,130,209,189,2,228,2,1,161,199,130,209,189,2,229,2,1,129,199,130,209,189,2,230,2,1,161,199,130,209,189,2,231,2,1,129,199,130,209,189,2,232,2,1,161,199,130,209,189,2,233,2,1,129,199,130,209,189,2,234,2,1,161,199,130,209,189,2,235,2,1,129,199,130,209,189,2,236,2,1,161,199,130,209,189,2,237,2,1,129,199,130,209,189,2,238,2,1,161,199,130,209,189,2,239,2,1,134,199,130,209,189,2,240,2,7,102,111,114,109,117,108,97,9,34,102,111,114,109,117,108,97,34,132,199,130,209,189,2,242,2,1,36,134,199,130,209,189,2,243,2,7,102,111,114,109,117,108,97,4,110,117,108,108,168,199,130,209,189,2,241,2,1,119,108,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,99,104,101,99,107,101,100,32,116,111,100,111,32,108,105,115,116,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,102,111,114,109,117,108,97,34,58,34,102,111,114,109,117,108,97,34,125,44,34,105,110,115,101,114,116,34,58,34,36,34,125,93,44,34,99,104,101,99,107,101,100,34,58,116,114,117,101,125,1,0,199,130,209,189,2,202,2,1,161,199,130,209,189,2,208,2,1,129,199,130,209,189,2,246,2,1,161,199,130,209,189,2,247,2,1,129,199,130,209,189,2,248,2,1,161,199,130,209,189,2,249,2,1,129,199,130,209,189,2,250,2,1,161,199,130,209,189,2,251,2,1,129,199,130,209,189,2,252,2,1,161,199,130,209,189,2,253,2,1,129,199,130,209,189,2,254,2,1,161,199,130,209,189,2,255,2,1,129,199,130,209,189,2,128,3,1,161,199,130,209,189,2,129,3,8,132,199,130,209,189,2,130,3,1,119,161,199,130,209,189,2,138,3,1,132,199,130,209,189,2,139,3,1,105,161,199,130,209,189,2,140,3,1,132,199,130,209,189,2,141,3,1,116,161,199,130,209,189,2,142,3,1,132,199,130,209,189,2,143,3,1,104,161,199,130,209,189,2,144,3,1,39,0,204,195,206,156,1,4,6,55,99,74,88,114,112,2,39,0,204,195,206,156,1,1,6,86,111,54,70,109,81,1,40,0,199,130,209,189,2,148,3,2,105,100,1,119,6,86,111,54,70,109,81,40,0,199,130,209,189,2,148,3,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,199,130,209,189,2,148,3,6,112,97,114,101,110,116,1,119,10,77,48,104,84,99,67,120,66,88,82,40,0,199,130,209,189,2,148,3,8,99,104,105,108,100,114,101,110,1,119,6,106,82,78,118,55,111,33,0,199,130,209,189,2,148,3,4,100,97,116,97,1,40,0,199,130,209,189,2,148,3,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,199,130,209,189,2,148,3,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,106,82,78,118,55,111,0,136,171,236,222,251,5,206,3,1,119,6,86,111,54,70,109,81,4,0,199,130,209,189,2,147,3,4,240,159,152,131,168,199,130,209,189,2,153,3,1,119,29,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,240,159,152,131,34,125,93,125,39,0,204,195,206,156,1,4,6,103,89,121,119,78,121,2,33,0,204,195,206,156,1,1,6,84,67,99,110,98,71,1,0,7,33,0,204,195,206,156,1,3,6,82,117,68,55,67,100,1,193,204,195,206,156,1,232,1,198,223,206,159,1,149,1,1,39,0,204,195,206,156,1,1,6,101,77,66,121,99,80,1,40,0,199,130,209,189,2,172,3,2,105,100,1,119,6,101,77,66,121,99,80,40,0,199,130,209,189,2,172,3,2,116,121,1,119,7,111,117,116,108,105,110,101,40,0,199,130,209,189,2,172,3,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,199,130,209,189,2,172,3,8,99,104,105,108,100,114,101,110,1,119,6,112,72,116,98,67,52,33,0,199,130,209,189,2,172,3,4,100,97,116,97,1,40,0,199,130,209,189,2,172,3,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,199,130,209,189,2,172,3,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,112,72,116,98,67,52,0,200,204,195,206,156,1,232,1,199,130,209,189,2,171,3,1,119,6,101,77,66,121,99,80,39,0,204,195,206,156,1,4,6,95,97,88,90,88,80,2,33,0,204,195,206,156,1,1,6,81,89,119,70,83,48,1,0,7,33,0,204,195,206,156,1,3,6,88,110,80,104,117,89,1,193,199,130,209,189,2,171,3,198,223,206,159,1,149,1,1,39,0,204,195,206,156,1,4,6,110,90,88,77,89,67,2,39,0,204,195,206,156,1,4,6,85,99,120,53,52,69,2,39,0,204,195,206,156,1,4,6,69,82,71,102,107,88,2,39,0,204,195,206,156,1,4,6,71,108,50,57,116,102,2,39,0,204,195,206,156,1,1,6,120,49,100,100,111,87,1,40,0,199,130,209,189,2,197,3,2,105,100,1,119,6,120,49,100,100,111,87,40,0,199,130,209,189,2,197,3,2,116,121,1,119,5,116,97,98,108,101,40,0,199,130,209,189,2,197,3,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,199,130,209,189,2,197,3,8,99,104,105,108,100,114,101,110,1,119,6,100,49,110,86,107,119,33,0,199,130,209,189,2,197,3,4,100,97,116,97,1,40,0,199,130,209,189,2,197,3,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,199,130,209,189,2,197,3,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,100,49,110,86,107,119,0,200,199,130,209,189,2,171,3,199,130,209,189,2,192,3,1,119,6,120,49,100,100,111,87,39,0,204,195,206,156,1,1,6,121,57,72,73,118,95,1,40,0,199,130,209,189,2,207,3,2,105,100,1,119,6,121,57,72,73,118,95,40,0,199,130,209,189,2,207,3,2,116,121,1,119,10,116,97,98,108,101,47,99,101,108,108,40,0,199,130,209,189,2,207,3,6,112,97,114,101,110,116,1,119,6,120,49,100,100,111,87,40,0,199,130,209,189,2,207,3,8,99,104,105,108,100,114,101,110,1,119,6,78,104,69,49,119,116,33,0,199,130,209,189,2,207,3,4,100,97,116,97,1,40,0,199,130,209,189,2,207,3,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,199,130,209,189,2,207,3,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,78,104,69,49,119,116,0,8,0,199,130,209,189,2,205,3,1,119,6,121,57,72,73,118,95,39,0,204,195,206,156,1,1,6,48,83,82,103,66,118,1,40,0,199,130,209,189,2,217,3,2,105,100,1,119,6,48,83,82,103,66,118,40,0,199,130,209,189,2,217,3,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,199,130,209,189,2,217,3,6,112,97,114,101,110,116,1,119,6,121,57,72,73,118,95,40,0,199,130,209,189,2,217,3,8,99,104,105,108,100,114,101,110,1,119,6,107,108,100,67,117,111,33,0,199,130,209,189,2,217,3,4,100,97,116,97,1,40,0,199,130,209,189,2,217,3,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,199,130,209,189,2,217,3,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,107,108,100,67,117,111,0,8,0,199,130,209,189,2,215,3,1,119,6,48,83,82,103,66,118,39,0,204,195,206,156,1,1,6,95,90,90,78,53,99,1,40,0,199,130,209,189,2,227,3,2,105,100,1,119,6,95,90,90,78,53,99,40,0,199,130,209,189,2,227,3,2,116,121,1,119,10,116,97,98,108,101,47,99,101,108,108,40,0,199,130,209,189,2,227,3,6,112,97,114,101,110,116,1,119,6,120,49,100,100,111,87,40,0,199,130,209,189,2,227,3,8,99,104,105,108,100,114,101,110,1,119,6,103,89,69,98,121,107,33,0,199,130,209,189,2,227,3,4,100,97,116,97,1,40,0,199,130,209,189,2,227,3,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,199,130,209,189,2,227,3,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,103,89,69,98,121,107,0,136,199,130,209,189,2,216,3,1,119,6,95,90,90,78,53,99,39,0,204,195,206,156,1,1,6,77,106,74,57,74,76,1,40,0,199,130,209,189,2,237,3,2,105,100,1,119,6,77,106,74,57,74,76,40,0,199,130,209,189,2,237,3,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,199,130,209,189,2,237,3,6,112,97,114,101,110,116,1,119,6,95,90,90,78,53,99,40,0,199,130,209,189,2,237,3,8,99,104,105,108,100,114,101,110,1,119,6,95,102,81,84,95,110,33,0,199,130,209,189,2,237,3,4,100,97,116,97,1,40,0,199,130,209,189,2,237,3,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,199,130,209,189,2,237,3,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,95,102,81,84,95,110,0,8,0,199,130,209,189,2,235,3,1,119,6,77,106,74,57,74,76,39,0,204,195,206,156,1,1,6,78,77,45,104,67,70,1,40,0,199,130,209,189,2,247,3,2,105,100,1,119,6,78,77,45,104,67,70,40,0,199,130,209,189,2,247,3,2,116,121,1,119,10,116,97,98,108,101,47,99,101,108,108,40,0,199,130,209,189,2,247,3,6,112,97,114,101,110,116,1,119,6,120,49,100,100,111,87,40,0,199,130,209,189,2,247,3,8,99,104,105,108,100,114,101,110,1,119,6,117,118,68,83,80,101,33,0,199,130,209,189,2,247,3,4,100,97,116,97,1,40,0,199,130,209,189,2,247,3,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,199,130,209,189,2,247,3,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,117,118,68,83,80,101,0,136,199,130,209,189,2,236,3,1,119,6,78,77,45,104,67,70,39,0,204,195,206,156,1,1,6,69,70,66,45,52,82,1,40,0,199,130,209,189,2,129,4,2,105,100,1,119,6,69,70,66,45,52,82,40,0,199,130,209,189,2,129,4,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,199,130,209,189,2,129,4,6,112,97,114,101,110,116,1,119,6,78,77,45,104,67,70,40,0,199,130,209,189,2,129,4,8,99,104,105,108,100,114,101,110,1,119,6,81,81,77,50,48,66,33,0,199,130,209,189,2,129,4,4,100,97,116,97,1,40,0,199,130,209,189,2,129,4,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,199,130,209,189,2,129,4,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,81,81,77,50,48,66,0,8,0,199,130,209,189,2,255,3,1,119,6,69,70,66,45,52,82,39,0,204,195,206,156,1,1,6,98,100,95,105,68,101,1,40,0,199,130,209,189,2,139,4,2,105,100,1,119,6,98,100,95,105,68,101,40,0,199,130,209,189,2,139,4,2,116,121,1,119,10,116,97,98,108,101,47,99,101,108,108,40,0,199,130,209,189,2,139,4,6,112,97,114,101,110,116,1,119,6,120,49,100,100,111,87,40,0,199,130,209,189,2,139,4,8,99,104,105,108,100,114,101,110,1,119,6,105,80,89,69,52,56,33,0,199,130,209,189,2,139,4,4,100,97,116,97,1,40,0,199,130,209,189,2,139,4,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,199,130,209,189,2,139,4,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,105,80,89,69,52,56,0,136,199,130,209,189,2,128,4,1,119,6,98,100,95,105,68,101,39,0,204,195,206,156,1,1,6,55,51,88,69,103,80,1,40,0,199,130,209,189,2,149,4,2,105,100,1,119,6,55,51,88,69,103,80,40,0,199,130,209,189,2,149,4,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,199,130,209,189,2,149,4,6,112,97,114,101,110,116,1,119,6,98,100,95,105,68,101,40,0,199,130,209,189,2,149,4,8,99,104,105,108,100,114,101,110,1,119,6,115,45,80,102,105,89,33,0,199,130,209,189,2,149,4,4,100,97,116,97,1,40,0,199,130,209,189,2,149,4,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,199,130,209,189,2,149,4,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,115,45,80,102,105,89,0,8,0,199,130,209,189,2,147,4,1,119,6,55,51,88,69,103,80,161,199,130,209,189,2,202,3,1,4,0,199,130,209,189,2,193,3,1,56,161,199,130,209,189,2,222,3,1,132,199,130,209,189,2,160,4,1,56,161,199,130,209,189,2,161,4,1,132,199,130,209,189,2,162,4,1,56,161,199,130,209,189,2,163,4,1,132,199,130,209,189,2,164,4,1,56,161,199,130,209,189,2,165,4,1,132,199,130,209,189,2,166,4,1,56,161,199,130,209,189,2,167,4,1,4,0,199,130,209,189,2,196,3,1,57,161,199,130,209,189,2,154,4,1,132,199,130,209,189,2,170,4,1,57,161,199,130,209,189,2,171,4,1,132,199,130,209,189,2,172,4,1,57,161,199,130,209,189,2,173,4,1,132,199,130,209,189,2,174,4,1,57,161,199,130,209,189,2,175,4,1,0,4,39,0,204,195,206,156,1,4,6,56,85,53,118,100,78,2,39,0,204,195,206,156,1,1,6,78,99,104,45,81,78,1,40,0,199,130,209,189,2,183,4,2,105,100,1,119,6,78,99,104,45,81,78,40,0,199,130,209,189,2,183,4,2,116,121,1,119,13,98,117,108,108,101,116,101,100,95,108,105,115,116,40,0,199,130,209,189,2,183,4,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,199,130,209,189,2,183,4,8,99,104,105,108,100,114,101,110,1,119,6,122,97,90,84,55,68,33,0,199,130,209,189,2,183,4,4,100,97,116,97,1,40,0,199,130,209,189,2,183,4,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,199,130,209,189,2,183,4,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,122,97,90,84,55,68,0,200,204,195,206,156,1,246,1,204,195,206,156,1,247,1,1,119,6,78,99,104,45,81,78,1,0,199,130,209,189,2,182,4,1,161,199,130,209,189,2,188,4,1,129,199,130,209,189,2,193,4,1,161,199,130,209,189,2,194,4,1,129,199,130,209,189,2,195,4,1,161,199,130,209,189,2,196,4,1,39,0,204,195,206,156,1,4,6,75,102,57,98,106,87,2,33,0,204,195,206,156,1,1,6,119,73,53,75,113,116,1,0,7,33,0,204,195,206,156,1,3,6,115,76,56,78,88,117,1,193,204,195,206,156,1,247,1,204,195,206,156,1,248,1,1,39,0,204,195,206,156,1,4,6,48,72,111,66,111,70,2,39,0,204,195,206,156,1,1,6,84,67,90,121,70,52,1,40,0,199,130,209,189,2,211,4,2,105,100,1,119,6,84,67,90,121,70,52,40,0,199,130,209,189,2,211,4,2,116,121,1,119,13,98,117,108,108,101,116,101,100,95,108,105,115,116,40,0,199,130,209,189,2,211,4,6,112,97,114,101,110,116,1,119,6,78,99,104,45,81,78,40,0,199,130,209,189,2,211,4,8,99,104,105,108,100,114,101,110,1,119,6,108,99,89,77,103,95,33,0,199,130,209,189,2,211,4,4,100,97,116,97,1,40,0,199,130,209,189,2,211,4,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,199,130,209,189,2,211,4,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,108,99,89,77,103,95,0,8,0,199,130,209,189,2,191,4,1,119,6,84,67,90,121,70,52,1,0,199,130,209,189,2,210,4,1,161,199,130,209,189,2,216,4,1,129,199,130,209,189,2,221,4,1,161,199,130,209,189,2,222,4,1,129,199,130,209,189,2,223,4,1,161,199,130,209,189,2,224,4,1,39,0,204,195,206,156,1,4,6,108,73,54,101,68,85,2,33,0,204,195,206,156,1,1,6,67,118,56,72,55,83,1,0,7,33,0,204,195,206,156,1,3,6,107,119,71,100,66,65,1,129,199,130,209,189,2,220,4,1,39,0,204,195,206,156,1,4,6,57,83,80,71,121,88,2,39,0,204,195,206,156,1,1,6,52,119,120,102,90,72,1,40,0,199,130,209,189,2,239,4,2,105,100,1,119,6,52,119,120,102,90,72,40,0,199,130,209,189,2,239,4,2,116,121,1,119,13,98,117,108,108,101,116,101,100,95,108,105,115,116,40,0,199,130,209,189,2,239,4,6,112,97,114,101,110,116,1,119,6,84,67,90,121,70,52,40,0,199,130,209,189,2,239,4,8,99,104,105,108,100,114,101,110,1,119,6,118,103,105,70,69,106,33,0,199,130,209,189,2,239,4,4,100,97,116,97,1,40,0,199,130,209,189,2,239,4,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,199,130,209,189,2,239,4,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,118,103,105,70,69,106,0,8,0,199,130,209,189,2,219,4,1,119,6,52,119,120,102,90,72,1,0,199,130,209,189,2,238,4,1,161,199,130,209,189,2,244,4,1,129,199,130,209,189,2,249,4,1,161,199,130,209,189,2,250,4,1,129,199,130,209,189,2,251,4,1,161,199,130,209,189,2,252,4,1,39,0,204,195,206,156,1,4,6,106,81,55,52,49,100,2,39,0,204,195,206,156,1,1,6,109,102,89,53,57,121,1,40,0,199,130,209,189,2,128,5,2,105,100,1,119,6,109,102,89,53,57,121,40,0,199,130,209,189,2,128,5,2,116,121,1,119,13,98,117,108,108,101,116,101,100,95,108,105,115,116,40,0,199,130,209,189,2,128,5,6,112,97,114,101,110,116,1,119,6,84,67,90,121,70,52,40,0,199,130,209,189,2,128,5,8,99,104,105,108,100,114,101,110,1,119,6,71,116,121,76,66,108,33,0,199,130,209,189,2,128,5,4,100,97,116,97,1,40,0,199,130,209,189,2,128,5,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,199,130,209,189,2,128,5,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,71,116,121,76,66,108,0,136,199,130,209,189,2,248,4,1,119,6,109,102,89,53,57,121,1,0,199,130,209,189,2,255,4,1,161,199,130,209,189,2,133,5,1,129,199,130,209,189,2,138,5,1,161,199,130,209,189,2,139,5,1,129,199,130,209,189,2,140,5,1,161,199,130,209,189,2,141,5,1,39,0,204,195,206,156,1,4,6,99,82,52,54,74,83,2,33,0,204,195,206,156,1,1,6,95,95,82,90,106,107,1,0,7,33,0,204,195,206,156,1,3,6,117,113,87,99,122,50,1,129,199,130,209,189,2,137,5,1,4,0,199,130,209,189,2,144,5,1,49,0,1,132,199,130,209,189,2,155,5,1,50,0,1,132,199,130,209,189,2,157,5,1,51,0,1,39,0,204,195,206,156,1,4,6,84,108,76,116,78,119,2,1,0,199,130,209,189,2,161,5,3,39,0,204,195,206,156,1,1,6,87,57,68,108,99,56,1,40,0,199,130,209,189,2,165,5,2,105,100,1,119,6,87,57,68,108,99,56,40,0,199,130,209,189,2,165,5,2,116,121,1,119,13,98,117,108,108,101,116,101,100,95,108,105,115,116,40,0,199,130,209,189,2,165,5,6,112,97,114,101,110,116,1,119,6,78,99,104,45,81,78,40,0,199,130,209,189,2,165,5,8,99,104,105,108,100,114,101,110,1,119,6,71,113,89,119,74,81,33,0,199,130,209,189,2,165,5,4,100,97,116,97,1,40,0,199,130,209,189,2,165,5,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,199,130,209,189,2,165,5,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,71,113,89,119,74,81,0,136,199,130,209,189,2,237,4,1,119,6,87,57,68,108,99,56,129,199,130,209,189,2,164,5,1,161,199,130,209,189,2,170,5,1,129,199,130,209,189,2,175,5,1,161,199,130,209,189,2,176,5,1,129,199,130,209,189,2,177,5,1,161,199,130,209,189,2,178,5,1,39,0,204,195,206,156,1,4,6,105,45,118,52,52,66,2,33,0,204,195,206,156,1,1,6,116,72,104,110,105,69,1,0,7,33,0,204,195,206,156,1,3,6,79,69,107,76,69,106,1,129,199,130,209,189,2,174,5,1,1,0,199,130,209,189,2,181,5,1,0,1,129,199,130,209,189,2,192,5,1,0,3,132,199,130,209,189,2,194,5,1,62,0,1,39,0,204,195,206,156,1,4,6,76,115,116,55,78,103,2,39,0,204,195,206,156,1,1,6,113,90,76,56,88,88,1,40,0,199,130,209,189,2,201,5,2,105,100,1,119,6,113,90,76,56,88,88,40,0,199,130,209,189,2,201,5,2,116,121,1,119,11,116,111,103,103,108,101,95,108,105,115,116,40,0,199,130,209,189,2,201,5,6,112,97,114,101,110,116,1,119,6,78,99,104,45,81,78,40,0,199,130,209,189,2,201,5,8,99,104,105,108,100,114,101,110,1,119,6,49,98,68,104,69,101,33,0,199,130,209,189,2,201,5,4,100,97,116,97,1,40,0,199,130,209,189,2,201,5,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,199,130,209,189,2,201,5,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,49,98,68,104,69,101,0,200,199,130,209,189,2,174,5,199,130,209,189,2,191,5,1,119,6,113,90,76,56,88,88,1,0,199,130,209,189,2,200,5,1,161,199,130,209,189,2,206,5,1,129,199,130,209,189,2,211,5,1,161,199,130,209,189,2,212,5,1,129,199,130,209,189,2,213,5,1,161,199,130,209,189,2,214,5,1,39,0,204,195,206,156,1,4,6,88,103,107,99,56,110,2,39,0,204,195,206,156,1,1,6,105,66,98,109,87,48,1,40,0,199,130,209,189,2,218,5,2,105,100,1,119,6,105,66,98,109,87,48,40,0,199,130,209,189,2,218,5,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,199,130,209,189,2,218,5,6,112,97,114,101,110,116,1,119,6,113,90,76,56,88,88,40,0,199,130,209,189,2,218,5,8,99,104,105,108,100,114,101,110,1,119,6,72,95,104,114,75,71,33,0,199,130,209,189,2,218,5,4,100,97,116,97,1,40,0,199,130,209,189,2,218,5,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,199,130,209,189,2,218,5,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,72,95,104,114,75,71,0,8,0,199,130,209,189,2,209,5,1,119,6,105,66,98,109,87,48,161,199,130,209,189,2,216,5,1,1,0,199,130,209,189,2,217,5,1,161,199,130,209,189,2,223,5,1,129,199,130,209,189,2,229,5,1,161,199,130,209,189,2,230,5,1,129,199,130,209,189,2,231,5,1,161,199,130,209,189,2,232,5,1,39,0,204,195,206,156,1,4,6,52,90,104,112,86,73,2,33,0,204,195,206,156,1,1,6,78,79,116,108,71,74,1,0,7,33,0,204,195,206,156,1,3,6,52,107,104,115,81,48,1,129,199,130,209,189,2,227,5,1,39,0,204,195,206,156,1,4,6,74,98,106,103,98,105,2,39,0,204,195,206,156,1,1,6,55,71,119,105,74,83,1,40,0,199,130,209,189,2,247,5,2,105,100,1,119,6,55,71,119,105,74,83,40,0,199,130,209,189,2,247,5,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,199,130,209,189,2,247,5,6,112,97,114,101,110,116,1,119,6,105,66,98,109,87,48,40,0,199,130,209,189,2,247,5,8,99,104,105,108,100,114,101,110,1,119,6,99,53,87,50,53,102,33,0,199,130,209,189,2,247,5,4,100,97,116,97,1,40,0,199,130,209,189,2,247,5,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,199,130,209,189,2,247,5,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,99,53,87,50,53,102,0,8,0,199,130,209,189,2,226,5,1,119,6,55,71,119,105,74,83,1,0,199,130,209,189,2,246,5,1,161,199,130,209,189,2,252,5,1,129,199,130,209,189,2,129,6,1,161,199,130,209,189,2,130,6,1,129,199,130,209,189,2,131,6,1,161,199,130,209,189,2,132,6,1,39,0,204,195,206,156,1,4,6,118,87,56,68,45,102,2,33,0,204,195,206,156,1,1,6,99,88,73,114,105,45,1,0,7,33,0,204,195,206,156,1,3,6,122,70,104,98,74,88,1,129,199,130,209,189,2,128,6,1,39,0,204,195,206,156,1,4,6,86,65,80,82,86,55,2,33,0,204,195,206,156,1,1,6,99,72,102,57,114,111,1,0,7,33,0,204,195,206,156,1,3,6,70,112,56,103,98,56,1,129,199,130,209,189,2,245,5,1,4,0,199,130,209,189,2,146,6,1,49,0,1,132,199,130,209,189,2,157,6,1,50,0,1,132,199,130,209,189,2,159,6,1,51,0,1,39,0,204,195,206,156,1,4,6,95,70,68,79,103,89,2,4,0,199,130,209,189,2,163,6,3,49,50,51,33,0,204,195,206,156,1,1,6,84,69,81,71,120,89,1,0,7,33,0,204,195,206,156,1,3,6,72,120,102,70,78,49,1,129,199,130,209,189,2,191,5,1,68,199,130,209,189,2,193,4,1,98,161,199,130,209,189,2,198,4,1,132,199,130,209,189,2,197,4,1,117,161,199,130,209,189,2,178,6,1,129,199,130,209,189,2,179,6,1,132,199,130,209,189,2,181,6,1,108,161,199,130,209,189,2,180,6,2,132,199,130,209,189,2,182,6,1,108,161,199,130,209,189,2,184,6,1,132,199,130,209,189,2,185,6,1,101,161,199,130,209,189,2,186,6,1,132,199,130,209,189,2,187,6,1,116,161,199,130,209,189,2,188,6,1,132,199,130,209,189,2,189,6,1,101,161,199,130,209,189,2,190,6,1,132,199,130,209,189,2,191,6,1,100,161,199,130,209,189,2,192,6,1,132,199,130,209,189,2,193,6,1,32,161,199,130,209,189,2,194,6,1,132,199,130,209,189,2,195,6,1,108,161,199,130,209,189,2,196,6,1,132,199,130,209,189,2,197,6,1,105,161,199,130,209,189,2,198,6,1,132,199,130,209,189,2,199,6,1,115,161,199,130,209,189,2,200,6,1,132,199,130,209,189,2,201,6,1,116,168,199,130,209,189,2,202,6,1,119,38,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,98,117,108,108,101,116,101,100,32,108,105,115,116,34,125,93,125,68,199,130,209,189,2,221,4,1,99,161,199,130,209,189,2,226,4,1,132,199,130,209,189,2,225,4,1,104,161,199,130,209,189,2,206,6,1,132,199,130,209,189,2,207,6,1,105,161,199,130,209,189,2,208,6,1,132,199,130,209,189,2,209,6,1,108,161,199,130,209,189,2,210,6,1,132,199,130,209,189,2,211,6,1,100,161,199,130,209,189,2,212,6,1,129,199,130,209,189,2,213,6,1,161,199,130,209,189,2,214,6,2,132,199,130,209,189,2,215,6,1,45,161,199,130,209,189,2,217,6,1,132,199,130,209,189,2,218,6,1,49,168,199,130,209,189,2,219,6,1,119,32,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,99,104,105,108,100,45,49,34,125,93,125,68,199,130,209,189,2,249,4,1,99,161,199,130,209,189,2,254,4,1,132,199,130,209,189,2,253,4,1,104,161,199,130,209,189,2,223,6,1,132,199,130,209,189,2,224,6,1,105,161,199,130,209,189,2,225,6,1,132,199,130,209,189,2,226,6,1,108,161,199,130,209,189,2,227,6,1,132,199,130,209,189,2,228,6,1,100,161,199,130,209,189,2,229,6,1,132,199,130,209,189,2,230,6,1,45,161,199,130,209,189,2,231,6,1,132,199,130,209,189,2,232,6,1,49,161,199,130,209,189,2,233,6,1,132,199,130,209,189,2,234,6,1,45,161,199,130,209,189,2,235,6,1,132,199,130,209,189,2,236,6,1,49,168,199,130,209,189,2,237,6,1,119,34,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,99,104,105,108,100,45,49,45,49,34,125,93,125,68,199,130,209,189,2,138,5,1,99,161,199,130,209,189,2,143,5,1,132,199,130,209,189,2,142,5,1,104,161,199,130,209,189,2,241,6,1,132,199,130,209,189,2,242,6,1,105,161,199,130,209,189,2,243,6,1,132,199,130,209,189,2,244,6,1,108,161,199,130,209,189,2,245,6,1,132,199,130,209,189,2,246,6,1,100,161,199,130,209,189,2,247,6,1,132,199,130,209,189,2,248,6,1,45,161,199,130,209,189,2,249,6,1,132,199,130,209,189,2,250,6,1,49,161,199,130,209,189,2,251,6,1,132,199,130,209,189,2,252,6,1,45,161,199,130,209,189,2,253,6,1,132,199,130,209,189,2,254,6,1,50,168,199,130,209,189,2,255,6,1,119,34,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,99,104,105,108,100,45,49,45,50,34,125,93,125,68,199,130,209,189,2,162,5,1,99,161,199,130,209,189,2,180,5,1,132,199,130,209,189,2,179,5,1,104,161,199,130,209,189,2,131,7,1,132,199,130,209,189,2,132,7,1,105,161,199,130,209,189,2,133,7,1,132,199,130,209,189,2,134,7,1,108,161,199,130,209,189,2,135,7,1,132,199,130,209,189,2,136,7,1,100,161,199,130,209,189,2,137,7,1,132,199,130,209,189,2,138,7,1,45,161,199,130,209,189,2,139,7,1,132,199,130,209,189,2,140,7,1,50,168,199,130,209,189,2,141,7,1,119,32,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,99,104,105,108,100,45,50,34,125,93,125,65,199,130,209,189,2,211,5,1,161,199,130,209,189,2,228,5,1,129,199,130,209,189,2,215,5,1,161,199,130,209,189,2,145,7,1,129,199,130,209,189,2,146,7,1,161,199,130,209,189,2,147,7,1,129,199,130,209,189,2,148,7,1,161,199,130,209,189,2,149,7,1,129,199,130,209,189,2,150,7,1,161,199,130,209,189,2,151,7,1,129,199,130,209,189,2,152,7,1,161,199,130,209,189,2,153,7,1,129,199,130,209,189,2,154,7,1,161,199,130,209,189,2,155,7,8,132,199,130,209,189,2,156,7,1,116,161,199,130,209,189,2,164,7,1,132,199,130,209,189,2,165,7,1,111,161,199,130,209,189,2,166,7,1,132,199,130,209,189,2,167,7,1,103,161,199,130,209,189,2,168,7,1,132,199,130,209,189,2,169,7,1,103,161,199,130,209,189,2,170,7,1,132,199,130,209,189,2,171,7,1,108,161,199,130,209,189,2,172,7,1,132,199,130,209,189,2,173,7,1,101,161,199,130,209,189,2,174,7,1,132,199,130,209,189,2,175,7,1,32,161,199,130,209,189,2,176,7,1,132,199,130,209,189,2,177,7,1,108,161,199,130,209,189,2,178,7,1,132,199,130,209,189,2,179,7,1,105,161,199,130,209,189,2,180,7,1,132,199,130,209,189,2,181,7,1,115,161,199,130,209,189,2,182,7,1,132,199,130,209,189,2,183,7,1,116,168,199,130,209,189,2,184,7,1,119,54,123,34,99,111,108,108,97,112,115,101,100,34,58,102,97,108,115,101,44,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,116,111,103,103,108,101,32,108,105,115,116,34,125,93,125,68,199,130,209,189,2,229,5,1,99,161,199,130,209,189,2,234,5,1,132,199,130,209,189,2,233,5,1,104,161,199,130,209,189,2,188,7,1,132,199,130,209,189,2,189,7,1,105,161,199,130,209,189,2,190,7,1,132,199,130,209,189,2,191,7,1,108,161,199,130,209,189,2,192,7,1,132,199,130,209,189,2,193,7,1,100,161,199,130,209,189,2,194,7,1,132,199,130,209,189,2,195,7,1,45,161,199,130,209,189,2,196,7,1,129,199,130,209,189,2,197,7,1,161,199,130,209,189,2,198,7,2,132,199,130,209,189,2,199,7,1,49,168,199,130,209,189,2,201,7,1,119,32,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,99,104,105,108,100,45,49,34,125,93,125,68,199,130,209,189,2,129,6,1,99,161,199,130,209,189,2,134,6,1,132,199,130,209,189,2,133,6,1,104,161,199,130,209,189,2,205,7,1,132,199,130,209,189,2,206,7,1,105,161,199,130,209,189,2,207,7,1,132,199,130,209,189,2,208,7,1,108,161,199,130,209,189,2,209,7,1,132,199,130,209,189,2,210,7,1,100,161,199,130,209,189,2,211,7,1,132,199,130,209,189,2,212,7,1,45,161,199,130,209,189,2,213,7,1,132,199,130,209,189,2,214,7,1,49,161,199,130,209,189,2,215,7,1,129,199,130,209,189,2,216,7,1,161,199,130,209,189,2,217,7,1,129,199,130,209,189,2,218,7,1,161,199,130,209,189,2,219,7,3,129,199,130,209,189,2,220,7,1,161,199,130,209,189,2,223,7,1,129,199,130,209,189,2,224,7,1,161,199,130,209,189,2,225,7,3,132,199,130,209,189,2,226,7,1,45,161,199,130,209,189,2,229,7,1,132,199,130,209,189,2,230,7,1,49,168,199,130,209,189,2,231,7,1,119,34,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,99,104,105,108,100,45,49,45,49,34,125,93,125,39,0,204,195,206,156,1,4,6,55,88,55,105,70,103,2,39,0,204,195,206,156,1,1,6,101,79,68,109,108,65,1,40,0,199,130,209,189,2,235,7,2,105,100,1,119,6,101,79,68,109,108,65,40,0,199,130,209,189,2,235,7,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,199,130,209,189,2,235,7,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,199,130,209,189,2,235,7,8,99,104,105,108,100,114,101,110,1,119,6,109,112,74,69,74,90,33,0,199,130,209,189,2,235,7,4,100,97,116,97,1,40,0,199,130,209,189,2,235,7,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,199,130,209,189,2,235,7,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,109,112,74,69,74,90,0,200,204,195,206,156,1,242,1,204,195,206,156,1,243,1,1,119,6,101,79,68,109,108,65,168,204,195,206,156,1,164,1,1,119,79,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,75,101,121,98,111,97,114,100,32,115,104,111,114,116,99,117,116,115,44,32,109,97,114,107,100,111,119,110,44,32,97,110,100,32,99,111,100,101,32,98,108,111,99,107,34,125,93,44,34,108,101,118,101,108,34,58,50,125,168,204,195,206,156,1,165,1,1,119,10,97,98,100,49,105,117,71,81,109,68,168,204,195,206,156,1,166,1,1,119,4,116,101,120,116,1,0,199,130,209,189,2,234,7,1,161,199,130,209,189,2,240,7,1,168,199,130,209,189,2,249,7,1,119,12,123,34,100,101,108,116,97,34,58,91,93,125,132,199,130,209,189,2,48,1,32,161,199,130,209,189,2,61,1,129,199,130,209,189,2,251,7,1,161,199,130,209,189,2,252,7,1,134,199,130,209,189,2,253,7,7,109,101,110,116,105,111,110,51,123,34,116,121,112,101,34,58,34,100,97,116,101,34,44,34,100,97,116,101,34,58,34,50,48,50,52,45,48,52,45,49,56,84,49,52,58,50,53,58,51,50,46,52,53,55,50,55,55,34,125,132,199,130,209,189,2,255,7,1,36,134,199,130,209,189,2,128,8,7,109,101,110,116,105,111,110,4,110,117,108,108,161,199,130,209,189,2,254,7,1,132,199,130,209,189,2,129,8,1,109,161,199,130,209,189,2,130,8,1,132,199,130,209,189,2,131,8,1,101,161,199,130,209,189,2,132,8,1,132,199,130,209,189,2,133,8,1,110,161,199,130,209,189,2,134,8,1,129,199,130,209,189,2,135,8,1,132,199,130,209,189,2,137,8,1,116,161,199,130,209,189,2,136,8,2,1,236,158,128,159,2,0,161,219,200,174,197,9,24,4,1,245,181,155,135,2,0,161,151,234,142,238,11,26,23,176,1,146,216,250,133,2,0,161,243,138,171,183,10,60,1,161,243,138,171,183,10,61,1,161,243,138,171,183,10,62,1,161,243,138,171,183,10,71,1,161,243,138,171,183,10,63,1,161,243,138,171,183,10,64,1,161,243,138,171,183,10,65,1,161,146,216,250,133,2,3,1,161,243,138,171,183,10,67,1,161,243,138,171,183,10,68,1,161,243,138,171,183,10,69,1,161,146,216,250,133,2,7,1,161,243,138,171,183,10,84,1,161,243,138,171,183,10,85,1,161,243,138,171,183,10,86,1,161,243,138,171,183,10,95,3,161,243,138,171,183,10,91,1,161,243,138,171,183,10,92,1,161,243,138,171,183,10,93,1,161,243,138,171,183,10,87,1,161,243,138,171,183,10,88,1,161,243,138,171,183,10,89,1,161,243,138,171,183,10,96,1,161,243,138,171,183,10,97,1,161,243,138,171,183,10,98,1,161,243,138,171,183,10,107,1,161,243,138,171,183,10,100,1,161,243,138,171,183,10,101,1,161,243,138,171,183,10,102,1,161,146,216,250,133,2,27,1,161,243,138,171,183,10,104,1,161,243,138,171,183,10,105,1,161,243,138,171,183,10,106,1,161,146,216,250,133,2,31,1,161,243,138,171,183,10,108,1,161,243,138,171,183,10,109,1,161,243,138,171,183,10,110,1,161,243,138,171,183,10,119,1,161,243,138,171,183,10,112,1,161,243,138,171,183,10,113,1,161,243,138,171,183,10,114,1,161,146,216,250,133,2,39,1,161,243,138,171,183,10,116,1,161,243,138,171,183,10,117,1,161,243,138,171,183,10,118,1,161,146,216,250,133,2,43,1,161,243,138,171,183,10,120,1,161,243,138,171,183,10,121,1,161,243,138,171,183,10,122,1,161,243,138,171,183,10,123,1,161,243,138,171,183,10,124,1,161,243,138,171,183,10,125,1,161,243,138,171,183,10,131,1,2,161,243,138,171,183,10,127,1,161,243,138,171,183,10,128,1,1,161,243,138,171,183,10,129,1,1,161,146,216,250,133,2,55,1,161,243,138,171,183,10,132,1,1,161,243,138,171,183,10,133,1,1,161,243,138,171,183,10,134,1,1,161,243,138,171,183,10,143,1,1,161,243,138,171,183,10,136,1,1,161,243,138,171,183,10,137,1,1,161,243,138,171,183,10,138,1,1,161,146,216,250,133,2,63,1,161,243,138,171,183,10,140,1,1,161,243,138,171,183,10,141,1,1,161,243,138,171,183,10,142,1,1,161,146,216,250,133,2,67,1,161,243,138,171,183,10,144,1,1,161,243,138,171,183,10,145,1,1,161,243,138,171,183,10,146,1,1,161,243,138,171,183,10,155,1,1,161,243,138,171,183,10,148,1,1,161,243,138,171,183,10,149,1,1,161,243,138,171,183,10,150,1,1,161,146,216,250,133,2,75,1,161,243,138,171,183,10,152,1,1,161,243,138,171,183,10,153,1,1,161,243,138,171,183,10,154,1,1,161,146,216,250,133,2,79,1,161,243,138,171,183,10,156,1,1,161,243,138,171,183,10,157,1,1,161,243,138,171,183,10,158,1,1,161,243,138,171,183,10,167,1,1,161,243,138,171,183,10,160,1,1,161,243,138,171,183,10,161,1,1,161,243,138,171,183,10,162,1,1,161,146,216,250,133,2,87,1,161,243,138,171,183,10,164,1,1,161,243,138,171,183,10,165,1,1,161,243,138,171,183,10,166,1,1,161,146,216,250,133,2,91,1,161,243,138,171,183,10,168,1,1,161,243,138,171,183,10,169,1,1,161,243,138,171,183,10,170,1,1,161,243,138,171,183,10,179,1,1,161,243,138,171,183,10,176,1,1,161,243,138,171,183,10,177,1,1,161,243,138,171,183,10,178,1,1,161,146,216,250,133,2,99,1,161,243,138,171,183,10,172,1,1,161,243,138,171,183,10,173,1,1,161,243,138,171,183,10,174,1,1,161,146,216,250,133,2,103,1,161,243,138,171,183,10,180,1,1,161,243,138,171,183,10,181,1,1,161,243,138,171,183,10,182,1,1,161,243,138,171,183,10,191,1,1,161,243,138,171,183,10,188,1,1,161,243,138,171,183,10,189,1,1,161,243,138,171,183,10,190,1,1,161,146,216,250,133,2,111,1,161,243,138,171,183,10,184,1,1,161,243,138,171,183,10,185,1,1,161,243,138,171,183,10,186,1,1,161,146,216,250,133,2,115,1,161,243,138,171,183,10,192,1,1,161,243,138,171,183,10,193,1,1,161,243,138,171,183,10,194,1,1,161,243,138,171,183,10,203,1,1,161,243,138,171,183,10,196,1,1,161,243,138,171,183,10,197,1,1,161,243,138,171,183,10,198,1,1,161,146,216,250,133,2,123,1,161,243,138,171,183,10,200,1,1,161,243,138,171,183,10,201,1,1,161,243,138,171,183,10,202,1,1,161,146,216,250,133,2,127,1,161,243,138,171,183,10,204,1,1,161,243,138,171,183,10,205,1,1,161,243,138,171,183,10,206,1,1,161,243,138,171,183,10,215,1,1,161,243,138,171,183,10,209,1,1,161,243,138,171,183,10,210,1,1,161,243,138,171,183,10,211,1,1,161,146,216,250,133,2,135,1,1,161,243,138,171,183,10,212,1,1,161,243,138,171,183,10,213,1,1,161,243,138,171,183,10,214,1,1,161,146,216,250,133,2,139,1,1,161,243,138,171,183,10,216,1,1,161,243,138,171,183,10,217,1,1,161,243,138,171,183,10,218,1,1,161,243,138,171,183,10,227,1,1,161,243,138,171,183,10,220,1,1,161,243,138,171,183,10,221,1,1,161,243,138,171,183,10,222,1,1,161,146,216,250,133,2,147,1,1,161,243,138,171,183,10,224,1,1,161,243,138,171,183,10,225,1,1,161,243,138,171,183,10,226,1,1,161,146,216,250,133,2,151,1,1,161,243,138,171,183,10,228,1,1,161,243,138,171,183,10,229,1,1,161,243,138,171,183,10,230,1,1,161,243,138,171,183,10,239,1,1,161,243,138,171,183,10,232,1,1,161,243,138,171,183,10,233,1,1,161,243,138,171,183,10,234,1,1,161,146,216,250,133,2,159,1,1,161,243,138,171,183,10,236,1,1,161,243,138,171,183,10,237,1,1,161,243,138,171,183,10,238,1,1,161,146,216,250,133,2,163,1,1,161,146,216,250,133,2,156,1,1,161,146,216,250,133,2,157,1,1,161,146,216,250,133,2,158,1,1,161,146,216,250,133,2,160,1,1,161,146,216,250,133,2,161,1,1,161,146,216,250,133,2,162,1,1,161,146,216,250,133,2,167,1,1,161,146,216,250,133,2,164,1,1,161,146,216,250,133,2,165,1,1,161,146,216,250,133,2,166,1,1,161,146,216,250,133,2,174,1,2,9,172,254,181,239,1,0,39,0,204,195,206,156,1,4,6,108,45,56,109,101,45,2,4,0,172,254,181,239,1,0,4,104,106,107,100,161,198,223,206,159,1,153,1,1,132,172,254,181,239,1,4,2,39,100,161,172,254,181,239,1,5,1,132,172,254,181,239,1,7,2,39,100,161,172,254,181,239,1,8,1,132,172,254,181,239,1,10,2,39,100,161,172,254,181,239,1,11,1,1,153,236,182,220,1,0,161,195,254,251,180,11,57,4,10,155,213,159,176,1,0,161,131,182,180,202,12,50,1,161,131,182,180,202,12,51,1,161,131,182,180,202,12,52,1,161,155,213,159,176,1,0,1,161,155,213,159,176,1,1,1,161,155,213,159,176,1,2,1,129,131,182,180,202,12,43,1,161,155,213,159,176,1,3,1,161,155,213,159,176,1,4,1,161,155,213,159,176,1,5,1,179,1,198,223,206,159,1,0,39,0,204,195,206,156,1,4,6,57,70,53,89,108,75,2,4,0,198,223,206,159,1,0,13,103,104,104,104,229,143,145,230,140,165,229,165,189,168,171,236,222,251,5,166,2,1,119,38,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,103,104,104,104,229,143,145,230,140,165,229,165,189,34,125,93,125,39,0,204,195,206,156,1,4,6,89,50,51,82,99,105,2,4,0,198,223,206,159,1,9,12,229,185,178,230,180,187,229,147,136,229,147,136,168,171,236,222,251,5,240,3,1,119,37,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,229,185,178,230,180,187,229,147,136,229,147,136,34,125,93,125,0,3,39,0,204,195,206,156,1,4,6,72,90,117,111,112,102,2,33,0,204,195,206,156,1,1,6,67,102,80,66,48,85,1,0,7,33,0,204,195,206,156,1,3,6,81,117,121,48,102,66,1,193,171,236,222,251,5,196,1,171,236,222,251,5,170,2,1,1,0,198,223,206,159,1,18,3,0,2,129,198,223,206,159,1,31,1,0,4,39,0,204,195,206,156,1,4,6,48,82,103,55,103,55,2,33,0,204,195,206,156,1,1,6,72,51,76,88,97,79,1,0,7,33,0,204,195,206,156,1,3,6,75,83,56,80,116,80,1,193,171,236,222,251,5,196,1,198,223,206,159,1,28,1,39,0,204,195,206,156,1,4,6,105,90,51,118,76,100,2,33,0,204,195,206,156,1,1,6,121,102,76,72,69,119,1,0,7,33,0,204,195,206,156,1,3,6,48,80,108,53,77,98,1,193,171,236,222,251,5,196,1,198,223,206,159,1,49,1,39,0,204,195,206,156,1,4,6,72,97,76,66,45,86,2,33,0,204,195,206,156,1,1,6,98,65,77,76,51,82,1,0,7,33,0,204,195,206,156,1,3,6,81,83,99,52,51,111,1,193,171,236,222,251,5,196,1,198,223,206,159,1,60,1,39,0,204,195,206,156,1,4,6,98,86,122,115,102,101,2,39,0,204,195,206,156,1,1,6,52,90,113,105,51,76,1,40,0,198,223,206,159,1,73,2,105,100,1,119,6,52,90,113,105,51,76,40,0,198,223,206,159,1,73,2,116,121,1,119,5,113,117,111,116,101,40,0,198,223,206,159,1,73,6,112,97,114,101,110,116,1,119,10,77,48,104,84,99,67,120,66,88,82,40,0,198,223,206,159,1,73,8,99,104,105,108,100,114,101,110,1,119,6,98,50,103,102,70,95,33,0,198,223,206,159,1,73,4,100,97,116,97,1,40,0,198,223,206,159,1,73,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,198,223,206,159,1,73,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,98,50,103,102,70,95,0,200,171,236,222,251,5,196,1,198,223,206,159,1,71,1,119,6,52,90,113,105,51,76,4,0,198,223,206,159,1,72,6,231,155,145,230,142,167,168,198,223,206,159,1,78,1,119,31,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,231,155,145,230,142,167,34,125,93,125,193,204,195,206,156,1,244,5,204,195,206,156,1,245,5,3,161,204,195,206,156,1,137,1,1,161,204,195,206,156,1,138,1,1,161,204,195,206,156,1,139,1,1,39,0,204,195,206,156,1,4,6,50,101,101,116,51,53,2,33,0,204,195,206,156,1,1,6,77,103,77,119,109,49,1,0,7,33,0,204,195,206,156,1,3,6,52,76,51,66,86,49,1,193,204,195,206,156,1,232,1,204,195,206,156,1,233,1,1,0,3,39,0,204,195,206,156,1,4,6,100,87,119,54,116,114,2,39,0,204,195,206,156,1,1,6,77,89,55,45,90,70,1,40,0,198,223,206,159,1,107,2,105,100,1,119,6,77,89,55,45,90,70,40,0,198,223,206,159,1,107,2,116,121,1,119,5,113,117,111,116,101,40,0,198,223,206,159,1,107,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,198,223,206,159,1,107,8,99,104,105,108,100,114,101,110,1,119,6,112,88,122,66,110,100,33,0,198,223,206,159,1,107,4,100,97,116,97,1,40,0,198,223,206,159,1,107,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,198,223,206,159,1,107,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,112,88,122,66,110,100,0,200,204,195,206,156,1,232,1,198,223,206,159,1,102,1,119,6,77,89,55,45,90,70,4,0,198,223,206,159,1,106,9,229,144,140,228,184,128,228,184,170,161,198,223,206,159,1,112,1,132,198,223,206,159,1,119,3,106,106,106,161,198,223,206,159,1,120,1,39,0,204,195,206,156,1,4,6,71,121,120,95,72,54,2,33,0,204,195,206,156,1,1,6,83,101,74,81,114,75,1,0,7,33,0,204,195,206,156,1,3,6,122,116,99,78,71,87,1,193,204,195,206,156,1,232,1,198,223,206,159,1,116,1,0,3,39,0,204,195,206,156,1,4,6,51,107,108,102,97,80,2,39,0,204,195,206,156,1,1,6,85,72,48,53,51,70,1,40,0,198,223,206,159,1,140,1,2,105,100,1,119,6,85,72,48,53,51,70,40,0,198,223,206,159,1,140,1,2,116,121,1,119,9,116,111,100,111,95,108,105,115,116,40,0,198,223,206,159,1,140,1,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,198,223,206,159,1,140,1,8,99,104,105,108,100,114,101,110,1,119,6,52,75,90,73,113,76,33,0,198,223,206,159,1,140,1,4,100,97,116,97,1,40,0,198,223,206,159,1,140,1,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,198,223,206,159,1,140,1,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,52,75,90,73,113,76,0,200,204,195,206,156,1,232,1,198,223,206,159,1,135,1,1,119,6,85,72,48,53,51,70,4,0,198,223,206,159,1,139,1,3,104,106,107,161,198,223,206,159,1,145,1,1,39,0,204,195,206,156,1,1,6,114,78,78,65,105,82,1,40,0,198,223,206,159,1,154,1,2,105,100,1,119,6,114,78,78,65,105,82,40,0,198,223,206,159,1,154,1,2,116,121,1,119,13,109,97,116,104,95,101,113,117,97,116,105,111,110,40,0,198,223,206,159,1,154,1,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,198,223,206,159,1,154,1,8,99,104,105,108,100,114,101,110,1,119,6,69,82,69,45,78,66,33,0,198,223,206,159,1,154,1,4,100,97,116,97,1,40,0,198,223,206,159,1,154,1,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,198,223,206,159,1,154,1,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,69,82,69,45,78,66,0,200,204,195,206,156,1,240,1,204,195,206,156,1,241,1,1,119,6,114,78,78,65,105,82,168,198,223,206,159,1,159,1,1,119,24,123,34,102,111,114,109,117,108,97,34,58,34,105,231,156,139,231,187,143,230,181,142,34,125,39,0,204,195,206,156,1,1,6,68,114,122,68,111,83,1,40,0,198,223,206,159,1,165,1,2,105,100,1,119,6,68,114,122,68,111,83,40,0,198,223,206,159,1,165,1,2,116,121,1,119,5,105,109,97,103,101,40,0,198,223,206,159,1,165,1,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,198,223,206,159,1,165,1,8,99,104,105,108,100,114,101,110,1,119,6,57,68,97,108,108,97,33,0,198,223,206,159,1,165,1,4,100,97,116,97,1,40,0,198,223,206,159,1,165,1,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,198,223,206,159,1,165,1,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,57,68,97,108,108,97,0,200,204,195,206,156,1,251,1,204,195,206,156,1,252,1,1,119,6,68,114,122,68,111,83,161,198,223,206,159,1,170,1,1,39,0,204,195,206,156,1,4,6,102,80,55,52,75,113,2,33,0,204,195,206,156,1,1,6,84,120,69,107,78,52,1,0,7,33,0,204,195,206,156,1,3,6,104,109,65,56,45,115,1,193,204,195,206,156,1,244,1,204,195,206,156,1,245,1,1,39,0,204,195,206,156,1,4,6,118,105,52,104,122,104,2,39,0,204,195,206,156,1,1,6,95,98,119,81,76,101,1,40,0,198,223,206,159,1,188,1,2,105,100,1,119,6,95,98,119,81,76,101,40,0,198,223,206,159,1,188,1,2,116,121,1,119,13,110,117,109,98,101,114,101,100,95,108,105,115,116,40,0,198,223,206,159,1,188,1,6,112,97,114,101,110,116,1,119,10,101,110,68,45,73,83,100,100,99,55,40,0,198,223,206,159,1,188,1,8,99,104,105,108,100,114,101,110,1,119,6,104,102,109,108,88,52,33,0,198,223,206,159,1,188,1,4,100,97,116,97,1,40,0,198,223,206,159,1,188,1,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,198,223,206,159,1,188,1,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,104,102,109,108,88,52,0,8,0,204,195,206,156,1,23,1,119,6,95,98,119,81,76,101,4,0,198,223,206,159,1,187,1,3,105,106,106,168,198,223,206,159,1,193,1,1,119,28,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,105,106,106,34,125,93,125,39,0,204,195,206,156,1,4,6,55,83,79,113,80,69,2,33,0,204,195,206,156,1,1,6,75,119,55,52,104,73,1,0,7,33,0,204,195,206,156,1,3,6,80,82,74,72,65,95,1,129,198,223,206,159,1,197,1,1,39,0,204,195,206,156,1,4,6,78,97,78,121,113,76,2,39,0,204,195,206,156,1,1,6,72,90,88,98,113,104,1,40,0,198,223,206,159,1,214,1,2,105,100,1,119,6,72,90,88,98,113,104,40,0,198,223,206,159,1,214,1,2,116,121,1,119,13,110,117,109,98,101,114,101,100,95,108,105,115,116,40,0,198,223,206,159,1,214,1,6,112,97,114,101,110,116,1,119,6,95,98,119,81,76,101,40,0,198,223,206,159,1,214,1,8,99,104,105,108,100,114,101,110,1,119,6,110,98,72,85,90,106,33,0,198,223,206,159,1,214,1,4,100,97,116,97,1,40,0,198,223,206,159,1,214,1,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,198,223,206,159,1,214,1,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,110,98,72,85,90,106,0,8,0,198,223,206,159,1,196,1,1,119,6,72,90,88,98,113,104,4,0,198,223,206,159,1,213,1,4,106,107,110,98,168,198,223,206,159,1,219,1,1,119,29,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,106,107,110,98,34,125,93,125,39,0,204,195,206,156,1,4,6,57,56,55,97,106,50,2,33,0,204,195,206,156,1,1,6,110,117,56,75,122,68,1,0,7,33,0,204,195,206,156,1,3,6,85,56,79,113,105,78,1,129,198,223,206,159,1,223,1,1,39,0,204,195,206,156,1,4,6,88,116,82,99,45,53,2,39,0,204,195,206,156,1,1,6,88,52,88,118,49,84,1,40,0,198,223,206,159,1,241,1,2,105,100,1,119,6,88,52,88,118,49,84,40,0,198,223,206,159,1,241,1,2,116,121,1,119,13,110,117,109,98,101,114,101,100,95,108,105,115,116,40,0,198,223,206,159,1,241,1,6,112,97,114,101,110,116,1,119,6,72,90,88,98,113,104,40,0,198,223,206,159,1,241,1,8,99,104,105,108,100,114,101,110,1,119,6,119,77,90,48,100,71,33,0,198,223,206,159,1,241,1,4,100,97,116,97,1,40,0,198,223,206,159,1,241,1,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,198,223,206,159,1,241,1,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,119,77,90,48,100,71,0,8,0,198,223,206,159,1,222,1,1,119,6,88,52,88,118,49,84,4,0,198,223,206,159,1,240,1,6,232,191,155,230,173,165,161,198,223,206,159,1,246,1,1,132,198,223,206,159,1,252,1,6,230,156,186,228,188,154,168,198,223,206,159,1,253,1,1,119,37,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,232,191,155,230,173,165,230,156,186,228,188,154,34,125,93,125,39,0,204,195,206,156,1,4,6,121,85,99,121,82,100,2,39,0,204,195,206,156,1,1,6,100,121,76,82,53,100,1,40,0,198,223,206,159,1,130,2,2,105,100,1,119,6,100,121,76,82,53,100,40,0,198,223,206,159,1,130,2,2,116,121,1,119,13,110,117,109,98,101,114,101,100,95,108,105,115,116,40,0,198,223,206,159,1,130,2,6,112,97,114,101,110,116,1,119,6,72,90,88,98,113,104,40,0,198,223,206,159,1,130,2,8,99,104,105,108,100,114,101,110,1,119,6,55,89,79,70,48,116,33,0,198,223,206,159,1,130,2,4,100,97,116,97,1,40,0,198,223,206,159,1,130,2,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,198,223,206,159,1,130,2,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,55,89,79,70,48,116,0,136,198,223,206,159,1,250,1,1,119,6,100,121,76,82,53,100,4,0,198,223,206,159,1,129,2,12,230,150,164,230,150,164,232,174,161,232,190,131,168,198,223,206,159,1,135,2,1,119,37,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,230,150,164,230,150,164,232,174,161,232,190,131,34,125,93,125,231,2,204,195,206,156,1,0,39,1,4,100,97,116,97,8,100,111,99,117,109,101,110,116,1,39,0,204,195,206,156,1,0,6,98,108,111,99,107,115,1,39,0,204,195,206,156,1,0,4,109,101,116,97,1,39,0,204,195,206,156,1,2,12,99,104,105,108,100,114,101,110,95,109,97,112,1,39,0,204,195,206,156,1,2,8,116,101,120,116,95,109,97,112,1,40,0,204,195,206,156,1,0,7,112,97,103,101,95,105,100,1,119,10,109,54,120,76,118,72,89,48,76,107,39,0,204,195,206,156,1,1,10,77,48,104,84,99,67,120,66,88,82,1,40,0,204,195,206,156,1,6,2,105,100,1,119,10,77,48,104,84,99,67,120,66,88,82,40,0,204,195,206,156,1,6,2,116,121,1,119,9,116,111,100,111,95,108,105,115,116,40,0,204,195,206,156,1,6,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,204,195,206,156,1,6,8,99,104,105,108,100,114,101,110,1,119,10,49,87,78,107,89,75,118,109,105,50,33,0,204,195,206,156,1,6,4,100,97,116,97,1,33,0,204,195,206,156,1,6,11,101,120,116,101,114,110,97,108,95,105,100,1,33,0,204,195,206,156,1,6,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,39,0,204,195,206,156,1,3,10,49,87,78,107,89,75,118,109,105,50,0,39,0,204,195,206,156,1,1,10,101,110,68,45,73,83,100,100,99,55,1,40,0,204,195,206,156,1,15,2,105,100,1,119,10,101,110,68,45,73,83,100,100,99,55,40,0,204,195,206,156,1,15,2,116,121,1,119,13,110,117,109,98,101,114,101,100,95,108,105,115,116,40,0,204,195,206,156,1,15,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,204,195,206,156,1,15,8,99,104,105,108,100,114,101,110,1,119,10,103,106,110,76,109,66,89,118,68,65,40,0,204,195,206,156,1,15,4,100,97,116,97,1,119,2,123,125,40,0,204,195,206,156,1,15,11,101,120,116,101,114,110,97,108,95,105,100,1,119,10,102,56,54,108,88,117,88,74,101,54,40,0,204,195,206,156,1,15,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,119,4,116,101,120,116,39,0,204,195,206,156,1,3,10,103,106,110,76,109,66,89,118,68,65,0,39,0,204,195,206,156,1,1,10,113,115,110,89,82,48,74,72,74,56,1,40,0,204,195,206,156,1,24,2,105,100,1,119,10,113,115,110,89,82,48,74,72,74,56,40,0,204,195,206,156,1,24,2,116,121,1,119,9,116,111,100,111,95,108,105,115,116,40,0,204,195,206,156,1,24,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,204,195,206,156,1,24,8,99,104,105,108,100,114,101,110,1,119,10,116,79,53,122,78,78,73,82,69,100,40,0,204,195,206,156,1,24,4,100,97,116,97,1,119,17,123,34,99,104,101,99,107,101,100,34,58,102,97,108,115,101,125,40,0,204,195,206,156,1,24,11,101,120,116,101,114,110,97,108,95,105,100,1,119,10,51,72,115,115,121,121,66,84,57,50,40,0,204,195,206,156,1,24,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,119,4,116,101,120,116,39,0,204,195,206,156,1,3,10,116,79,53,122,78,78,73,82,69,100,0,39,0,204,195,206,156,1,1,10,75,54,50,76,100,101,119,53,95,121,1,40,0,204,195,206,156,1,33,2,105,100,1,119,10,75,54,50,76,100,101,119,53,95,121,40,0,204,195,206,156,1,33,2,116,121,1,119,7,104,101,97,100,105,110,103,40,0,204,195,206,156,1,33,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,204,195,206,156,1,33,8,99,104,105,108,100,114,101,110,1,119,10,57,118,109,120,98,73,71,120,109,73,40,0,204,195,206,156,1,33,4,100,97,116,97,1,119,11,123,34,108,101,118,101,108,34,58,50,125,40,0,204,195,206,156,1,33,11,101,120,116,101,114,110,97,108,95,105,100,1,119,10,72,116,114,88,117,57,102,65,95,107,40,0,204,195,206,156,1,33,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,119,4,116,101,120,116,39,0,204,195,206,156,1,3,10,57,118,109,120,98,73,71,120,109,73,0,39,0,204,195,206,156,1,1,10,117,51,120,66,95,83,69,116,53,68,1,40,0,204,195,206,156,1,42,2,105,100,1,119,10,117,51,120,66,95,83,69,116,53,68,40,0,204,195,206,156,1,42,2,116,121,1,119,7,99,97,108,108,111,117,116,40,0,204,195,206,156,1,42,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,204,195,206,156,1,42,8,99,104,105,108,100,114,101,110,1,119,10,50,88,118,55,52,84,105,73,70,108,40,0,204,195,206,156,1,42,4,100,97,116,97,1,119,15,123,34,105,99,111,110,34,58,34,240,159,165,176,34,125,40,0,204,195,206,156,1,42,11,101,120,116,101,114,110,97,108,95,105,100,1,119,10,108,119,101,104,75,79,117,78,68,67,40,0,204,195,206,156,1,42,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,119,4,116,101,120,116,39,0,204,195,206,156,1,3,10,50,88,118,55,52,84,105,73,70,108,0,39,0,204,195,206,156,1,1,10,78,73,76,105,97,84,121,72,108,112,1,40,0,204,195,206,156,1,51,2,105,100,1,119,10,78,73,76,105,97,84,121,72,108,112,40,0,204,195,206,156,1,51,2,116,121,1,119,5,113,117,111,116,101,40,0,204,195,206,156,1,51,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,204,195,206,156,1,51,8,99,104,105,108,100,114,101,110,1,119,10,109,82,95,75,65,57,45,108,110,78,33,0,204,195,206,156,1,51,4,100,97,116,97,1,33,0,204,195,206,156,1,51,11,101,120,116,101,114,110,97,108,95,105,100,1,33,0,204,195,206,156,1,51,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,39,0,204,195,206,156,1,3,10,109,82,95,75,65,57,45,108,110,78,0,33,0,204,195,206,156,1,1,10,99,108,78,111,66,75,99,119,73,82,1,0,7,33,0,204,195,206,156,1,3,10,117,117,65,100,55,95,119,72,72,106,1,39,0,204,195,206,156,1,1,10,78,89,54,108,121,101,57,108,88,51,1,40,0,204,195,206,156,1,69,2,105,100,1,119,10,78,89,54,108,121,101,57,108,88,51,40,0,204,195,206,156,1,69,2,116,121,1,119,9,116,111,100,111,95,108,105,115,116,40,0,204,195,206,156,1,69,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,204,195,206,156,1,69,8,99,104,105,108,100,114,101,110,1,119,10,108,77,72,53,73,113,54,77,68,78,40,0,204,195,206,156,1,69,4,100,97,116,97,1,119,17,123,34,99,104,101,99,107,101,100,34,58,102,97,108,115,101,125,40,0,204,195,206,156,1,69,11,101,120,116,101,114,110,97,108,95,105,100,1,119,10,109,69,119,56,90,66,102,95,100,68,40,0,204,195,206,156,1,69,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,119,4,116,101,120,116,39,0,204,195,206,156,1,3,10,108,77,72,53,73,113,54,77,68,78,0,39,0,204,195,206,156,1,1,10,101,104,73,115,79,74,69,114,55,73,1,40,0,204,195,206,156,1,78,2,105,100,1,119,10,101,104,73,115,79,74,69,114,55,73,40,0,204,195,206,156,1,78,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,204,195,206,156,1,78,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,204,195,206,156,1,78,8,99,104,105,108,100,114,101,110,1,119,10,56,116,67,52,100,103,121,98,57,55,33,0,204,195,206,156,1,78,4,100,97,116,97,1,33,0,204,195,206,156,1,78,11,101,120,116,101,114,110,97,108,95,105,100,1,33,0,204,195,206,156,1,78,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,39,0,204,195,206,156,1,3,10,56,116,67,52,100,103,121,98,57,55,0,39,0,204,195,206,156,1,1,10,68,90,114,95,72,118,106,65,78,107,1,40,0,204,195,206,156,1,87,2,105,100,1,119,10,68,90,114,95,72,118,106,65,78,107,40,0,204,195,206,156,1,87,2,116,121,1,119,9,116,111,100,111,95,108,105,115,116,40,0,204,195,206,156,1,87,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,204,195,206,156,1,87,8,99,104,105,108,100,114,101,110,1,119,10,119,95,65,55,90,114,77,89,86,122,40,0,204,195,206,156,1,87,4,100,97,116,97,1,119,17,123,34,99,104,101,99,107,101,100,34,58,102,97,108,115,101,125,40,0,204,195,206,156,1,87,11,101,120,116,101,114,110,97,108,95,105,100,1,119,10,54,74,108,118,72,71,53,111,120,90,40,0,204,195,206,156,1,87,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,119,4,116,101,120,116,39,0,204,195,206,156,1,3,10,119,95,65,55,90,114,77,89,86,122,0,33,0,204,195,206,156,1,1,10,105,90,113,50,95,68,72,49,50,69,1,0,7,33,0,204,195,206,156,1,3,10,119,54,53,71,114,77,54,109,119,69,1,39,0,204,195,206,156,1,1,10,48,105,122,109,122,95,86,65,55,70,1,40,0,204,195,206,156,1,105,2,105,100,1,119,10,48,105,122,109,122,95,86,65,55,70,40,0,204,195,206,156,1,105,2,116,121,1,119,13,110,117,109,98,101,114,101,100,95,108,105,115,116,40,0,204,195,206,156,1,105,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,204,195,206,156,1,105,8,99,104,105,108,100,114,101,110,1,119,10,65,90,49,50,53,79,88,51,65,97,40,0,204,195,206,156,1,105,4,100,97,116,97,1,119,2,123,125,40,0,204,195,206,156,1,105,11,101,120,116,101,114,110,97,108,95,105,100,1,119,10,109,73,73,113,81,111,118,74,105,101,40,0,204,195,206,156,1,105,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,119,4,116,101,120,116,39,0,204,195,206,156,1,3,10,65,90,49,50,53,79,88,51,65,97,0,39,0,204,195,206,156,1,1,10,55,107,121,57,118,72,100,98,90,90,1,40,0,204,195,206,156,1,114,2,105,100,1,119,10,55,107,121,57,118,72,100,98,90,90,40,0,204,195,206,156,1,114,2,116,121,1,119,9,116,111,100,111,95,108,105,115,116,40,0,204,195,206,156,1,114,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,204,195,206,156,1,114,8,99,104,105,108,100,114,101,110,1,119,10,118,122,73,48,69,73,102,97,111,55,33,0,204,195,206,156,1,114,4,100,97,116,97,1,33,0,204,195,206,156,1,114,11,101,120,116,101,114,110,97,108,95,105,100,1,33,0,204,195,206,156,1,114,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,39,0,204,195,206,156,1,3,10,118,122,73,48,69,73,102,97,111,55,0,39,0,204,195,206,156,1,1,10,76,77,51,100,74,90,103,105,119,106,1,40,0,204,195,206,156,1,123,2,105,100,1,119,10,76,77,51,100,74,90,103,105,119,106,40,0,204,195,206,156,1,123,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,204,195,206,156,1,123,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,204,195,206,156,1,123,8,99,104,105,108,100,114,101,110,1,119,10,65,49,72,80,70,85,72,104,51,86,40,0,204,195,206,156,1,123,4,100,97,116,97,1,119,2,123,125,40,0,204,195,206,156,1,123,11,101,120,116,101,114,110,97,108,95,105,100,1,119,10,120,116,103,85,69,74,52,104,81,95,40,0,204,195,206,156,1,123,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,119,4,116,101,120,116,39,0,204,195,206,156,1,3,10,65,49,72,80,70,85,72,104,51,86,0,39,0,204,195,206,156,1,1,10,109,73,66,54,73,106,49,57,52,77,1,40,0,204,195,206,156,1,132,1,2,105,100,1,119,10,109,73,66,54,73,106,49,57,52,77,40,0,204,195,206,156,1,132,1,2,116,121,1,119,9,116,111,100,111,95,108,105,115,116,40,0,204,195,206,156,1,132,1,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,204,195,206,156,1,132,1,8,99,104,105,108,100,114,101,110,1,119,10,121,56,100,54,52,108,75,54,81,109,33,0,204,195,206,156,1,132,1,4,100,97,116,97,1,33,0,204,195,206,156,1,132,1,11,101,120,116,101,114,110,97,108,95,105,100,1,33,0,204,195,206,156,1,132,1,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,39,0,204,195,206,156,1,3,10,121,56,100,54,52,108,75,54,81,109,0,39,0,204,195,206,156,1,1,10,109,79,82,56,99,51,71,108,104,101,1,40,0,204,195,206,156,1,141,1,2,105,100,1,119,10,109,79,82,56,99,51,71,108,104,101,40,0,204,195,206,156,1,141,1,2,116,121,1,119,13,110,117,109,98,101,114,101,100,95,108,105,115,116,40,0,204,195,206,156,1,141,1,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,204,195,206,156,1,141,1,8,99,104,105,108,100,114,101,110,1,119,10,75,50,75,54,117,121,80,56,108,65,40,0,204,195,206,156,1,141,1,4,100,97,116,97,1,119,2,123,125,40,0,204,195,206,156,1,141,1,11,101,120,116,101,114,110,97,108,95,105,100,1,119,10,52,97,84,122,117,113,66,107,110,70,40,0,204,195,206,156,1,141,1,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,119,4,116,101,120,116,39,0,204,195,206,156,1,3,10,75,50,75,54,117,121,80,56,108,65,0,39,0,204,195,206,156,1,1,10,118,110,69,86,85,50,114,57,65,88,1,40,0,204,195,206,156,1,150,1,2,105,100,1,119,10,118,110,69,86,85,50,114,57,65,88,40,0,204,195,206,156,1,150,1,2,116,121,1,119,4,99,111,100,101,40,0,204,195,206,156,1,150,1,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,204,195,206,156,1,150,1,8,99,104,105,108,100,114,101,110,1,119,10,75,119,115,101,107,79,85,115,115,57,33,0,204,195,206,156,1,150,1,4,100,97,116,97,1,33,0,204,195,206,156,1,150,1,11,101,120,116,101,114,110,97,108,95,105,100,1,33,0,204,195,206,156,1,150,1,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,39,0,204,195,206,156,1,3,10,75,119,115,101,107,79,85,115,115,57,0,39,0,204,195,206,156,1,1,10,104,87,121,95,110,110,79,73,101,108,1,40,0,204,195,206,156,1,159,1,2,105,100,1,119,10,104,87,121,95,110,110,79,73,101,108,40,0,204,195,206,156,1,159,1,2,116,121,1,119,7,104,101,97,100,105,110,103,40,0,204,195,206,156,1,159,1,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,204,195,206,156,1,159,1,8,99,104,105,108,100,114,101,110,1,119,10,95,74,97,104,108,70,88,117,82,109,33,0,204,195,206,156,1,159,1,4,100,97,116,97,1,33,0,204,195,206,156,1,159,1,11,101,120,116,101,114,110,97,108,95,105,100,1,33,0,204,195,206,156,1,159,1,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,39,0,204,195,206,156,1,3,10,95,74,97,104,108,70,88,117,82,109,0,33,0,204,195,206,156,1,1,10,71,45,117,115,79,56,75,107,81,81,1,0,7,33,0,204,195,206,156,1,3,10,56,112,113,84,95,112,120,118,65,78,1,33,0,204,195,206,156,1,1,10,87,114,65,73,121,89,90,76,79,110,1,0,7,33,0,204,195,206,156,1,3,10,89,100,82,106,88,106,109,55,118,114,1,33,0,204,195,206,156,1,1,10,95,90,102,110,119,90,114,87,68,105,1,0,7,33,0,204,195,206,156,1,3,10,49,86,117,68,73,110,45,56,100,114,1,39,0,204,195,206,156,1,1,10,82,50,56,82,106,69,66,70,99,71,1,40,0,204,195,206,156,1,195,1,2,105,100,1,119,10,82,50,56,82,106,69,66,70,99,71,40,0,204,195,206,156,1,195,1,2,116,121,1,119,7,100,105,118,105,100,101,114,40,0,204,195,206,156,1,195,1,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,204,195,206,156,1,195,1,8,99,104,105,108,100,114,101,110,1,119,10,119,115,98,50,74,101,113,52,87,71,40,0,204,195,206,156,1,195,1,4,100,97,116,97,1,119,2,123,125,40,0,204,195,206,156,1,195,1,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,204,195,206,156,1,195,1,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,10,119,115,98,50,74,101,113,52,87,71,0,39,0,204,195,206,156,1,1,10,109,54,120,76,118,72,89,48,76,107,1,40,0,204,195,206,156,1,204,1,2,105,100,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,204,195,206,156,1,204,1,2,116,121,1,119,4,112,97,103,101,40,0,204,195,206,156,1,204,1,6,112,97,114,101,110,116,1,119,0,40,0,204,195,206,156,1,204,1,8,99,104,105,108,100,114,101,110,1,119,10,120,68,48,121,90,73,118,109,51,115,33,0,204,195,206,156,1,204,1,4,100,97,116,97,1,33,0,204,195,206,156,1,204,1,11,101,120,116,101,114,110,97,108,95,105,100,1,33,0,204,195,206,156,1,204,1,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,39,0,204,195,206,156,1,3,10,120,68,48,121,90,73,118,109,51,115,0,33,0,204,195,206,156,1,1,10,97,115,74,118,54,70,114,65,82,97,1,0,7,33,0,204,195,206,156,1,3,10,68,75,70,79,99,81,75,54,52,72,1,39,0,204,195,206,156,1,1,10,119,70,86,108,107,88,117,108,104,74,1,40,0,204,195,206,156,1,222,1,2,105,100,1,119,10,119,70,86,108,107,88,117,108,104,74,40,0,204,195,206,156,1,222,1,2,116,121,1,119,7,104,101,97,100,105,110,103,40,0,204,195,206,156,1,222,1,6,112,97,114,101,110,116,1,119,10,109,54,120,76,118,72,89,48,76,107,40,0,204,195,206,156,1,222,1,8,99,104,105,108,100,114,101,110,1,119,10,69,113,72,71,75,105,54,115,68,53,33,0,204,195,206,156,1,222,1,4,100,97,116,97,1,33,0,204,195,206,156,1,222,1,11,101,120,116,101,114,110,97,108,95,105,100,1,33,0,204,195,206,156,1,222,1,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,39,0,204,195,206,156,1,3,10,69,113,72,71,75,105,54,115,68,53,0,8,0,204,195,206,156,1,212,1,1,119,10,119,70,86,108,107,88,117,108,104,74,129,204,195,206,156,1,231,1,1,136,204,195,206,156,1,232,1,6,119,10,109,73,66,54,73,106,49,57,52,77,119,10,78,89,54,108,121,101,57,108,88,51,119,10,68,90,114,95,72,118,106,65,78,107,119,10,113,115,110,89,82,48,74,72,74,56,119,10,55,107,121,57,118,72,100,98,90,90,119,10,77,48,104,84,99,67,120,66,88,82,129,204,195,206,156,1,238,1,1,136,204,195,206,156,1,239,1,1,119,10,82,50,56,82,106,69,66,70,99,71,129,204,195,206,156,1,240,1,1,136,204,195,206,156,1,241,1,1,119,10,104,87,121,95,110,110,79,73,101,108,136,204,195,206,156,1,242,1,2,119,10,48,105,122,109,122,95,86,65,55,70,119,10,101,110,68,45,73,83,100,100,99,55,136,204,195,206,156,1,244,1,2,119,10,109,79,82,56,99,51,71,108,104,101,119,10,118,110,69,86,85,50,114,57,65,88,129,204,195,206,156,1,246,1,1,136,204,195,206,156,1,247,1,3,119,10,75,54,50,76,100,101,119,53,95,121,119,10,78,73,76,105,97,84,121,72,108,112,119,10,101,104,73,115,79,74,69,114,55,73,136,204,195,206,156,1,250,1,1,119,10,117,51,120,66,95,83,69,116,53,68,129,204,195,206,156,1,251,1,1,136,204,195,206,156,1,252,1,1,119,10,76,77,51,100,74,90,103,105,119,106,129,204,195,206,156,1,253,1,1,39,0,204,195,206,156,1,4,10,97,98,100,49,105,117,71,81,109,68,2,4,0,204,195,206,156,1,255,1,44,75,101,121,98,111,97,114,100,32,115,104,111,114,116,99,117,116,115,44,32,109,97,114,107,100,111,119,110,44,32,97,110,100,32,99,111,100,101,32,98,108,111,99,107,39,0,204,195,206,156,1,4,10,54,74,108,118,72,71,53,111,120,90,2,4,0,204,195,206,156,1,172,2,20,65,115,32,115,111,111,110,32,97,115,32,121,111,117,32,116,121,112,101,32,134,204,195,206,156,1,192,2,10,102,111,110,116,95,99,111,108,111,114,12,34,48,120,102,102,48,48,98,53,102,102,34,134,204,195,206,156,1,193,2,4,99,111,100,101,4,116,114,117,101,132,204,195,206,156,1,194,2,1,47,134,204,195,206,156,1,195,2,10,102,111,110,116,95,99,111,108,111,114,4,110,117,108,108,134,204,195,206,156,1,196,2,4,99,111,100,101,4,110,117,108,108,132,204,195,206,156,1,197,2,28,32,97,32,109,101,110,117,32,119,105,108,108,32,112,111,112,32,117,112,46,32,83,101,108,101,99,116,32,134,204,195,206,156,1,225,2,8,98,103,95,99,111,108,111,114,12,34,48,120,52,100,57,99,50,55,98,48,34,132,204,195,206,156,1,226,2,15,100,105,102,102,101,114,101,110,116,32,116,121,112,101,115,134,204,195,206,156,1,241,2,8,98,103,95,99,111,108,111,114,4,110,117,108,108,132,204,195,206,156,1,242,2,31,32,111,102,32,99,111,110,116,101,110,116,32,98,108,111,99,107,115,32,121,111,117,32,99,97,110,32,97,100,100,46,39,0,204,195,206,156,1,4,10,51,72,115,115,121,121,66,84,57,50,2,4,0,204,195,206,156,1,146,3,5,84,121,112,101,32,134,204,195,206,156,1,151,3,4,99,111,100,101,4,116,114,117,101,132,204,195,206,156,1,152,3,1,47,134,204,195,206,156,1,153,3,4,99,111,100,101,4,110,117,108,108,132,204,195,206,156,1,154,3,13,32,102,111,108,108,111,119,101,100,32,98,121,32,134,204,195,206,156,1,167,3,4,99,111,100,101,4,116,114,117,101,132,204,195,206,156,1,168,3,7,47,98,117,108,108,101,116,134,204,195,206,156,1,175,3,4,99,111,100,101,4,110,117,108,108,132,204,195,206,156,1,176,3,4,32,111,114,32,134,204,195,206,156,1,180,3,4,99,111,100,101,4,116,114,117,101,132,204,195,206,156,1,181,3,4,47,110,117,109,134,204,195,206,156,1,185,3,4,99,111,100,101,4,110,117,108,108,198,204,195,206,156,1,185,3,204,195,206,156,1,186,3,4,99,111,100,101,5,102,97,108,115,101,196,204,195,206,156,1,187,3,204,195,206,156,1,186,3,18,32,116,111,32,99,114,101,97,116,101,32,97,32,108,105,115,116,46,198,204,195,206,156,1,205,3,204,195,206,156,1,186,3,4,99,111,100,101,4,116,114,117,101,33,0,204,195,206,156,1,4,10,84,82,53,102,106,82,122,115,114,105,1,39,0,204,195,206,156,1,4,10,119,86,82,81,117,71,111,121,116,48,2,4,0,204,195,206,156,1,208,3,6,67,108,105,99,107,32,134,204,195,206,156,1,214,3,4,99,111,100,101,4,116,114,117,101,132,204,195,206,156,1,215,3,1,63,134,204,195,206,156,1,216,3,4,99,111,100,101,4,110,117,108,108,132,204,195,206,156,1,217,3,41,32,97,116,32,116,104,101,32,98,111,116,116,111,109,32,114,105,103,104,116,32,102,111,114,32,104,101,108,112,32,97,110,100,32,115,117,112,112,111,114,116,129,204,195,206,156,1,130,4,1,39,0,204,195,206,156,1,4,10,107,106,48,68,49,121,121,88,78,119,2,39,0,204,195,206,156,1,4,10,120,116,103,85,69,74,52,104,81,95,2,39,0,204,195,206,156,1,4,10,112,70,113,76,55,45,79,83,121,86,2,33,0,204,195,206,156,1,4,10,102,114,97,74,99,70,55,54,70,99,1,39,0,204,195,206,156,1,4,10,122,77,121,109,67,97,118,83,107,102,2,4,0,204,195,206,156,1,136,4,6,67,108,105,99,107,32,134,204,195,206,156,1,142,4,4,99,111,100,101,4,116,114,117,101,132,204,195,206,156,1,143,4,11,43,32,78,101,119,32,80,97,103,101,32,134,204,195,206,156,1,154,4,4,99,111,100,101,4,110,117,108,108,132,204,195,206,156,1,155,4,50,98,117,116,116,111,110,32,97,116,32,116,104,101,32,98,111,116,116,111,109,32,111,102,32,121,111,117,114,32,115,105,100,101,98,97,114,32,116,111,32,97,100,100,32,97,32,110,101,119,32,129,204,195,206,156,1,205,4,4,132,204,195,206,156,1,209,4,1,46,39,0,204,195,206,156,1,4,10,72,116,114,88,117,57,102,65,95,107,2,4,0,204,195,206,156,1,211,4,18,72,97,118,101,32,97,32,113,117,101,115,116,105,111,110,226,157,147,39,0,204,195,206,156,1,4,10,49,112,115,100,67,122,97,87,104,49,2,4,0,204,195,206,156,1,228,4,30,47,47,32,84,104,105,115,32,105,115,32,116,104,101,32,109,97,105,110,32,102,117,110,99,116,105,111,110,46,10,129,204,195,206,156,1,130,5,77,39,0,204,195,206,156,1,4,10,119,79,108,117,99,85,55,51,73,76,2,1,0,204,195,206,156,1,208,5,36,129,204,195,206,156,1,244,5,1,33,0,204,195,206,156,1,4,10,69,72,117,95,67,112,120,53,67,103,1,39,0,204,195,206,156,1,4,10,98,113,76,109,98,57,111,45,109,109,2,4,0,204,195,206,156,1,247,5,6,67,108,105,99,107,32,134,204,195,206,156,1,253,5,4,99,111,100,101,4,116,114,117,101,132,204,195,206,156,1,254,5,1,43,134,204,195,206,156,1,255,5,4,99,111,100,101,4,110,117,108,108,132,204,195,206,156,1,128,6,1,32,129,204,195,206,156,1,129,6,4,132,204,195,206,156,1,133,6,37,32,116,111,32,97,110,121,32,112,97,103,101,32,116,105,116,108,101,32,105,110,32,116,104,101,32,115,105,100,101,98,97,114,32,116,111,32,134,204,195,206,156,1,170,6,10,102,111,110,116,95,99,111,108,111,114,12,34,48,120,102,102,56,52,50,55,101,48,34,132,204,195,206,156,1,171,6,7,113,117,105,99,107,108,121,134,204,195,206,156,1,178,6,10,102,111,110,116,95,99,111,108,111,114,4,110,117,108,108,132,204,195,206,156,1,179,6,1,32,129,204,195,206,156,1,180,6,3,132,204,195,206,156,1,183,6,16,32,97,32,110,101,119,32,115,117,98,112,97,103,101,44,32,134,204,195,206,156,1,199,6,4,99,111,100,101,4,116,114,117,101,132,204,195,206,156,1,200,6,8,68,111,99,117,109,101,110,116,134,204,195,206,156,1,208,6,4,99,111,100,101,4,110,117,108,108,198,204,195,206,156,1,208,6,204,195,206,156,1,209,6,4,99,111,100,101,5,102,97,108,115,101,196,204,195,206,156,1,210,6,204,195,206,156,1,209,6,2,44,32,198,204,195,206,156,1,212,6,204,195,206,156,1,209,6,4,99,111,100,101,4,116,114,117,101,196,204,195,206,156,1,213,6,204,195,206,156,1,209,6,4,71,114,105,100,198,204,195,206,156,1,217,6,204,195,206,156,1,209,6,4,99,111,100,101,5,102,97,108,115,101,196,204,195,206,156,1,218,6,204,195,206,156,1,209,6,5,44,32,111,114,32,198,204,195,206,156,1,223,6,204,195,206,156,1,209,6,4,99,111,100,101,4,116,114,117,101,196,204,195,206,156,1,224,6,204,195,206,156,1,209,6,12,75,97,110,98,97,110,32,66,111,97,114,100,198,204,195,206,156,1,236,6,204,195,206,156,1,209,6,4,99,111,100,101,5,102,97,108,115,101,196,204,195,206,156,1,237,6,204,195,206,156,1,209,6,1,46,198,204,195,206,156,1,238,6,204,195,206,156,1,209,6,4,99,111,100,101,4,116,114,117,101,39,0,204,195,206,156,1,4,10,102,56,54,108,88,117,88,74,101,54,2,4,0,204,195,206,156,1,240,6,9,77,97,114,107,100,111,119,110,32,134,204,195,206,156,1,249,6,4,104,114,101,102,67,34,104,116,116,112,115,58,47,47,97,112,112,102,108,111,119,121,46,103,105,116,98,111,111,107,46,105,111,47,100,111,99,115,47,101,115,115,101,110,116,105,97,108,45,100,111,99,117,109,101,110,116,97,116,105,111,110,47,109,97,114,107,100,111,119,110,34,132,204,195,206,156,1,250,6,9,114,101,102,101,114,101,110,99,101,134,204,195,206,156,1,131,7,4,104,114,101,102,4,110,117,108,108,33,0,204,195,206,156,1,4,10,89,74,119,52,70,81,88,106,110,84,1,39,0,204,195,206,156,1,4,10,119,88,107,79,72,81,49,50,99,111,2,1,0,204,195,206,156,1,134,7,20,33,0,204,195,206,156,1,4,10,65,108,73,86,97,121,54,119,80,104,1,0,19,39,0,204,195,206,156,1,4,10,109,69,119,56,90,66,102,95,100,68,2,6,0,204,195,206,156,1,175,7,8,98,103,95,99,111,108,111,114,12,34,48,120,52,100,102,102,101,98,51,98,34,132,204,195,206,156,1,176,7,10,72,105,103,104,108,105,103,104,116,32,134,204,195,206,156,1,186,7,8,98,103,95,99,111,108,111,114,4,110,117,108,108,132,204,195,206,156,1,187,7,38,97,110,121,32,116,101,120,116,44,32,97,110,100,32,117,115,101,32,116,104,101,32,101,100,105,116,105,110,103,32,109,101,110,117,32,116,111,32,134,204,195,206,156,1,225,7,6,105,116,97,108,105,99,4,116,114,117,101,132,204,195,206,156,1,226,7,5,115,116,121,108,101,134,204,195,206,156,1,231,7,6,105,116,97,108,105,99,4,110,117,108,108,132,204,195,206,156,1,232,7,1,32,134,204,195,206,156,1,233,7,4,98,111,108,100,4,116,114,117,101,132,204,195,206,156,1,234,7,4,121,111,117,114,134,204,195,206,156,1,238,7,4,98,111,108,100,4,110,117,108,108,132,204,195,206,156,1,239,7,1,32,134,204,195,206,156,1,240,7,9,117,110,100,101,114,108,105,110,101,4,116,114,117,101,132,204,195,206,156,1,241,7,7,119,114,105,116,105,110,103,134,204,195,206,156,1,248,7,9,117,110,100,101,114,108,105,110,101,4,110,117,108,108,132,204,195,206,156,1,249,7,1,32,134,204,195,206,156,1,250,7,4,99,111,100,101,4,116,114,117,101,132,204,195,206,156,1,251,7,7,104,111,119,101,118,101,114,134,204,195,206,156,1,130,8,4,99,111,100,101,4,110,117,108,108,132,204,195,206,156,1,131,8,5,32,121,111,117,32,134,204,195,206,156,1,136,8,13,115,116,114,105,107,101,116,104,114,111,117,103,104,4,116,114,117,101,132,204,195,206,156,1,137,8,5,108,105,107,101,46,134,204,195,206,156,1,142,8,13,115,116,114,105,107,101,116,104,114,111,117,103,104,4,110,117,108,108,33,0,204,195,206,156,1,4,10,98,122,103,70,79,75,118,99,117,89,1,39,0,204,195,206,156,1,4,10,52,97,84,122,117,113,66,107,110,70,2,4,0,204,195,206,156,1,145,8,5,84,121,112,101,32,134,204,195,206,156,1,150,8,4,99,111,100,101,4,116,114,117,101,132,204,195,206,156,1,151,8,5,47,99,111,100,101,134,204,195,206,156,1,156,8,4,99,111,100,101,4,110,117,108,108,198,204,195,206,156,1,156,8,204,195,206,156,1,157,8,4,99,111,100,101,5,102,97,108,115,101,196,204,195,206,156,1,158,8,204,195,206,156,1,157,8,23,32,116,111,32,105,110,115,101,114,116,32,97,32,99,111,100,101,32,98,108,111,99,107,198,204,195,206,156,1,181,8,204,195,206,156,1,157,8,4,99,111,100,101,4,116,114,117,101,39,0,204,195,206,156,1,4,10,108,119,101,104,75,79,117,78,68,67,2,4,0,204,195,206,156,1,183,8,27,10,76,105,107,101,32,65,112,112,70,108,111,119,121,63,32,70,111,108,108,111,119,32,117,115,58,10,134,204,195,206,156,1,210,8,4,104,114,101,102,41,34,104,116,116,112,115,58,47,47,103,105,116,104,117,98,46,99,111,109,47,65,112,112,70,108,111,119,121,45,73,79,47,65,112,112,70,108,111,119,121,34,132,204,195,206,156,1,211,8,6,71,105,116,72,117,98,134,204,195,206,156,1,217,8,4,104,114,101,102,4,110,117,108,108,132,204,195,206,156,1,218,8,1,10,134,204,195,206,156,1,219,8,4,104,114,101,102,30,34,104,116,116,112,115,58,47,47,116,119,105,116,116,101,114,46,99,111,109,47,97,112,112,102,108,111,119,121,34,132,204,195,206,156,1,220,8,7,84,119,105,116,116,101,114,134,204,195,206,156,1,227,8,4,104,114,101,102,4,110,117,108,108,132,204,195,206,156,1,228,8,12,58,32,64,97,112,112,102,108,111,119,121,10,134,204,195,206,156,1,240,8,4,104,114,101,102,33,34,104,116,116,112,115,58,47,47,98,108,111,103,45,97,112,112,102,108,111,119,121,46,103,104,111,115,116,46,105,111,47,34,132,204,195,206,156,1,241,8,10,78,101,119,115,108,101,116,116,101,114,134,204,195,206,156,1,251,8,4,104,114,101,102,4,110,117,108,108,132,204,195,206,156,1,252,8,1,10,39,0,204,195,206,156,1,4,10,109,73,73,113,81,111,118,74,105,101,2,4,0,204,195,206,156,1,254,8,19,75,101,121,98,111,97,114,100,32,115,104,111,114,116,99,117,116,115,32,134,204,195,206,156,1,145,9,4,104,114,101,102,68,34,104,116,116,112,115,58,47,47,97,112,112,102,108,111,119,121,46,103,105,116,98,111,111,107,46,105,111,47,100,111,99,115,47,101,115,115,101,110,116,105,97,108,45,100,111,99,117,109,101,110,116,97,116,105,111,110,47,115,104,111,114,116,99,117,116,115,34,132,204,195,206,156,1,146,9,5,103,117,105,100,101,134,204,195,206,156,1,151,9,4,104,114,101,102,4,110,117,108,108,2,131,159,159,151,1,0,161,237,140,187,206,2,16,1,161,237,140,187,206,2,20,71,1,141,178,210,127,0,0,3,1,206,214,243,86,0,161,236,158,128,159,2,3,178,1,62,194,228,144,71,0,161,243,138,171,183,10,246,1,1,161,243,138,171,183,10,247,1,1,161,243,138,171,183,10,248,1,1,161,131,128,202,229,9,0,1,161,194,228,144,71,0,1,161,194,228,144,71,1,1,161,194,228,144,71,2,1,161,194,228,144,71,3,1,39,0,204,195,206,156,1,4,6,114,114,111,103,100,98,2,33,0,204,195,206,156,1,1,6,85,95,66,110,68,101,1,0,7,33,0,204,195,206,156,1,3,6,79,70,89,50,114,113,1,193,199,130,209,189,2,174,5,199,130,209,189,2,210,5,1,1,0,194,228,144,71,8,1,0,1,129,194,228,144,71,19,1,0,3,39,0,204,195,206,156,1,4,6,103,109,54,79,74,117,2,33,0,204,195,206,156,1,1,6,114,78,78,121,56,74,1,0,7,33,0,204,195,206,156,1,3,6,88,101,115,97,82,119,1,193,199,130,209,189,2,174,5,194,228,144,71,18,1,4,0,194,228,144,71,25,1,62,0,1,39,0,204,195,206,156,1,4,6,99,82,86,69,118,53,2,39,0,204,195,206,156,1,1,6,109,55,85,85,85,68,1,40,0,194,228,144,71,39,2,105,100,1,119,6,109,55,85,85,85,68,40,0,194,228,144,71,39,2,116,121,1,119,11,116,111,103,103,108,101,95,108,105,115,116,40,0,194,228,144,71,39,6,112,97,114,101,110,116,1,119,6,78,99,104,45,81,78,40,0,194,228,144,71,39,8,99,104,105,108,100,114,101,110,1,119,6,120,53,79,107,74,71,33,0,194,228,144,71,39,4,100,97,116,97,1,40,0,194,228,144,71,39,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,194,228,144,71,39,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,120,53,79,107,74,71,0,200,199,130,209,189,2,174,5,194,228,144,71,35,1,119,6,109,55,85,85,85,68,4,0,194,228,144,71,38,1,49,161,194,228,144,71,44,1,132,194,228,144,71,49,1,50,161,194,228,144,71,50,1,132,194,228,144,71,51,1,51,161,194,228,144,71,52,1,39,0,204,195,206,156,1,4,6,105,72,102,106,109,56,2,39,0,204,195,206,156,1,1,6,73,121,89,76,77,104,1,40,0,194,228,144,71,56,2,105,100,1,119,6,73,121,89,76,77,104,40,0,194,228,144,71,56,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,194,228,144,71,56,6,112,97,114,101,110,116,1,119,6,109,55,85,85,85,68,40,0,194,228,144,71,56,8,99,104,105,108,100,114,101,110,1,119,6,79,76,111,88,102,98,33,0,194,228,144,71,56,4,100,97,116,97,1,40,0,194,228,144,71,56,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,194,228,144,71,56,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,204,195,206,156,1,3,6,79,76,111,88,102,98,0,8,0,194,228,144,71,47,1,119,6,73,121,89,76,77,104,161,194,228,144,71,54,1,4,0,194,228,144,71,55,1,52,161,194,228,144,71,61,1,132,194,228,144,71,67,1,52,161,194,228,144,71,68,1,132,194,228,144,71,69,1,52,161,194,228,144,71,70,1,132,194,228,144,71,71,1,52,168,194,228,144,71,72,1,119,29,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,52,52,52,52,34,125,93,125,168,194,228,144,71,66,1,119,45,123,34,99,111,108,108,97,112,115,101,100,34,58,116,114,117,101,44,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,49,50,51,34,125,93,125,1,185,164,169,62,0,161,198,234,131,228,11,49,90,1,229,154,194,35,0,161,136,172,186,168,4,181,6,178,1,1,218,255,204,32,0,161,150,152,188,203,6,19,21,1,183,182,135,14,0,161,207,210,187,205,12,7,227,2,73,131,159,159,151,1,1,0,72,132,236,218,251,9,1,0,14,131,182,180,202,12,1,0,53,131,128,202,229,9,1,0,1,133,181,204,218,3,8,15,1,17,2,20,1,22,1,24,1,26,1,28,1,30,1,136,172,186,168,4,1,0,182,6,136,199,176,231,9,1,20,10,140,167,201,161,14,1,0,10,141,151,160,163,4,1,0,24,142,211,188,164,13,1,0,15,141,178,210,127,1,0,3,145,224,235,133,7,1,0,3,146,209,153,247,13,1,0,186,1,146,216,250,133,2,1,0,180,1,146,175,139,236,2,1,0,12,150,152,188,203,6,1,0,20,151,234,142,238,11,1,0,27,150,216,171,142,3,87,0,171,3,172,3,3,176,3,3,180,3,3,184,3,3,188,3,3,192,3,3,196,3,3,200,3,3,204,3,3,208,3,3,212,3,3,216,3,3,220,3,3,224,3,3,228,3,3,232,3,3,236,3,3,240,3,3,244,3,3,248,3,3,253,3,3,129,4,3,133,4,3,137,4,3,141,4,3,145,4,3,149,4,3,153,4,3,157,4,3,161,4,3,165,4,3,169,4,3,173,4,3,177,4,3,181,4,3,185,4,3,189,4,3,193,4,3,197,4,3,201,4,3,205,4,3,209,4,3,213,4,3,217,4,3,221,4,3,225,4,3,229,4,7,254,4,10,138,5,1,140,5,1,142,5,1,149,5,1,155,5,1,157,5,1,159,5,1,166,5,11,178,5,15,200,5,1,210,5,1,220,5,1,230,5,1,235,5,1,237,5,1,239,5,1,241,5,1,245,5,1,251,5,1,133,6,1,138,6,1,144,6,1,154,6,1,164,6,1,174,6,1,180,6,1,182,6,1,184,6,1,186,6,5,216,6,3,231,6,14,188,7,6,158,8,1,160,8,1,162,8,1,164,8,3,168,8,3,187,8,1,153,236,182,220,1,1,0,4,151,254,242,152,9,11,5,8,14,1,17,1,19,3,32,1,37,16,54,23,89,2,97,1,102,21,134,1,8,155,213,159,176,1,1,0,10,161,234,157,145,5,1,0,7,164,202,219,213,10,18,19,10,31,10,42,1,44,10,55,1,57,1,59,1,61,3,67,1,77,7,85,1,87,1,89,1,91,1,93,1,95,1,97,1,117,1,165,131,171,211,15,1,0,20,168,215,223,235,2,1,0,3,171,236,222,251,5,69,9,5,15,11,27,8,36,3,40,4,45,8,54,8,63,3,72,3,76,3,80,3,84,3,88,3,92,3,96,3,102,3,106,10,120,10,131,1,10,142,1,10,153,1,10,169,1,1,176,1,2,179,1,1,181,1,1,187,1,10,201,1,1,207,1,10,222,1,10,237,1,17,131,2,10,146,2,10,166,2,1,172,2,10,186,2,1,192,2,10,207,2,10,222,2,10,237,2,10,252,2,10,139,3,10,150,3,18,169,3,15,185,3,1,202,3,1,208,3,1,210,3,1,212,3,1,240,3,1,245,3,10,128,4,4,137,4,1,142,4,7,150,4,6,157,4,6,164,4,6,171,4,6,178,4,6,185,4,6,192,4,6,199,4,1,201,4,4,206,4,7,214,4,6,221,4,6,228,4,6,235,4,6,242,4,1,249,4,1,172,254,181,239,1,4,5,1,8,1,11,1,14,1,174,203,157,214,7,1,0,6,176,238,158,139,14,1,0,175,2,177,239,218,225,4,1,0,3,178,187,245,161,14,2,8,1,10,1,180,189,170,253,8,2,6,1,10,1,181,150,190,222,14,15,1,8,10,10,21,10,32,10,59,1,65,1,67,1,69,1,71,1,73,1,80,1,85,1,87,1,89,1,91,1,181,156,253,158,6,1,0,4,183,182,135,14,1,0,227,2,184,146,243,216,14,1,0,7,185,164,169,62,1,0,90,183,213,134,255,8,1,0,28,190,183,139,210,2,1,0,110,192,246,139,213,2,1,0,35,192,187,174,206,8,3,1,74,76,220,3,222,5,1,194,228,144,71,13,0,8,9,16,26,10,37,1,44,1,50,1,52,1,54,1,61,1,66,1,68,1,70,1,72,1,195,254,251,180,11,1,0,58,197,205,192,233,12,1,0,9,198,223,206,159,1,25,15,3,19,20,40,10,51,10,62,10,78,1,86,6,93,13,112,1,120,1,124,1,126,13,145,1,1,153,1,1,159,1,1,170,1,1,175,1,1,177,1,10,193,1,1,203,1,10,219,1,1,230,1,10,246,1,1,253,1,1,135,2,1,198,234,131,228,11,1,0,50,199,130,209,189,2,203,1,4,11,16,1,19,12,33,1,35,1,37,1,39,1,41,1,43,1,45,1,47,1,49,1,56,1,61,1,63,1,65,1,67,1,69,1,71,1,73,1,75,1,77,1,79,1,81,1,83,1,85,1,87,1,89,1,91,1,93,1,95,1,102,1,107,1,109,1,111,1,113,1,115,1,117,1,119,1,121,1,123,1,125,4,136,1,1,144,1,1,152,1,1,160,1,1,168,1,1,176,1,1,184,1,1,192,1,1,200,1,1,208,1,1,216,1,1,224,1,1,232,1,1,240,1,1,248,1,1,128,2,1,136,2,1,144,2,1,152,2,1,160,2,1,168,2,1,176,2,1,184,2,1,192,2,1,200,2,2,208,2,1,213,2,1,215,2,27,246,2,21,140,3,1,142,3,1,144,3,1,146,3,1,153,3,1,162,3,10,177,3,1,183,3,10,202,3,1,212,3,1,222,3,1,232,3,1,242,3,1,252,3,1,134,4,1,144,4,1,154,4,1,159,4,1,161,4,1,163,4,1,165,4,1,167,4,1,169,4,1,171,4,1,173,4,1,175,4,1,177,4,5,188,4,1,193,4,6,200,4,10,216,4,1,221,4,6,228,4,10,244,4,1,249,4,6,133,5,1,138,5,6,145,5,10,156,5,1,158,5,1,160,5,1,162,5,3,170,5,1,175,5,6,182,5,16,199,5,1,206,5,1,211,5,6,223,5,1,228,5,7,236,5,10,252,5,1,129,6,6,136,6,10,147,6,10,158,6,1,160,6,1,162,6,1,167,6,10,178,6,1,180,6,2,183,6,2,186,6,1,188,6,1,190,6,1,192,6,1,194,6,1,196,6,1,198,6,1,200,6,1,202,6,1,206,6,1,208,6,1,210,6,1,212,6,1,214,6,4,219,6,1,223,6,1,225,6,1,227,6,1,229,6,1,231,6,1,233,6,1,235,6,1,237,6,1,241,6,1,243,6,1,245,6,1,247,6,1,249,6,1,251,6,1,253,6,1,255,6,1,131,7,1,133,7,1,135,7,1,137,7,1,139,7,1,141,7,1,144,7,21,166,7,1,168,7,1,170,7,1,172,7,1,174,7,1,176,7,1,178,7,1,180,7,1,182,7,1,184,7,1,188,7,1,190,7,1,192,7,1,194,7,1,196,7,1,198,7,4,205,7,1,207,7,1,209,7,1,211,7,1,213,7,1,215,7,1,217,7,13,231,7,1,240,7,1,248,7,2,252,7,3,130,8,1,132,8,1,134,8,1,136,8,2,139,8,2,204,195,206,156,1,30,11,3,56,3,60,9,83,3,96,9,119,3,137,1,3,155,1,3,164,1,3,168,1,27,209,1,3,213,1,9,227,1,3,232,1,1,239,1,1,241,1,1,247,1,1,252,1,1,254,1,1,207,3,1,131,4,1,135,4,1,206,4,4,131,5,77,209,5,38,130,6,4,181,6,3,133,7,1,135,7,40,144,8,1,206,214,243,86,1,0,178,1,207,210,187,205,12,1,0,8,208,203,223,226,9,1,0,81,207,231,154,196,9,1,0,3,217,168,198,159,4,1,0,7,218,255,204,32,1,0,21,219,200,174,197,9,1,0,25,220,225,223,240,3,8,0,4,7,3,11,24,41,1,46,2,51,1,53,3,59,1,223,215,172,155,15,1,0,5,224,159,166,178,15,1,0,30,226,167,254,250,5,3,8,1,10,1,12,1,227,211,144,195,8,1,0,12,228,242,134,215,15,4,5,1,7,1,9,1,11,1,229,154,194,35,1,0,178,1,226,235,133,189,11,1,0,7,236,158,128,159,2,1,0,4,237,140,187,206,2,1,0,21,236,253,128,205,3,1,0,9,239,239,208,251,10,1,0,17,240,179,157,219,7,1,0,4,241,147,239,232,6,1,0,4,238,153,239,204,9,7,0,3,4,3,8,3,30,1,32,1,46,1,48,1,243,138,171,183,10,1,0,252,1,245,181,155,135,2,1,0,23,247,212,219,208,10,1,0,46],"version":0,"object_id":"26d5c8c1-1c66-459c-bc6c-f4da1a663348"},"code":0,"message":"Operation completed successfully."} \ No newline at end of file diff --git a/frontend/appflowy_web_app/cypress/fixtures/simple_doc.json b/frontend/appflowy_web_app/cypress/fixtures/simple_doc.json deleted file mode 100644 index 97bd9c99b5..0000000000 --- a/frontend/appflowy_web_app/cypress/fixtures/simple_doc.json +++ /dev/null @@ -1 +0,0 @@ -{"data":{"state_vector":[5,200,244,136,224,7,3,178,246,186,209,6,72,147,128,159,145,14,26,195,133,217,18,167,13,156,139,194,87,4],"doc_state":[5,26,147,128,159,145,14,0,39,1,4,100,97,116,97,8,100,111,99,117,109,101,110,116,1,39,0,147,128,159,145,14,0,6,98,108,111,99,107,115,1,39,0,147,128,159,145,14,0,4,109,101,116,97,1,39,0,147,128,159,145,14,2,12,99,104,105,108,100,114,101,110,95,109,97,112,1,39,0,147,128,159,145,14,2,8,116,101,120,116,95,109,97,112,1,40,0,147,128,159,145,14,0,7,112,97,103,101,95,105,100,1,119,10,85,86,79,107,81,88,110,117,86,114,39,0,147,128,159,145,14,1,10,51,77,108,48,104,78,110,102,79,82,1,40,0,147,128,159,145,14,6,2,105,100,1,119,10,51,77,108,48,104,78,110,102,79,82,40,0,147,128,159,145,14,6,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,147,128,159,145,14,6,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,147,128,159,145,14,6,8,99,104,105,108,100,114,101,110,1,119,10,73,121,84,67,107,48,105,52,113,114,33,0,147,128,159,145,14,6,4,100,97,116,97,1,33,0,147,128,159,145,14,6,11,101,120,116,101,114,110,97,108,95,105,100,1,33,0,147,128,159,145,14,6,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,39,0,147,128,159,145,14,3,10,73,121,84,67,107,48,105,52,113,114,0,39,0,147,128,159,145,14,1,10,85,86,79,107,81,88,110,117,86,114,1,40,0,147,128,159,145,14,15,2,105,100,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,147,128,159,145,14,15,2,116,121,1,119,4,112,97,103,101,40,0,147,128,159,145,14,15,6,112,97,114,101,110,116,1,119,0,40,0,147,128,159,145,14,15,8,99,104,105,108,100,114,101,110,1,119,10,67,102,118,66,115,66,84,122,83,105,40,0,147,128,159,145,14,15,4,100,97,116,97,1,119,2,123,125,40,0,147,128,159,145,14,15,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,147,128,159,145,14,15,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,10,67,102,118,66,115,66,84,122,83,105,0,8,0,147,128,159,145,14,23,1,119,10,51,77,108,48,104,78,110,102,79,82,39,0,147,128,159,145,14,4,10,84,97,119,48,120,69,66,121,65,83,2,1,200,244,136,224,7,0,33,1,4,109,101,116,97,12,108,97,115,116,95,115,121,110,99,95,97,116,3,1,178,246,186,209,6,0,161,200,244,136,224,7,2,72,2,156,139,194,87,0,161,178,246,186,209,6,71,3,168,156,139,194,87,2,1,122,0,0,0,0,102,34,168,95,251,5,195,133,217,18,0,4,0,147,128,159,145,14,25,2,85,73,168,147,128,159,145,14,11,1,119,27,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,85,73,34,125,93,125,168,147,128,159,145,14,12,1,119,10,84,97,119,48,120,69,66,121,65,83,168,147,128,159,145,14,13,1,119,4,116,101,120,116,39,0,147,128,159,145,14,4,6,120,52,56,106,57,65,2,39,0,147,128,159,145,14,1,6,53,77,89,104,51,105,1,40,0,195,133,217,18,6,2,105,100,1,119,6,53,77,89,104,51,105,40,0,195,133,217,18,6,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,195,133,217,18,6,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,6,8,99,104,105,108,100,114,101,110,1,119,6,75,79,49,111,105,114,33,0,195,133,217,18,6,4,100,97,116,97,1,40,0,195,133,217,18,6,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,6,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,75,79,49,111,105,114,0,136,147,128,159,145,14,24,1,119,6,53,77,89,104,51,105,4,0,195,133,217,18,5,3,111,111,111,168,195,133,217,18,11,1,119,28,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,111,111,111,34,125,93,125,39,0,147,128,159,145,14,4,6,73,72,56,87,97,118,2,33,0,147,128,159,145,14,1,6,85,81,88,116,105,65,1,0,7,33,0,147,128,159,145,14,3,6,65,122,48,88,110,77,1,129,195,133,217,18,15,1,39,0,147,128,159,145,14,4,6,82,122,107,73,79,49,2,39,0,147,128,159,145,14,1,6,69,79,113,57,79,119,1,40,0,195,133,217,18,32,2,105,100,1,119,6,69,79,113,57,79,119,40,0,195,133,217,18,32,2,116,121,1,119,9,116,111,100,111,95,108,105,115,116,40,0,195,133,217,18,32,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,32,8,99,104,105,108,100,114,101,110,1,119,6,105,80,115,106,50,65,33,0,195,133,217,18,32,4,100,97,116,97,1,40,0,195,133,217,18,32,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,32,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,105,80,115,106,50,65,0,200,195,133,217,18,15,195,133,217,18,30,1,119,6,69,79,113,57,79,119,4,0,195,133,217,18,31,6,232,191,155,233,151,168,161,195,133,217,18,37,1,39,0,147,128,159,145,14,4,6,95,56,78,114,97,97,2,39,0,147,128,159,145,14,1,6,86,73,50,122,54,78,1,40,0,195,133,217,18,46,2,105,100,1,119,6,86,73,50,122,54,78,40,0,195,133,217,18,46,2,116,121,1,119,9,116,111,100,111,95,108,105,115,116,40,0,195,133,217,18,46,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,46,8,99,104,105,108,100,114,101,110,1,119,6,56,118,75,112,112,71,33,0,195,133,217,18,46,4,100,97,116,97,1,40,0,195,133,217,18,46,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,46,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,56,118,75,112,112,71,0,136,195,133,217,18,30,1,119,6,86,73,50,122,54,78,168,195,133,217,18,44,1,119,47,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,232,191,155,233,151,168,34,125,93,44,34,99,104,101,99,107,101,100,34,58,102,97,108,115,101,125,4,0,195,133,217,18,45,9,229,147,136,229,147,136,229,147,136,161,195,133,217,18,51,1,39,0,147,128,159,145,14,4,6,82,119,103,79,71,104,2,33,0,147,128,159,145,14,1,6,57,82,83,84,76,77,1,0,7,33,0,147,128,159,145,14,3,6,51,85,73,116,84,78,1,129,195,133,217,18,55,1,168,195,133,217,18,60,1,119,50,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,229,147,136,229,147,136,229,147,136,34,125,93,44,34,99,104,101,99,107,101,100,34,58,102,97,108,115,101,125,39,0,147,128,159,145,14,4,6,114,77,45,67,67,70,2,39,0,147,128,159,145,14,1,6,112,116,116,106,121,52,1,40,0,195,133,217,18,74,2,105,100,1,119,6,112,116,116,106,121,52,40,0,195,133,217,18,74,2,116,121,1,119,9,116,111,100,111,95,108,105,115,116,40,0,195,133,217,18,74,6,112,97,114,101,110,116,1,119,6,86,73,50,122,54,78,40,0,195,133,217,18,74,8,99,104,105,108,100,114,101,110,1,119,6,110,57,101,88,110,75,33,0,195,133,217,18,74,4,100,97,116,97,1,40,0,195,133,217,18,74,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,74,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,110,57,101,88,110,75,0,8,0,195,133,217,18,54,1,119,6,112,116,116,106,121,52,4,0,195,133,217,18,73,12,229,129,165,229,186,183,233,130,163,232,190,185,161,195,133,217,18,79,1,39,0,147,128,159,145,14,4,6,79,86,73,85,113,85,2,33,0,147,128,159,145,14,1,6,55,90,111,73,74,109,1,0,7,33,0,147,128,159,145,14,3,6,117,57,107,50,68,78,1,129,195,133,217,18,71,1,39,0,147,128,159,145,14,4,6,85,111,95,84,114,107,2,4,0,195,133,217,18,100,11,49,50,51,32,36,32,32,101,114,32,32,39,0,147,128,159,145,14,4,6,99,102,56,95,106,100,2,4,0,195,133,217,18,112,3,49,50,51,39,0,147,128,159,145,14,4,6,53,87,72,110,88,75,2,4,0,195,133,217,18,116,19,99,104,101,99,107,101,100,32,116,111,100,111,32,108,105,115,116,32,36,39,0,147,128,159,145,14,4,6,45,107,95,95,66,113,2,39,0,147,128,159,145,14,4,6,95,67,82,55,75,53,2,4,0,195,133,217,18,137,1,180,1,108,111,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,76,101,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,39,0,147,128,159,145,14,4,6,122,79,53,86,113,78,2,39,0,147,128,159,145,14,4,6,50,51,68,78,97,74,2,4,0,195,133,217,18,191,2,4,119,105,116,104,39,0,147,128,159,145,14,4,6,95,56,114,66,75,71,2,39,0,147,128,159,145,14,4,6,67,54,45,78,116,106,2,4,0,195,133,217,18,197,2,11,229,144,140,228,184,128,228,184,170,110,105,39,0,147,128,159,145,14,4,6,120,83,103,122,75,55,2,4,0,195,133,217,18,203,2,55,67,108,105,99,107,32,97,110,121,119,104,101,114,101,32,97,110,100,32,106,117,115,116,32,115,116,97,114,116,32,116,121,112,105,110,103,229,147,136,229,147,136,229,147,136,46,229,176,177,229,135,160,229,174,182,39,0,147,128,159,145,14,4,6,53,118,101,50,119,103,2,39,0,147,128,159,145,14,4,6,84,86,66,74,86,72,2,6,0,195,133,217,18,248,2,8,98,103,95,99,111,108,111,114,12,34,48,120,98,51,102,102,101,98,51,98,34,132,195,133,217,18,249,2,10,72,105,103,104,108,105,103,104,116,32,134,195,133,217,18,131,3,8,98,103,95,99,111,108,111,114,4,110,117,108,108,132,195,133,217,18,132,3,38,97,110,121,32,116,101,120,116,44,32,97,110,100,32,117,115,101,32,116,104,101,32,101,100,105,116,105,110,103,32,109,101,110,117,32,116,111,32,134,195,133,217,18,170,3,6,105,116,97,108,105,99,4,116,114,117,101,132,195,133,217,18,171,3,5,115,116,121,108,101,134,195,133,217,18,176,3,6,105,116,97,108,105,99,4,110,117,108,108,132,195,133,217,18,177,3,1,32,134,195,133,217,18,178,3,4,98,111,108,100,4,116,114,117,101,132,195,133,217,18,179,3,4,121,111,117,114,134,195,133,217,18,183,3,4,98,111,108,100,4,110,117,108,108,132,195,133,217,18,184,3,1,32,134,195,133,217,18,185,3,9,117,110,100,101,114,108,105,110,101,4,116,114,117,101,132,195,133,217,18,186,3,7,119,114,105,116,105,110,103,134,195,133,217,18,193,3,9,117,110,100,101,114,108,105,110,101,4,110,117,108,108,132,195,133,217,18,194,3,1,32,134,195,133,217,18,195,3,4,99,111,100,101,4,116,114,117,101,132,195,133,217,18,196,3,7,104,111,119,101,118,101,114,134,195,133,217,18,203,3,4,99,111,100,101,4,110,117,108,108,132,195,133,217,18,204,3,5,32,121,111,117,32,134,195,133,217,18,209,3,13,115,116,114,105,107,101,116,104,114,111,117,103,104,4,116,114,117,101,132,195,133,217,18,210,3,5,108,105,107,101,46,134,195,133,217,18,215,3,13,115,116,114,105,107,101,116,104,114,111,117,103,104,4,110,117,108,108,39,0,147,128,159,145,14,4,6,109,119,113,108,50,67,2,39,0,147,128,159,145,14,4,6,67,82,79,71,73,119,2,4,0,195,133,217,18,218,3,20,65,115,32,115,111,111,110,32,97,115,32,121,111,117,32,116,121,112,101,32,134,195,133,217,18,238,3,10,102,111,110,116,95,99,111,108,111,114,11,34,48,120,49,48,48,98,53,102,102,34,132,195,133,217,18,239,3,1,47,134,195,133,217,18,240,3,10,102,111,110,116,95,99,111,108,111,114,4,110,117,108,108,132,195,133,217,18,241,3,28,32,97,32,109,101,110,117,32,119,105,108,108,32,112,111,112,32,117,112,46,32,83,101,108,101,99,116,32,134,195,133,217,18,141,4,8,98,103,95,99,111,108,111,114,12,34,48,120,98,51,57,99,50,55,98,48,34,132,195,133,217,18,142,4,15,100,105,102,102,101,114,101,110,116,32,116,121,112,101,115,134,195,133,217,18,157,4,8,98,103,95,99,111,108,111,114,4,110,117,108,108,132,195,133,217,18,158,4,31,32,111,102,32,99,111,110,116,101,110,116,32,98,108,111,99,107,115,32,121,111,117,32,99,97,110,32,97,100,100,46,39,0,147,128,159,145,14,4,6,53,72,118,84,75,51,2,39,0,147,128,159,145,14,4,6,90,79,118,81,105,51,2,4,0,195,133,217,18,191,4,5,84,121,112,101,32,134,195,133,217,18,196,4,4,99,111,100,101,4,116,114,117,101,132,195,133,217,18,197,4,1,47,134,195,133,217,18,198,4,4,99,111,100,101,4,110,117,108,108,132,195,133,217,18,199,4,13,32,102,111,108,108,111,119,101,100,32,98,121,32,134,195,133,217,18,212,4,4,99,111,100,101,4,116,114,117,101,132,195,133,217,18,213,4,7,47,98,117,108,108,101,116,134,195,133,217,18,220,4,4,99,111,100,101,4,110,117,108,108,132,195,133,217,18,221,4,4,32,111,114,32,134,195,133,217,18,225,4,4,99,111,100,101,4,116,114,117,101,132,195,133,217,18,226,4,22,47,110,117,109,32,116,111,32,99,114,101,97,116,101,32,97,32,108,105,115,116,46,134,195,133,217,18,248,4,4,99,111,100,101,4,110,117,108,108,39,0,147,128,159,145,14,4,6,79,90,115,66,78,49,2,39,0,147,128,159,145,14,4,6,81,116,69,74,118,51,2,4,0,195,133,217,18,251,4,6,67,108,105,99,107,32,134,195,133,217,18,129,5,4,99,111,100,101,4,116,114,117,101,132,195,133,217,18,130,5,11,43,32,78,101,119,32,80,97,103,101,32,134,195,133,217,18,141,5,4,99,111,100,101,4,110,117,108,108,132,195,133,217,18,142,5,50,98,117,116,116,111,110,32,97,116,32,116,104,101,32,98,111,116,116,111,109,32,111,102,32,121,111,117,114,32,115,105,100,101,98,97,114,32,116,111,32,97,100,100,32,97,32,110,101,119,32,134,195,133,217,18,192,5,4,98,111,108,100,4,116,114,117,101,132,195,133,217,18,193,5,4,112,97,103,101,134,195,133,217,18,197,5,4,98,111,108,100,4,110,117,108,108,132,195,133,217,18,198,5,1,46,39,0,147,128,159,145,14,4,6,84,100,107,119,102,104,2,39,0,147,128,159,145,14,4,6,90,70,108,73,71,121,2,4,0,195,133,217,18,201,5,6,67,108,105,99,107,32,134,195,133,217,18,207,5,4,99,111,100,101,4,116,114,117,101,132,195,133,217,18,208,5,1,43,134,195,133,217,18,209,5,4,99,111,100,101,4,110,117,108,108,132,195,133,217,18,210,5,1,32,134,195,133,217,18,211,5,10,102,111,110,116,95,99,111,108,111,114,11,34,48,120,49,100,98,51,54,51,54,34,134,195,133,217,18,212,5,8,98,103,95,99,111,108,111,114,11,34,48,120,49,102,102,100,97,101,54,34,132,195,133,217,18,213,5,4,110,101,120,116,134,195,133,217,18,217,5,8,98,103,95,99,111,108,111,114,4,110,117,108,108,134,195,133,217,18,218,5,10,102,111,110,116,95,99,111,108,111,114,4,110,117,108,108,132,195,133,217,18,219,5,37,32,116,111,32,97,110,121,32,112,97,103,101,32,116,105,116,108,101,32,105,110,32,116,104,101,32,115,105,100,101,98,97,114,32,116,111,32,134,195,133,217,18,128,6,10,102,111,110,116,95,99,111,108,111,114,11,34,48,120,49,56,52,50,55,101,48,34,132,195,133,217,18,129,6,7,113,117,105,99,107,108,121,134,195,133,217,18,136,6,10,102,111,110,116,95,99,111,108,111,114,4,110,117,108,108,132,195,133,217,18,137,6,1,32,134,195,133,217,18,138,6,6,105,116,97,108,105,99,4,116,114,117,101,134,195,133,217,18,139,6,9,117,110,100,101,114,108,105,110,101,4,116,114,117,101,134,195,133,217,18,140,6,13,115,116,114,105,107,101,116,104,114,111,117,103,104,4,116,114,117,101,132,195,133,217,18,141,6,9,230,140,168,233,161,191,230,137,147,134,195,133,217,18,144,6,9,117,110,100,101,114,108,105,110,101,4,110,117,108,108,134,195,133,217,18,145,6,6,105,116,97,108,105,99,4,110,117,108,108,134,195,133,217,18,146,6,13,115,116,114,105,107,101,116,104,114,111,117,103,104,4,110,117,108,108,132,195,133,217,18,147,6,16,32,97,32,110,101,119,32,115,117,98,112,97,103,101,44,32,134,195,133,217,18,163,6,4,99,111,100,101,4,116,114,117,101,132,195,133,217,18,164,6,32,68,111,99,117,109,101,110,116,44,32,71,114,105,100,44,32,111,114,32,75,97,110,98,97,110,32,66,111,97,114,100,46,134,195,133,217,18,196,6,4,99,111,100,101,4,110,117,108,108,39,0,147,128,159,145,14,4,6,51,55,75,112,109,74,2,39,0,147,128,159,145,14,4,6,114,49,67,51,121,66,2,4,0,195,133,217,18,199,6,6,228,189,147,233,170,140,134,195,133,217,18,201,6,13,115,116,114,105,107,101,116,104,114,111,117,103,104,4,116,114,117,101,134,195,133,217,18,202,6,9,117,110,100,101,114,108,105,110,101,4,116,114,117,101,132,195,133,217,18,203,6,3,228,184,128,134,195,133,217,18,204,6,9,117,110,100,101,114,108,105,110,101,4,110,117,108,108,134,195,133,217,18,205,6,13,115,116,114,105,107,101,116,104,114,111,117,103,104,4,110,117,108,108,39,0,147,128,159,145,14,4,6,104,48,112,77,45,68,2,4,0,195,133,217,18,207,6,6,231,155,145,230,142,167,39,0,147,128,159,145,14,4,6,88,85,57,77,122,75,2,4,0,195,133,217,18,210,6,13,103,104,104,104,229,143,145,230,140,165,229,165,189,39,0,147,128,159,145,14,4,6,88,101,101,105,77,89,2,4,0,195,133,217,18,218,6,4,54,54,54,57,39,0,147,128,159,145,14,4,6,111,121,69,56,121,53,2,4,0,195,133,217,18,223,6,4,240,159,152,131,39,0,147,128,159,145,14,4,6,80,69,50,72,56,68,2,4,0,195,133,217,18,226,6,12,229,185,178,230,180,187,229,147,136,229,147,136,39,0,147,128,159,145,14,4,6,72,80,78,114,99,102,2,6,0,195,133,217,18,231,6,8,98,103,95,99,111,108,111,114,11,34,48,120,49,97,55,100,102,52,97,34,134,195,133,217,18,232,6,10,102,111,110,116,95,99,111,108,111,114,11,34,48,120,49,101,97,56,102,48,54,34,132,195,133,217,18,233,6,4,54,54,54,57,134,195,133,217,18,237,6,10,102,111,110,116,95,99,111,108,111,114,4,110,117,108,108,134,195,133,217,18,238,6,8,98,103,95,99,111,108,111,114,4,110,117,108,108,39,0,147,128,159,145,14,4,6,55,81,111,111,73,112,2,39,0,147,128,159,145,14,4,6,120,117,78,48,102,83,2,4,0,195,133,217,18,241,6,44,75,101,121,98,111,97,114,100,32,115,104,111,114,116,99,117,116,115,44,32,109,97,114,107,100,111,119,110,44,32,97,110,100,32,99,111,100,101,32,98,108,111,99,107,39,0,147,128,159,145,14,4,6,108,107,118,90,121,113,2,4,0,195,133,217,18,158,7,19,75,101,121,98,111,97,114,100,32,115,104,111,114,116,99,117,116,115,32,134,195,133,217,18,177,7,4,104,114,101,102,68,34,104,116,116,112,115,58,47,47,97,112,112,102,108,111,119,121,46,103,105,116,98,111,111,107,46,105,111,47,100,111,99,115,47,101,115,115,101,110,116,105,97,108,45,100,111,99,117,109,101,110,116,97,116,105,111,110,47,115,104,111,114,116,99,117,116,115,34,132,195,133,217,18,178,7,5,103,117,105,100,101,134,195,133,217,18,183,7,4,104,114,101,102,4,110,117,108,108,39,0,147,128,159,145,14,4,6,97,50,90,104,55,55,2,4,0,195,133,217,18,185,7,9,77,97,114,107,100,111,119,110,32,134,195,133,217,18,194,7,4,104,114,101,102,67,34,104,116,116,112,115,58,47,47,97,112,112,102,108,111,119,121,46,103,105,116,98,111,111,107,46,105,111,47,100,111,99,115,47,101,115,115,101,110,116,105,97,108,45,100,111,99,117,109,101,110,116,97,116,105,111,110,47,109,97,114,107,100,111,119,110,34,132,195,133,217,18,195,7,9,114,101,102,101,114,101,110,99,101,134,195,133,217,18,204,7,4,104,114,101,102,4,110,117,108,108,39,0,147,128,159,145,14,4,6,122,122,70,106,54,119,2,4,0,195,133,217,18,206,7,3,105,106,106,39,0,147,128,159,145,14,4,6,72,85,89,49,86,115,2,4,0,195,133,217,18,210,7,4,106,107,110,98,39,0,147,128,159,145,14,4,6,102,79,45,120,115,55,2,4,0,195,133,217,18,215,7,12,232,191,155,230,173,165,230,156,186,228,188,154,39,0,147,128,159,145,14,4,6,51,110,110,101,106,112,2,4,0,195,133,217,18,220,7,12,230,150,164,230,150,164,232,174,161,232,190,131,39,0,147,128,159,145,14,4,6,109,54,68,111,117,70,2,4,0,195,133,217,18,225,7,5,84,121,112,101,32,134,195,133,217,18,230,7,4,99,111,100,101,4,116,114,117,101,132,195,133,217,18,231,7,28,47,99,111,100,101,32,116,111,32,105,110,115,101,114,116,32,97,32,99,111,100,101,32,98,108,111,99,107,134,195,133,217,18,131,8,4,99,111,100,101,4,110,117,108,108,39,0,147,128,159,145,14,4,6,51,119,76,72,119,80,2,4,0,195,133,217,18,133,8,13,98,117,108,108,101,116,101,100,32,108,105,115,116,39,0,147,128,159,145,14,4,6,73,57,55,106,83,103,2,4,0,195,133,217,18,147,8,7,99,104,105,108,100,45,49,39,0,147,128,159,145,14,4,6,114,55,104,73,74,95,2,4,0,195,133,217,18,155,8,9,99,104,105,108,100,45,49,45,49,39,0,147,128,159,145,14,4,6,53,74,52,110,52,56,2,4,0,195,133,217,18,165,8,9,99,104,105,108,100,45,49,45,50,39,0,147,128,159,145,14,4,6,82,105,78,75,118,55,2,4,0,195,133,217,18,175,8,7,99,104,105,108,100,45,50,39,0,147,128,159,145,14,4,6,57,119,57,113,66,45,2,4,0,195,133,217,18,183,8,3,49,50,51,39,0,147,128,159,145,14,4,6,84,81,109,75,119,97,2,4,0,195,133,217,18,187,8,18,72,97,118,101,32,97,32,113,117,101,115,116,105,111,110,226,157,147,39,0,147,128,159,145,14,4,6,50,83,115,67,101,65,2,4,0,195,133,217,18,204,8,6,67,108,105,99,107,32,134,195,133,217,18,210,8,4,99,111,100,101,4,116,114,117,101,132,195,133,217,18,211,8,1,63,134,195,133,217,18,212,8,4,99,111,100,101,4,110,117,108,108,132,195,133,217,18,213,8,42,32,97,116,32,116,104,101,32,98,111,116,116,111,109,32,114,105,103,104,116,32,102,111,114,32,104,101,108,112,32,97,110,100,32,115,117,112,112,111,114,116,46,39,0,147,128,159,145,14,1,6,118,71,89,57,89,100,1,40,0,195,133,217,18,128,9,2,105,100,1,119,6,118,71,89,57,89,100,40,0,195,133,217,18,128,9,2,116,121,1,119,7,104,101,97,100,105,110,103,40,0,195,133,217,18,128,9,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,128,9,8,99,104,105,108,100,114,101,110,1,119,6,122,55,68,52,54,115,40,0,195,133,217,18,128,9,4,100,97,116,97,1,119,46,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,49,50,51,32,36,32,32,101,114,32,32,34,125,93,44,34,108,101,118,101,108,34,58,50,125,40,0,195,133,217,18,128,9,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,128,9,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,122,55,68,52,54,115,0,200,195,133,217,18,71,195,133,217,18,99,1,119,6,118,71,89,57,89,100,39,0,147,128,159,145,14,1,6,115,45,78,113,116,119,1,40,0,195,133,217,18,138,9,2,105,100,1,119,6,115,45,78,113,116,119,40,0,195,133,217,18,138,9,2,116,121,1,119,7,104,101,97,100,105,110,103,40,0,195,133,217,18,138,9,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,138,9,8,99,104,105,108,100,114,101,110,1,119,6,85,65,51,119,110,54,40,0,195,133,217,18,138,9,4,100,97,116,97,1,119,38,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,49,50,51,34,125,93,44,34,108,101,118,101,108,34,58,51,125,40,0,195,133,217,18,138,9,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,138,9,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,85,65,51,119,110,54,0,200,195,133,217,18,137,9,195,133,217,18,99,1,119,6,115,45,78,113,116,119,39,0,147,128,159,145,14,1,6,75,55,102,84,65,65,1,40,0,195,133,217,18,148,9,2,105,100,1,119,6,75,55,102,84,65,65,40,0,195,133,217,18,148,9,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,195,133,217,18,148,9,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,148,9,8,99,104,105,108,100,114,101,110,1,119,6,88,112,113,70,83,99,40,0,195,133,217,18,148,9,4,100,97,116,97,1,119,44,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,99,104,101,99,107,101,100,32,116,111,100,111,32,108,105,115,116,32,36,34,125,93,125,40,0,195,133,217,18,148,9,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,148,9,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,88,112,113,70,83,99,0,200,195,133,217,18,147,9,195,133,217,18,99,1,119,6,75,55,102,84,65,65,39,0,147,128,159,145,14,1,6,113,84,87,120,77,103,1,40,0,195,133,217,18,158,9,2,105,100,1,119,6,113,84,87,120,77,103,40,0,195,133,217,18,158,9,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,195,133,217,18,158,9,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,158,9,8,99,104,105,108,100,114,101,110,1,119,6,115,116,70,77,88,66,40,0,195,133,217,18,158,9,4,100,97,116,97,1,119,12,123,34,100,101,108,116,97,34,58,91,93,125,40,0,195,133,217,18,158,9,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,158,9,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,115,116,70,77,88,66,0,200,195,133,217,18,157,9,195,133,217,18,99,1,119,6,113,84,87,120,77,103,39,0,147,128,159,145,14,1,6,79,116,113,105,98,55,1,40,0,195,133,217,18,168,9,2,105,100,1,119,6,79,116,113,105,98,55,40,0,195,133,217,18,168,9,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,195,133,217,18,168,9,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,168,9,8,99,104,105,108,100,114,101,110,1,119,6,53,56,45,56,70,76,40,0,195,133,217,18,168,9,4,100,97,116,97,1,119,205,1,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,108,111,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,76,101,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,110,103,32,116,101,120,116,34,125,93,125,40,0,195,133,217,18,168,9,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,168,9,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,53,56,45,56,70,76,0,200,195,133,217,18,167,9,195,133,217,18,99,1,119,6,79,116,113,105,98,55,39,0,147,128,159,145,14,1,6,120,87,45,65,56,86,1,40,0,195,133,217,18,178,9,2,105,100,1,119,6,120,87,45,65,56,86,40,0,195,133,217,18,178,9,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,195,133,217,18,178,9,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,178,9,8,99,104,105,108,100,114,101,110,1,119,6,103,84,78,70,76,73,40,0,195,133,217,18,178,9,4,100,97,116,97,1,119,12,123,34,100,101,108,116,97,34,58,91,93,125,40,0,195,133,217,18,178,9,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,178,9,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,103,84,78,70,76,73,0,200,195,133,217,18,177,9,195,133,217,18,99,1,119,6,120,87,45,65,56,86,39,0,147,128,159,145,14,1,6,112,109,117,76,121,114,1,40,0,195,133,217,18,188,9,2,105,100,1,119,6,112,109,117,76,121,114,40,0,195,133,217,18,188,9,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,195,133,217,18,188,9,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,188,9,8,99,104,105,108,100,114,101,110,1,119,6,100,121,111,70,45,65,40,0,195,133,217,18,188,9,4,100,97,116,97,1,119,29,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,119,105,116,104,34,125,93,125,40,0,195,133,217,18,188,9,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,188,9,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,100,121,111,70,45,65,0,200,195,133,217,18,187,9,195,133,217,18,99,1,119,6,112,109,117,76,121,114,39,0,147,128,159,145,14,1,6,88,87,120,55,57,115,1,40,0,195,133,217,18,198,9,2,105,100,1,119,6,88,87,120,55,57,115,40,0,195,133,217,18,198,9,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,195,133,217,18,198,9,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,198,9,8,99,104,105,108,100,114,101,110,1,119,6,88,67,102,53,75,117,40,0,195,133,217,18,198,9,4,100,97,116,97,1,119,12,123,34,100,101,108,116,97,34,58,91,93,125,40,0,195,133,217,18,198,9,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,198,9,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,88,67,102,53,75,117,0,200,195,133,217,18,197,9,195,133,217,18,99,1,119,6,88,87,120,55,57,115,39,0,147,128,159,145,14,1,6,87,50,72,99,77,83,1,40,0,195,133,217,18,208,9,2,105,100,1,119,6,87,50,72,99,77,83,40,0,195,133,217,18,208,9,2,116,121,1,119,5,113,117,111,116,101,40,0,195,133,217,18,208,9,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,208,9,8,99,104,105,108,100,114,101,110,1,119,6,76,82,68,50,65,86,40,0,195,133,217,18,208,9,4,100,97,116,97,1,119,36,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,229,144,140,228,184,128,228,184,170,110,105,34,125,93,125,40,0,195,133,217,18,208,9,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,208,9,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,76,82,68,50,65,86,0,200,195,133,217,18,207,9,195,133,217,18,99,1,119,6,87,50,72,99,77,83,39,0,147,128,159,145,14,1,6,114,45,105,49,57,106,1,40,0,195,133,217,18,218,9,2,105,100,1,119,6,114,45,105,49,57,106,40,0,195,133,217,18,218,9,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,195,133,217,18,218,9,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,218,9,8,99,104,105,108,100,114,101,110,1,119,6,76,97,68,83,112,65,40,0,195,133,217,18,218,9,4,100,97,116,97,1,119,80,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,67,108,105,99,107,32,97,110,121,119,104,101,114,101,32,97,110,100,32,106,117,115,116,32,115,116,97,114,116,32,116,121,112,105,110,103,229,147,136,229,147,136,229,147,136,46,229,176,177,229,135,160,229,174,182,34,125,93,125,40,0,195,133,217,18,218,9,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,218,9,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,76,97,68,83,112,65,0,200,195,133,217,18,217,9,195,133,217,18,99,1,119,6,114,45,105,49,57,106,39,0,147,128,159,145,14,1,6,45,77,89,115,65,114,1,40,0,195,133,217,18,228,9,2,105,100,1,119,6,45,77,89,115,65,114,40,0,195,133,217,18,228,9,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,195,133,217,18,228,9,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,228,9,8,99,104,105,108,100,114,101,110,1,119,6,70,122,111,65,105,114,40,0,195,133,217,18,228,9,4,100,97,116,97,1,119,12,123,34,100,101,108,116,97,34,58,91,93,125,40,0,195,133,217,18,228,9,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,228,9,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,70,122,111,65,105,114,0,200,195,133,217,18,227,9,195,133,217,18,99,1,119,6,45,77,89,115,65,114,39,0,147,128,159,145,14,1,6,53,99,99,77,84,71,1,40,0,195,133,217,18,238,9,2,105,100,1,119,6,53,99,99,77,84,71,40,0,195,133,217,18,238,9,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,195,133,217,18,238,9,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,238,9,8,99,104,105,108,100,114,101,110,1,119,6,105,85,97,67,74,107,40,0,195,133,217,18,238,9,4,100,97,116,97,1,119,183,3,123,34,100,101,108,116,97,34,58,91,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,98,103,95,99,111,108,111,114,34,58,34,48,120,98,51,102,102,101,98,51,98,34,125,44,34,105,110,115,101,114,116,34,58,34,72,105,103,104,108,105,103,104,116,32,34,125,44,123,34,105,110,115,101,114,116,34,58,34,97,110,121,32,116,101,120,116,44,32,97,110,100,32,117,115,101,32,116,104,101,32,101,100,105,116,105,110,103,32,109,101,110,117,32,116,111,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,105,116,97,108,105,99,34,58,116,114,117,101,125,44,34,105,110,115,101,114,116,34,58,34,115,116,121,108,101,34,125,44,123,34,105,110,115,101,114,116,34,58,34,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,98,111,108,100,34,58,116,114,117,101,125,44,34,105,110,115,101,114,116,34,58,34,121,111,117,114,34,125,44,123,34,105,110,115,101,114,116,34,58,34,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,117,110,100,101,114,108,105,110,101,34,58,116,114,117,101,125,44,34,105,110,115,101,114,116,34,58,34,119,114,105,116,105,110,103,34,125,44,123,34,105,110,115,101,114,116,34,58,34,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,99,111,100,101,34,58,116,114,117,101,125,44,34,105,110,115,101,114,116,34,58,34,104,111,119,101,118,101,114,34,125,44,123,34,105,110,115,101,114,116,34,58,34,32,121,111,117,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,115,116,114,105,107,101,116,104,114,111,117,103,104,34,58,116,114,117,101,125,44,34,105,110,115,101,114,116,34,58,34,108,105,107,101,46,34,125,93,125,40,0,195,133,217,18,238,9,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,238,9,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,105,85,97,67,74,107,0,200,195,133,217,18,237,9,195,133,217,18,99,1,119,6,53,99,99,77,84,71,39,0,147,128,159,145,14,1,6,57,88,75,76,69,115,1,40,0,195,133,217,18,248,9,2,105,100,1,119,6,57,88,75,76,69,115,40,0,195,133,217,18,248,9,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,195,133,217,18,248,9,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,248,9,8,99,104,105,108,100,114,101,110,1,119,6,82,116,101,71,70,116,40,0,195,133,217,18,248,9,4,100,97,116,97,1,119,12,123,34,100,101,108,116,97,34,58,91,93,125,40,0,195,133,217,18,248,9,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,248,9,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,82,116,101,71,70,116,0,200,195,133,217,18,247,9,195,133,217,18,99,1,119,6,57,88,75,76,69,115,39,0,147,128,159,145,14,1,6,45,99,53,69,50,113,1,40,0,195,133,217,18,130,10,2,105,100,1,119,6,45,99,53,69,50,113,40,0,195,133,217,18,130,10,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,195,133,217,18,130,10,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,130,10,8,99,104,105,108,100,114,101,110,1,119,6,90,52,77,78,84,95,40,0,195,133,217,18,130,10,4,100,97,116,97,1,119,255,1,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,65,115,32,115,111,111,110,32,97,115,32,121,111,117,32,116,121,112,101,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,102,111,110,116,95,99,111,108,111,114,34,58,34,48,120,49,48,48,98,53,102,102,34,125,44,34,105,110,115,101,114,116,34,58,34,47,34,125,44,123,34,105,110,115,101,114,116,34,58,34,32,97,32,109,101,110,117,32,119,105,108,108,32,112,111,112,32,117,112,46,32,83,101,108,101,99,116,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,98,103,95,99,111,108,111,114,34,58,34,48,120,98,51,57,99,50,55,98,48,34,125,44,34,105,110,115,101,114,116,34,58,34,100,105,102,102,101,114,101,110,116,32,116,121,112,101,115,34,125,44,123,34,105,110,115,101,114,116,34,58,34,32,111,102,32,99,111,110,116,101,110,116,32,98,108,111,99,107,115,32,121,111,117,32,99,97,110,32,97,100,100,46,34,125,93,125,40,0,195,133,217,18,130,10,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,130,10,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,90,52,77,78,84,95,0,200,195,133,217,18,129,10,195,133,217,18,99,1,119,6,45,99,53,69,50,113,39,0,147,128,159,145,14,1,6,108,73,53,65,67,48,1,40,0,195,133,217,18,140,10,2,105,100,1,119,6,108,73,53,65,67,48,40,0,195,133,217,18,140,10,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,195,133,217,18,140,10,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,140,10,8,99,104,105,108,100,114,101,110,1,119,6,57,86,108,107,82,87,40,0,195,133,217,18,140,10,4,100,97,116,97,1,119,12,123,34,100,101,108,116,97,34,58,91,93,125,40,0,195,133,217,18,140,10,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,140,10,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,57,86,108,107,82,87,0,200,195,133,217,18,139,10,195,133,217,18,99,1,119,6,108,73,53,65,67,48,39,0,147,128,159,145,14,1,6,115,98,73,99,106,103,1,40,0,195,133,217,18,150,10,2,105,100,1,119,6,115,98,73,99,106,103,40,0,195,133,217,18,150,10,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,195,133,217,18,150,10,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,150,10,8,99,104,105,108,100,114,101,110,1,119,6,56,119,111,119,68,50,40,0,195,133,217,18,150,10,4,100,97,116,97,1,119,228,1,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,84,121,112,101,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,99,111,100,101,34,58,116,114,117,101,125,44,34,105,110,115,101,114,116,34,58,34,47,34,125,44,123,34,105,110,115,101,114,116,34,58,34,32,102,111,108,108,111,119,101,100,32,98,121,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,99,111,100,101,34,58,116,114,117,101,125,44,34,105,110,115,101,114,116,34,58,34,47,98,117,108,108,101,116,34,125,44,123,34,105,110,115,101,114,116,34,58,34,32,111,114,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,99,111,100,101,34,58,116,114,117,101,125,44,34,105,110,115,101,114,116,34,58,34,47,110,117,109,32,116,111,32,99,114,101,97,116,101,32,97,32,108,105,115,116,46,34,125,93,125,40,0,195,133,217,18,150,10,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,150,10,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,56,119,111,119,68,50,0,200,195,133,217,18,149,10,195,133,217,18,99,1,119,6,115,98,73,99,106,103,39,0,147,128,159,145,14,1,6,65,72,89,121,80,85,1,40,0,195,133,217,18,160,10,2,105,100,1,119,6,65,72,89,121,80,85,40,0,195,133,217,18,160,10,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,195,133,217,18,160,10,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,160,10,8,99,104,105,108,100,114,101,110,1,119,6,70,68,79,70,76,77,40,0,195,133,217,18,160,10,4,100,97,116,97,1,119,12,123,34,100,101,108,116,97,34,58,91,93,125,40,0,195,133,217,18,160,10,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,160,10,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,70,68,79,70,76,77,0,200,195,133,217,18,159,10,195,133,217,18,99,1,119,6,65,72,89,121,80,85,39,0,147,128,159,145,14,1,6,72,110,55,49,119,97,1,40,0,195,133,217,18,170,10,2,105,100,1,119,6,72,110,55,49,119,97,40,0,195,133,217,18,170,10,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,195,133,217,18,170,10,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,170,10,8,99,104,105,108,100,114,101,110,1,119,6,80,55,68,49,81,54,40,0,195,133,217,18,170,10,4,100,97,116,97,1,119,207,1,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,67,108,105,99,107,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,99,111,100,101,34,58,116,114,117,101,125,44,34,105,110,115,101,114,116,34,58,34,43,32,78,101,119,32,80,97,103,101,32,34,125,44,123,34,105,110,115,101,114,116,34,58,34,98,117,116,116,111,110,32,97,116,32,116,104,101,32,98,111,116,116,111,109,32,111,102,32,121,111,117,114,32,115,105,100,101,98,97,114,32,116,111,32,97,100,100,32,97,32,110,101,119,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,98,111,108,100,34,58,116,114,117,101,125,44,34,105,110,115,101,114,116,34,58,34,112,97,103,101,34,125,44,123,34,105,110,115,101,114,116,34,58,34,46,34,125,93,125,40,0,195,133,217,18,170,10,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,170,10,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,80,55,68,49,81,54,0,200,195,133,217,18,169,10,195,133,217,18,99,1,119,6,72,110,55,49,119,97,39,0,147,128,159,145,14,1,6,56,120,67,119,110,83,1,40,0,195,133,217,18,180,10,2,105,100,1,119,6,56,120,67,119,110,83,40,0,195,133,217,18,180,10,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,195,133,217,18,180,10,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,180,10,8,99,104,105,108,100,114,101,110,1,119,6,114,116,113,68,113,100,40,0,195,133,217,18,180,10,4,100,97,116,97,1,119,12,123,34,100,101,108,116,97,34,58,91,93,125,40,0,195,133,217,18,180,10,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,180,10,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,114,116,113,68,113,100,0,200,195,133,217,18,179,10,195,133,217,18,99,1,119,6,56,120,67,119,110,83,39,0,147,128,159,145,14,1,6,67,79,113,95,50,67,1,40,0,195,133,217,18,190,10,2,105,100,1,119,6,67,79,113,95,50,67,40,0,195,133,217,18,190,10,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,195,133,217,18,190,10,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,190,10,8,99,104,105,108,100,114,101,110,1,119,6,109,54,56,82,52,55,40,0,195,133,217,18,190,10,4,100,97,116,97,1,119,233,3,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,67,108,105,99,107,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,99,111,100,101,34,58,116,114,117,101,125,44,34,105,110,115,101,114,116,34,58,34,43,34,125,44,123,34,105,110,115,101,114,116,34,58,34,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,98,103,95,99,111,108,111,114,34,58,34,48,120,49,102,102,100,97,101,54,34,44,34,102,111,110,116,95,99,111,108,111,114,34,58,34,48,120,49,100,98,51,54,51,54,34,125,44,34,105,110,115,101,114,116,34,58,34,110,101,120,116,34,125,44,123,34,105,110,115,101,114,116,34,58,34,32,116,111,32,97,110,121,32,112,97,103,101,32,116,105,116,108,101,32,105,110,32,116,104,101,32,115,105,100,101,98,97,114,32,116,111,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,102,111,110,116,95,99,111,108,111,114,34,58,34,48,120,49,56,52,50,55,101,48,34,125,44,34,105,110,115,101,114,116,34,58,34,113,117,105,99,107,108,121,34,125,44,123,34,105,110,115,101,114,116,34,58,34,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,105,116,97,108,105,99,34,58,116,114,117,101,44,34,115,116,114,105,107,101,116,104,114,111,117,103,104,34,58,116,114,117,101,44,34,117,110,100,101,114,108,105,110,101,34,58,116,114,117,101,125,44,34,105,110,115,101,114,116,34,58,34,230,140,168,233,161,191,230,137,147,34,125,44,123,34,105,110,115,101,114,116,34,58,34,32,97,32,110,101,119,32,115,117,98,112,97,103,101,44,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,99,111,100,101,34,58,116,114,117,101,125,44,34,105,110,115,101,114,116,34,58,34,68,111,99,117,109,101,110,116,44,32,71,114,105,100,44,32,111,114,32,75,97,110,98,97,110,32,66,111,97,114,100,46,34,125,93,125,40,0,195,133,217,18,190,10,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,190,10,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,109,54,56,82,52,55,0,200,195,133,217,18,189,10,195,133,217,18,99,1,119,6,67,79,113,95,50,67,39,0,147,128,159,145,14,1,6,114,71,120,87,45,114,1,40,0,195,133,217,18,200,10,2,105,100,1,119,6,114,71,120,87,45,114,40,0,195,133,217,18,200,10,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,195,133,217,18,200,10,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,200,10,8,99,104,105,108,100,114,101,110,1,119,6,112,82,70,53,54,68,40,0,195,133,217,18,200,10,4,100,97,116,97,1,119,12,123,34,100,101,108,116,97,34,58,91,93,125,40,0,195,133,217,18,200,10,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,200,10,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,112,82,70,53,54,68,0,200,195,133,217,18,199,10,195,133,217,18,99,1,119,6,114,71,120,87,45,114,39,0,147,128,159,145,14,1,6,102,48,55,89,71,81,1,40,0,195,133,217,18,210,10,2,105,100,1,119,6,102,48,55,89,71,81,40,0,195,133,217,18,210,10,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,195,133,217,18,210,10,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,210,10,8,99,104,105,108,100,114,101,110,1,119,6,78,56,98,56,111,65,40,0,195,133,217,18,210,10,4,100,97,116,97,1,119,101,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,228,189,147,233,170,140,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,115,116,114,105,107,101,116,104,114,111,117,103,104,34,58,116,114,117,101,44,34,117,110,100,101,114,108,105,110,101,34,58,116,114,117,101,125,44,34,105,110,115,101,114,116,34,58,34,228,184,128,34,125,93,125,40,0,195,133,217,18,210,10,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,210,10,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,78,56,98,56,111,65,0,200,195,133,217,18,209,10,195,133,217,18,99,1,119,6,102,48,55,89,71,81,39,0,147,128,159,145,14,1,6,68,73,122,109,100,87,1,40,0,195,133,217,18,220,10,2,105,100,1,119,6,68,73,122,109,100,87,40,0,195,133,217,18,220,10,2,116,121,1,119,5,113,117,111,116,101,40,0,195,133,217,18,220,10,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,220,10,8,99,104,105,108,100,114,101,110,1,119,6,55,114,82,118,114,89,40,0,195,133,217,18,220,10,4,100,97,116,97,1,119,31,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,231,155,145,230,142,167,34,125,93,125,40,0,195,133,217,18,220,10,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,220,10,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,55,114,82,118,114,89,0,200,195,133,217,18,219,10,195,133,217,18,99,1,119,6,68,73,122,109,100,87,39,0,147,128,159,145,14,1,6,88,104,87,98,66,48,1,40,0,195,133,217,18,230,10,2,105,100,1,119,6,88,104,87,98,66,48,40,0,195,133,217,18,230,10,2,116,121,1,119,5,113,117,111,116,101,40,0,195,133,217,18,230,10,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,230,10,8,99,104,105,108,100,114,101,110,1,119,6,69,110,114,122,69,100,40,0,195,133,217,18,230,10,4,100,97,116,97,1,119,38,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,103,104,104,104,229,143,145,230,140,165,229,165,189,34,125,93,125,40,0,195,133,217,18,230,10,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,230,10,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,69,110,114,122,69,100,0,200,195,133,217,18,229,10,195,133,217,18,99,1,119,6,88,104,87,98,66,48,39,0,147,128,159,145,14,1,6,117,122,54,88,89,118,1,40,0,195,133,217,18,240,10,2,105,100,1,119,6,117,122,54,88,89,118,40,0,195,133,217,18,240,10,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,195,133,217,18,240,10,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,240,10,8,99,104,105,108,100,114,101,110,1,119,6,80,72,76,53,98,110,40,0,195,133,217,18,240,10,4,100,97,116,97,1,119,29,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,54,54,54,57,34,125,93,125,40,0,195,133,217,18,240,10,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,240,10,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,80,72,76,53,98,110,0,200,195,133,217,18,239,10,195,133,217,18,99,1,119,6,117,122,54,88,89,118,39,0,147,128,159,145,14,1,6,51,110,100,90,103,51,1,40,0,195,133,217,18,250,10,2,105,100,1,119,6,51,110,100,90,103,51,40,0,195,133,217,18,250,10,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,195,133,217,18,250,10,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,250,10,8,99,104,105,108,100,114,101,110,1,119,6,79,120,115,115,72,77,40,0,195,133,217,18,250,10,4,100,97,116,97,1,119,29,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,240,159,152,131,34,125,93,125,40,0,195,133,217,18,250,10,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,250,10,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,79,120,115,115,72,77,0,200,195,133,217,18,249,10,195,133,217,18,99,1,119,6,51,110,100,90,103,51,39,0,147,128,159,145,14,1,6,110,103,95,69,109,50,1,40,0,195,133,217,18,132,11,2,105,100,1,119,6,110,103,95,69,109,50,40,0,195,133,217,18,132,11,2,116,121,1,119,5,113,117,111,116,101,40,0,195,133,217,18,132,11,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,132,11,8,99,104,105,108,100,114,101,110,1,119,6,102,107,66,48,107,107,40,0,195,133,217,18,132,11,4,100,97,116,97,1,119,37,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,229,185,178,230,180,187,229,147,136,229,147,136,34,125,93,125,40,0,195,133,217,18,132,11,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,132,11,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,102,107,66,48,107,107,0,200,195,133,217,18,131,11,195,133,217,18,99,1,119,6,110,103,95,69,109,50,39,0,147,128,159,145,14,1,6,102,84,86,120,101,99,1,40,0,195,133,217,18,142,11,2,105,100,1,119,6,102,84,86,120,101,99,40,0,195,133,217,18,142,11,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,195,133,217,18,142,11,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,142,11,8,99,104,105,108,100,114,101,110,1,119,6,99,107,80,105,100,83,40,0,195,133,217,18,142,11,4,100,97,116,97,1,119,92,123,34,100,101,108,116,97,34,58,91,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,98,103,95,99,111,108,111,114,34,58,34,48,120,49,97,55,100,102,52,97,34,44,34,102,111,110,116,95,99,111,108,111,114,34,58,34,48,120,49,101,97,56,102,48,54,34,125,44,34,105,110,115,101,114,116,34,58,34,54,54,54,57,34,125,93,125,40,0,195,133,217,18,142,11,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,142,11,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,99,107,80,105,100,83,0,200,195,133,217,18,141,11,195,133,217,18,99,1,119,6,102,84,86,120,101,99,39,0,147,128,159,145,14,1,6,111,56,70,84,77,117,1,40,0,195,133,217,18,152,11,2,105,100,1,119,6,111,56,70,84,77,117,40,0,195,133,217,18,152,11,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,195,133,217,18,152,11,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,152,11,8,99,104,105,108,100,114,101,110,1,119,6,108,119,100,112,111,55,40,0,195,133,217,18,152,11,4,100,97,116,97,1,119,12,123,34,100,101,108,116,97,34,58,91,93,125,40,0,195,133,217,18,152,11,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,152,11,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,108,119,100,112,111,55,0,200,195,133,217,18,151,11,195,133,217,18,99,1,119,6,111,56,70,84,77,117,39,0,147,128,159,145,14,1,6,82,74,81,67,50,82,1,40,0,195,133,217,18,162,11,2,105,100,1,119,6,82,74,81,67,50,82,40,0,195,133,217,18,162,11,2,116,121,1,119,7,104,101,97,100,105,110,103,40,0,195,133,217,18,162,11,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,162,11,8,99,104,105,108,100,114,101,110,1,119,6,119,97,87,79,66,52,40,0,195,133,217,18,162,11,4,100,97,116,97,1,119,79,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,75,101,121,98,111,97,114,100,32,115,104,111,114,116,99,117,116,115,44,32,109,97,114,107,100,111,119,110,44,32,97,110,100,32,99,111,100,101,32,98,108,111,99,107,34,125,93,44,34,108,101,118,101,108,34,58,50,125,40,0,195,133,217,18,162,11,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,162,11,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,119,97,87,79,66,52,0,200,195,133,217,18,161,11,195,133,217,18,99,1,119,6,82,74,81,67,50,82,39,0,147,128,159,145,14,1,6,117,70,54,49,55,109,1,40,0,195,133,217,18,172,11,2,105,100,1,119,6,117,70,54,49,55,109,40,0,195,133,217,18,172,11,2,116,121,1,119,13,110,117,109,98,101,114,101,100,95,108,105,115,116,40,0,195,133,217,18,172,11,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,172,11,8,99,104,105,108,100,114,101,110,1,119,6,55,118,110,77,69,78,40,0,195,133,217,18,172,11,4,100,97,116,97,1,119,154,1,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,75,101,121,98,111,97,114,100,32,115,104,111,114,116,99,117,116,115,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,104,114,101,102,34,58,34,104,116,116,112,115,58,47,47,97,112,112,102,108,111,119,121,46,103,105,116,98,111,111,107,46,105,111,47,100,111,99,115,47,101,115,115,101,110,116,105,97,108,45,100,111,99,117,109,101,110,116,97,116,105,111,110,47,115,104,111,114,116,99,117,116,115,34,125,44,34,105,110,115,101,114,116,34,58,34,103,117,105,100,101,34,125,93,125,40,0,195,133,217,18,172,11,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,172,11,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,55,118,110,77,69,78,0,200,195,133,217,18,171,11,195,133,217,18,99,1,119,6,117,70,54,49,55,109,39,0,147,128,159,145,14,1,6,76,87,83,67,73,57,1,40,0,195,133,217,18,182,11,2,105,100,1,119,6,76,87,83,67,73,57,40,0,195,133,217,18,182,11,2,116,121,1,119,13,110,117,109,98,101,114,101,100,95,108,105,115,116,40,0,195,133,217,18,182,11,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,182,11,8,99,104,105,108,100,114,101,110,1,119,6,111,67,89,117,74,57,40,0,195,133,217,18,182,11,4,100,97,116,97,1,119,147,1,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,77,97,114,107,100,111,119,110,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,104,114,101,102,34,58,34,104,116,116,112,115,58,47,47,97,112,112,102,108,111,119,121,46,103,105,116,98,111,111,107,46,105,111,47,100,111,99,115,47,101,115,115,101,110,116,105,97,108,45,100,111,99,117,109,101,110,116,97,116,105,111,110,47,109,97,114,107,100,111,119,110,34,125,44,34,105,110,115,101,114,116,34,58,34,114,101,102,101,114,101,110,99,101,34,125,93,125,40,0,195,133,217,18,182,11,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,182,11,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,111,67,89,117,74,57,0,200,195,133,217,18,181,11,195,133,217,18,99,1,119,6,76,87,83,67,73,57,39,0,147,128,159,145,14,1,6,110,98,54,83,103,101,1,40,0,195,133,217,18,192,11,2,105,100,1,119,6,110,98,54,83,103,101,40,0,195,133,217,18,192,11,2,116,121,1,119,13,110,117,109,98,101,114,101,100,95,108,105,115,116,40,0,195,133,217,18,192,11,6,112,97,114,101,110,116,1,119,6,76,87,83,67,73,57,40,0,195,133,217,18,192,11,8,99,104,105,108,100,114,101,110,1,119,6,105,87,100,115,72,89,40,0,195,133,217,18,192,11,4,100,97,116,97,1,119,28,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,105,106,106,34,125,93,125,40,0,195,133,217,18,192,11,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,192,11,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,105,87,100,115,72,89,0,8,0,195,133,217,18,190,11,1,119,6,110,98,54,83,103,101,39,0,147,128,159,145,14,1,6,77,73,113,111,117,90,1,40,0,195,133,217,18,202,11,2,105,100,1,119,6,77,73,113,111,117,90,40,0,195,133,217,18,202,11,2,116,121,1,119,13,110,117,109,98,101,114,101,100,95,108,105,115,116,40,0,195,133,217,18,202,11,6,112,97,114,101,110,116,1,119,6,110,98,54,83,103,101,40,0,195,133,217,18,202,11,8,99,104,105,108,100,114,101,110,1,119,6,88,65,95,122,90,115,40,0,195,133,217,18,202,11,4,100,97,116,97,1,119,29,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,106,107,110,98,34,125,93,125,40,0,195,133,217,18,202,11,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,202,11,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,88,65,95,122,90,115,0,8,0,195,133,217,18,200,11,1,119,6,77,73,113,111,117,90,39,0,147,128,159,145,14,1,6,98,112,45,97,121,53,1,40,0,195,133,217,18,212,11,2,105,100,1,119,6,98,112,45,97,121,53,40,0,195,133,217,18,212,11,2,116,121,1,119,13,110,117,109,98,101,114,101,100,95,108,105,115,116,40,0,195,133,217,18,212,11,6,112,97,114,101,110,116,1,119,6,77,73,113,111,117,90,40,0,195,133,217,18,212,11,8,99,104,105,108,100,114,101,110,1,119,6,73,84,120,118,112,57,40,0,195,133,217,18,212,11,4,100,97,116,97,1,119,37,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,232,191,155,230,173,165,230,156,186,228,188,154,34,125,93,125,40,0,195,133,217,18,212,11,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,212,11,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,73,84,120,118,112,57,0,8,0,195,133,217,18,210,11,1,119,6,98,112,45,97,121,53,39,0,147,128,159,145,14,1,6,56,121,102,112,65,112,1,40,0,195,133,217,18,222,11,2,105,100,1,119,6,56,121,102,112,65,112,40,0,195,133,217,18,222,11,2,116,121,1,119,13,110,117,109,98,101,114,101,100,95,108,105,115,116,40,0,195,133,217,18,222,11,6,112,97,114,101,110,116,1,119,6,77,73,113,111,117,90,40,0,195,133,217,18,222,11,8,99,104,105,108,100,114,101,110,1,119,6,45,109,54,99,76,105,40,0,195,133,217,18,222,11,4,100,97,116,97,1,119,37,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,230,150,164,230,150,164,232,174,161,232,190,131,34,125,93,125,40,0,195,133,217,18,222,11,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,222,11,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,45,109,54,99,76,105,0,136,195,133,217,18,221,11,1,119,6,56,121,102,112,65,112,39,0,147,128,159,145,14,1,6,111,109,54,99,122,66,1,40,0,195,133,217,18,232,11,2,105,100,1,119,6,111,109,54,99,122,66,40,0,195,133,217,18,232,11,2,116,121,1,119,13,110,117,109,98,101,114,101,100,95,108,105,115,116,40,0,195,133,217,18,232,11,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,232,11,8,99,104,105,108,100,114,101,110,1,119,6,68,82,102,101,121,118,40,0,195,133,217,18,232,11,4,100,97,116,97,1,119,99,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,84,121,112,101,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,99,111,100,101,34,58,116,114,117,101,125,44,34,105,110,115,101,114,116,34,58,34,47,99,111,100,101,32,116,111,32,105,110,115,101,114,116,32,97,32,99,111,100,101,32,98,108,111,99,107,34,125,93,125,40,0,195,133,217,18,232,11,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,232,11,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,68,82,102,101,121,118,0,200,195,133,217,18,191,11,195,133,217,18,99,1,119,6,111,109,54,99,122,66,39,0,147,128,159,145,14,1,6,65,120,74,79,67,97,1,40,0,195,133,217,18,242,11,2,105,100,1,119,6,65,120,74,79,67,97,40,0,195,133,217,18,242,11,2,116,121,1,119,13,98,117,108,108,101,116,101,100,95,108,105,115,116,40,0,195,133,217,18,242,11,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,242,11,8,99,104,105,108,100,114,101,110,1,119,6,49,52,84,74,100,51,40,0,195,133,217,18,242,11,4,100,97,116,97,1,119,38,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,98,117,108,108,101,116,101,100,32,108,105,115,116,34,125,93,125,40,0,195,133,217,18,242,11,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,242,11,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,49,52,84,74,100,51,0,200,195,133,217,18,241,11,195,133,217,18,99,1,119,6,65,120,74,79,67,97,39,0,147,128,159,145,14,1,6,48,55,67,110,83,97,1,40,0,195,133,217,18,252,11,2,105,100,1,119,6,48,55,67,110,83,97,40,0,195,133,217,18,252,11,2,116,121,1,119,13,98,117,108,108,101,116,101,100,95,108,105,115,116,40,0,195,133,217,18,252,11,6,112,97,114,101,110,116,1,119,6,65,120,74,79,67,97,40,0,195,133,217,18,252,11,8,99,104,105,108,100,114,101,110,1,119,6,89,108,74,119,83,49,40,0,195,133,217,18,252,11,4,100,97,116,97,1,119,32,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,99,104,105,108,100,45,49,34,125,93,125,40,0,195,133,217,18,252,11,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,252,11,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,89,108,74,119,83,49,0,8,0,195,133,217,18,250,11,1,119,6,48,55,67,110,83,97,39,0,147,128,159,145,14,1,6,95,57,76,55,68,108,1,40,0,195,133,217,18,134,12,2,105,100,1,119,6,95,57,76,55,68,108,40,0,195,133,217,18,134,12,2,116,121,1,119,13,98,117,108,108,101,116,101,100,95,108,105,115,116,40,0,195,133,217,18,134,12,6,112,97,114,101,110,116,1,119,6,48,55,67,110,83,97,40,0,195,133,217,18,134,12,8,99,104,105,108,100,114,101,110,1,119,6,68,90,49,112,114,48,40,0,195,133,217,18,134,12,4,100,97,116,97,1,119,34,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,99,104,105,108,100,45,49,45,49,34,125,93,125,40,0,195,133,217,18,134,12,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,134,12,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,68,90,49,112,114,48,0,8,0,195,133,217,18,132,12,1,119,6,95,57,76,55,68,108,39,0,147,128,159,145,14,1,6,118,109,65,70,53,76,1,40,0,195,133,217,18,144,12,2,105,100,1,119,6,118,109,65,70,53,76,40,0,195,133,217,18,144,12,2,116,121,1,119,13,98,117,108,108,101,116,101,100,95,108,105,115,116,40,0,195,133,217,18,144,12,6,112,97,114,101,110,116,1,119,6,48,55,67,110,83,97,40,0,195,133,217,18,144,12,8,99,104,105,108,100,114,101,110,1,119,6,118,87,53,73,57,111,40,0,195,133,217,18,144,12,4,100,97,116,97,1,119,34,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,99,104,105,108,100,45,49,45,50,34,125,93,125,40,0,195,133,217,18,144,12,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,144,12,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,118,87,53,73,57,111,0,136,195,133,217,18,143,12,1,119,6,118,109,65,70,53,76,39,0,147,128,159,145,14,1,6,121,55,78,87,55,99,1,40,0,195,133,217,18,154,12,2,105,100,1,119,6,121,55,78,87,55,99,40,0,195,133,217,18,154,12,2,116,121,1,119,13,98,117,108,108,101,116,101,100,95,108,105,115,116,40,0,195,133,217,18,154,12,6,112,97,114,101,110,116,1,119,6,65,120,74,79,67,97,40,0,195,133,217,18,154,12,8,99,104,105,108,100,114,101,110,1,119,6,79,51,103,80,85,76,40,0,195,133,217,18,154,12,4,100,97,116,97,1,119,32,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,99,104,105,108,100,45,50,34,125,93,125,40,0,195,133,217,18,154,12,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,154,12,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,79,51,103,80,85,76,0,136,195,133,217,18,133,12,1,119,6,121,55,78,87,55,99,39,0,147,128,159,145,14,1,6,83,102,48,120,100,54,1,40,0,195,133,217,18,164,12,2,105,100,1,119,6,83,102,48,120,100,54,40,0,195,133,217,18,164,12,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,195,133,217,18,164,12,6,112,97,114,101,110,116,1,119,6,65,120,74,79,67,97,40,0,195,133,217,18,164,12,8,99,104,105,108,100,114,101,110,1,119,6,78,76,97,67,89,115,33,0,195,133,217,18,164,12,4,100,97,116,97,1,40,0,195,133,217,18,164,12,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,164,12,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,78,76,97,67,89,115,0,136,195,133,217,18,163,12,1,119,6,83,102,48,120,100,54,39,0,147,128,159,145,14,1,6,54,104,67,112,68,80,1,40,0,195,133,217,18,174,12,2,105,100,1,119,6,54,104,67,112,68,80,40,0,195,133,217,18,174,12,2,116,121,1,119,7,104,101,97,100,105,110,103,40,0,195,133,217,18,174,12,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,174,12,8,99,104,105,108,100,114,101,110,1,119,6,57,77,71,74,107,73,40,0,195,133,217,18,174,12,4,100,97,116,97,1,119,53,123,34,108,101,118,101,108,34,58,50,44,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,72,97,118,101,32,97,32,113,117,101,115,116,105,111,110,226,157,147,34,125,93,125,40,0,195,133,217,18,174,12,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,174,12,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,57,77,71,74,107,73,0,200,195,133,217,18,251,11,195,133,217,18,99,1,119,6,54,104,67,112,68,80,39,0,147,128,159,145,14,1,6,90,68,65,45,49,122,1,40,0,195,133,217,18,184,12,2,105,100,1,119,6,90,68,65,45,49,122,40,0,195,133,217,18,184,12,2,116,121,1,119,5,113,117,111,116,101,40,0,195,133,217,18,184,12,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,184,12,8,99,104,105,108,100,114,101,110,1,119,6,122,50,122,106,95,53,40,0,195,133,217,18,184,12,4,100,97,116,97,1,119,129,1,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,67,108,105,99,107,32,34,125,44,123,34,97,116,116,114,105,98,117,116,101,115,34,58,123,34,99,111,100,101,34,58,116,114,117,101,125,44,34,105,110,115,101,114,116,34,58,34,63,34,125,44,123,34,105,110,115,101,114,116,34,58,34,32,97,116,32,116,104,101,32,98,111,116,116,111,109,32,114,105,103,104,116,32,102,111,114,32,104,101,108,112,32,97,110,100,32,115,117,112,112,111,114,116,46,34,125,93,125,40,0,195,133,217,18,184,12,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,184,12,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,122,50,122,106,95,53,0,200,195,133,217,18,183,12,195,133,217,18,99,1,119,6,90,68,65,45,49,122,39,0,147,128,159,145,14,4,6,110,66,48,103,72,72,2,4,0,195,133,217,18,194,12,15,229,129,165,229,186,183,233,130,163,232,190,185,85,73,105,168,195,133,217,18,88,1,119,56,123,34,99,104,101,99,107,101,100,34,58,102,97,108,115,101,44,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,229,129,165,229,186,183,233,130,163,232,190,185,85,73,105,34,125,93,125,39,0,147,128,159,145,14,4,6,112,119,95,118,45,79,2,33,0,147,128,159,145,14,1,6,102,77,98,109,66,116,1,0,7,33,0,147,128,159,145,14,3,6,122,104,84,113,87,83,1,129,195,133,217,18,99,1,39,0,147,128,159,145,14,1,6,69,120,118,84,102,57,1,40,0,195,133,217,18,214,12,2,105,100,1,119,6,69,120,118,84,102,57,40,0,195,133,217,18,214,12,2,116,121,1,119,5,105,109,97,103,101,40,0,195,133,217,18,214,12,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,214,12,8,99,104,105,108,100,114,101,110,1,119,6,57,87,71,65,49,95,33,0,195,133,217,18,214,12,4,100,97,116,97,1,40,0,195,133,217,18,214,12,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,214,12,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,57,87,71,65,49,95,0,200,195,133,217,18,99,195,133,217,18,213,12,1,119,6,69,120,118,84,102,57,39,0,147,128,159,145,14,4,6,119,48,80,67,108,103,2,39,0,147,128,159,145,14,1,6,102,100,85,89,106,108,1,40,0,195,133,217,18,225,12,2,105,100,1,119,6,102,100,85,89,106,108,40,0,195,133,217,18,225,12,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,195,133,217,18,225,12,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,225,12,8,99,104,105,108,100,114,101,110,1,119,6,68,107,98,56,50,87,40,0,195,133,217,18,225,12,4,100,97,116,97,1,119,12,123,34,100,101,108,116,97,34,58,91,93,125,40,0,195,133,217,18,225,12,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,225,12,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,68,107,98,56,50,87,0,136,195,133,217,18,213,12,1,119,6,102,100,85,89,106,108,168,195,133,217,18,219,12,1,119,212,1,123,34,117,114,108,34,58,34,104,116,116,112,115,58,47,47,105,109,97,103,101,115,46,117,110,115,112,108,97,115,104,46,99,111,109,47,112,104,111,116,111,45,49,55,49,51,48,57,56,48,57,56,56,51,51,45,102,52,53,100,49,56,48,99,97,57,97,50,63,99,114,111,112,61,101,110,116,114,111,112,121,38,99,115,61,116,105,110,121,115,114,103,98,38,102,105,116,61,109,97,120,38,102,109,61,106,112,103,38,105,120,105,100,61,77,51,119,49,77,84,69,49,77,122,100,56,77,72,119,120,102,72,74,104,98,109,82,118,98,88,120,56,102,72,120,56,102,72,120,56,102,68,69,51,77,84,77,49,78,68,73,51,78,84,74,56,38,105,120,108,105,98,61,114,98,45,52,46,48,46,51,38,113,61,56,48,38,119,61,49,48,56,48,34,44,34,97,108,105,103,110,34,58,34,99,101,110,116,101,114,34,125,39,0,147,128,159,145,14,4,6,70,80,88,99,109,117,2,39,0,147,128,159,145,14,1,6,109,99,66,107,98,115,1,40,0,195,133,217,18,237,12,2,105,100,1,119,6,109,99,66,107,98,115,40,0,195,133,217,18,237,12,2,116,121,1,119,9,112,97,114,97,103,114,97,112,104,40,0,195,133,217,18,237,12,6,112,97,114,101,110,116,1,119,10,85,86,79,107,81,88,110,117,86,114,40,0,195,133,217,18,237,12,8,99,104,105,108,100,114,101,110,1,119,6,113,49,74,97,114,53,40,0,195,133,217,18,237,12,4,100,97,116,97,1,119,12,123,34,100,101,108,116,97,34,58,91,93,125,40,0,195,133,217,18,237,12,11,101,120,116,101,114,110,97,108,95,105,100,1,126,40,0,195,133,217,18,237,12,13,101,120,116,101,114,110,97,108,95,116,121,112,101,1,126,39,0,147,128,159,145,14,3,6,113,49,74,97,114,53,0,136,195,133,217,18,234,12,1,119,6,109,99,66,107,98,115,39,0,147,128,159,145,14,4,6,66,87,74,50,81,114,2,4,0,195,133,217,18,247,12,2,49,50,168,195,133,217,18,169,12,1,119,27,123,34,100,101,108,116,97,34,58,91,123,34,105,110,115,101,114,116,34,58,34,49,50,34,125,93,125,39,0,147,128,159,145,14,4,6,116,83,114,83,65,45,2,33,0,147,128,159,145,14,1,6,118,71,79,68,118,57,1,0,7,33,0,147,128,159,145,14,3,6,66,122,109,101,85,67,1,129,195,133,217,18,173,12,1,39,0,147,128,159,145,14,4,6,56,82,84,70,84,106,2,33,0,147,128,159,145,14,1,6,100,48,101,105,48,99,1,0,7,33,0,147,128,159,145,14,3,6,55,103,88,67,83,76,1,193,195,133,217,18,251,11,195,133,217,18,183,12,1,39,0,147,128,159,145,14,4,6,86,49,69,99,77,120,2,33,0,147,128,159,145,14,1,6,57,82,76,118,75,83,1,0,7,33,0,147,128,159,145,14,3,6,100,84,102,75,114,54,1,129,195,133,217,18,133,13,1,39,0,147,128,159,145,14,4,6,95,90,85,102,102,106,2,33,0,147,128,159,145,14,1,6,78,102,79,67,79,76,1,0,7,33,0,147,128,159,145,14,3,6,66,52,111,107,80,118,1,193,195,133,217,18,144,13,195,133,217,18,183,12,1,5,200,244,136,224,7,1,0,3,178,246,186,209,6,1,0,72,147,128,159,145,14,1,11,3,195,133,217,18,17,11,1,21,10,37,1,44,1,51,1,60,1,62,10,79,1,88,1,90,10,169,12,1,204,12,10,219,12,1,252,12,10,135,13,10,146,13,10,157,13,10,156,139,194,87,1,0,3],"version":0},"code":0,"message":"Operation completed successfully."} \ No newline at end of file diff --git a/frontend/appflowy_web_app/cypress/support/commands.ts b/frontend/appflowy_web_app/cypress/support/commands.ts deleted file mode 100644 index 1b5199b01a..0000000000 --- a/frontend/appflowy_web_app/cypress/support/commands.ts +++ /dev/null @@ -1,33 +0,0 @@ -/// -// *********************************************** -// This example commands.ts shows you how to -// create various custom commands and overwrite -// existing commands. -// -// For more comprehensive examples of custom -// commands please read more here: -// https://on.cypress.io/custom-commands -// *********************************************** -// -// -// -- This is a parent command -- -// Cypress.Commands.add('login', (email, password) => { ... }) -// -// -// -- This is a child command -- -// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) -// -// -// -- This is a dual command -- -// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) -// -// -// -- This will overwrite an existing command -- -// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) -// - -Cypress.Commands.add('mockAPI', () => { - // Mock the API -}); - -export {}; diff --git a/frontend/appflowy_web_app/cypress/support/component-index.html b/frontend/appflowy_web_app/cypress/support/component-index.html deleted file mode 100644 index 1633d91f21..0000000000 --- a/frontend/appflowy_web_app/cypress/support/component-index.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - Components App - - - - - -
- - \ No newline at end of file diff --git a/frontend/appflowy_web_app/cypress/support/component.ts b/frontend/appflowy_web_app/cypress/support/component.ts deleted file mode 100644 index f60970e7ed..0000000000 --- a/frontend/appflowy_web_app/cypress/support/component.ts +++ /dev/null @@ -1,189 +0,0 @@ -// *********************************************************** -// This example support/component.ts is processed and -// loaded automatically before your test files. -// -// This is a great place to put global configuration and -// behavior that modifies Cypress. -// -// You can change the location of this file or turn off -// automatically serving support files with the -// 'supportFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/configuration -// *********************************************************** -import { addMatchImageSnapshotCommand } from 'cypress-image-snapshot/command'; -import 'cypress-real-events'; - -// Import commands.js using ES2015 syntax: -import '@cypress/code-coverage/support'; -import 'cypress-real-events/support'; -import './commands'; -import './document'; - -// Alternatively you can use CommonJS syntax: -// require('./commands') - -import { mount } from 'cypress/react18'; - -// Augment the Cypress namespace to include type definitions for -// your custom command. -// Alternatively, can be defined in cypress/support/component.d.ts -// with a at the top of your spec. -declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace Cypress { - interface Chainable { - mount: typeof mount; - mockAPI: () => void; - mockDatabase: () => void; - mockCurrentWorkspace: () => void; - mockGetWorkspaceDatabases: () => void; - mockDocument: (id: string) => void; - clickOutside: () => void; - getTestingSelector: (testId: string) => Chainable>; - selectText: (text: string) => void; - - selectMultipleText: (texts: string[]) => void; - } - } -} - -Cypress.Commands.add('mount', mount); - -Cypress.Commands.add('getTestingSelector', (testId: string) => { - return cy.get(`[data-testid="${testId}"]`); -}); - -Cypress.Commands.add('clickOutside', () => { - cy.document().then((doc) => { - // [0, 0] is the top left corner of the window - const x = 0; - const y = 0; - - const evt = new MouseEvent('click', { - bubbles: true, - cancelable: true, - view: window, - clientX: x, - clientY: y, - }); - - // Dispatch the event - doc.elementFromPoint(x, y)?.dispatchEvent(evt); - }); -}); - -function mergeRanges (ranges: Range[]): Range | null { - if (ranges.length === 0) return null; - - const mergedRange = ranges[0].cloneRange(); - - for (let i = 1; i < ranges.length; i++) { - if (ranges[i].compareBoundaryPoints(Range.START_TO_START, mergedRange) < 0) { - mergedRange.setStart(ranges[i].startContainer, ranges[i].startOffset); - } - - if (ranges[i].compareBoundaryPoints(Range.END_TO_END, mergedRange) > 0) { - mergedRange.setEnd(ranges[i].endContainer, ranges[i].endOffset); - } - } - - return mergedRange; -} - -Cypress.Commands.add('selectMultipleText', (texts: string[]) => { - const ranges: Range[] = []; - - cy.window().then((win) => { - const promises = texts.map((text) => { - return new Cypress.Promise((resolve) => { - cy.contains(text).then(($el) => { - if (!$el) { - throw new Error(`The text "${text}" was not found in the document`); - } - - const el = $el[0] as HTMLElement; - const document = el.ownerDocument; - const range = document.createRange(); - - const fullText = el.textContent || ''; - const startIndex = fullText.indexOf(text); - const endIndex = startIndex + text.length; - - if (startIndex !== -1 && endIndex !== -1) { - range.setStart(el.firstChild as Node, startIndex); - range.setEnd(el.firstChild as Node, endIndex); - ranges.push(range); - } else { - throw new Error(`The text "${text}" was not found in the element`); - } - - resolve(); - }); - }); - }); - - void Cypress.Promise.all(promises).then(() => { - const selection = win.getSelection(); - - if (selection) { - const mergedRange = mergeRanges(ranges); - - selection.removeAllRanges(); - if (mergedRange) { - selection.addRange(mergedRange); - - } - } - - cy.document().trigger('mouseup'); - cy.document().trigger('selectionchange'); - }); - }); -}); -Cypress.Commands.add('selectText', (text: string) => { - cy.contains(text).then(($el) => { - if (!$el) { - throw new Error(`The text "${text}" was not found in the document`); - } - - const el = $el[0] as HTMLElement; - const document = el.ownerDocument; - - const range = document.createRange(); - - range.selectNodeContents(el); - - const fullText = el.textContent || ''; - const startIndex = fullText.indexOf(text); - const endIndex = startIndex + text.length; - - if (startIndex !== -1 && endIndex !== -1) { - range.setStart(el.firstChild as HTMLElement, startIndex); - range.setEnd(el.firstChild as HTMLElement, endIndex); - - const selection = document.getSelection() as Selection; - - selection.removeAllRanges(); - selection.addRange(range); - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - $el.trigger('mouseup'); - cy.document().trigger('selectionchange'); - } else { - throw new Error(`The text "${text}" was not found in the element`); - } - }); -}); - -// Example use: -// cy.mount() - -addMatchImageSnapshotCommand({ - failureThreshold: 0.03, // 允许 3% 的像素差异 - failureThresholdType: 'percent', - customDiffConfig: { threshold: 0.1 }, - capture: 'viewport', -}); diff --git a/frontend/appflowy_web_app/cypress/support/document.ts b/frontend/appflowy_web_app/cypress/support/document.ts deleted file mode 100644 index b7dfe0900a..0000000000 --- a/frontend/appflowy_web_app/cypress/support/document.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { BlockId, BlockType, YBlocks, YChildrenMap, YjsEditorKey, YTextMap } from '@/application/types'; -import { nanoid } from 'nanoid'; -import { Op } from 'quill-delta'; -import * as Y from 'yjs'; - -export interface FromBlockJSON { - type: string; - children: FromBlockJSON[]; - data: Record; - text: Op[]; -} - -export class DocumentTest { - public doc: Y.Doc; - - private blocks: YBlocks; - - private childrenMap: YChildrenMap; - - private textMap: YTextMap; - - private pageId: string; - - constructor () { - const doc = new Y.Doc(); - - this.doc = doc; - const collab = doc.getMap(YjsEditorKey.data_section); - const document = new Y.Map(); - const blocks = new Y.Map() as YBlocks; - const pageId = nanoid(8); - const meta = new Y.Map(); - const childrenMap = new Y.Map() as YChildrenMap; - const textMap = new Y.Map() as YTextMap; - - const block = new Y.Map(); - - block.set(YjsEditorKey.block_id, pageId); - block.set(YjsEditorKey.block_type, BlockType.Page); - block.set(YjsEditorKey.block_children, pageId); - block.set(YjsEditorKey.block_external_id, pageId); - block.set(YjsEditorKey.block_external_type, YjsEditorKey.text); - block.set(YjsEditorKey.block_data, ''); - blocks.set(pageId, block); - - document.set(YjsEditorKey.page_id, pageId); - document.set(YjsEditorKey.blocks, blocks); - document.set(YjsEditorKey.meta, meta); - meta.set(YjsEditorKey.children_map, childrenMap); - meta.set(YjsEditorKey.text_map, textMap); - collab.set(YjsEditorKey.document, document); - - this.blocks = blocks; - this.childrenMap = childrenMap; - this.textMap = textMap; - this.pageId = pageId; - } - - insertParagraph (text: string) { - const blockId = nanoid(8); - const block = new Y.Map(); - - block.set(YjsEditorKey.block_id, blockId); - block.set(YjsEditorKey.block_type, BlockType.Paragraph); - block.set(YjsEditorKey.block_children, blockId); - block.set(YjsEditorKey.block_external_id, blockId); - block.set(YjsEditorKey.block_external_type, YjsEditorKey.text); - block.set(YjsEditorKey.block_parent, this.pageId); - block.set(YjsEditorKey.block_data, ''); - this.blocks.set(blockId, block); - const pageChildren = this.childrenMap.get(this.pageId) ?? new Y.Array(); - - pageChildren.push([blockId]); - this.childrenMap.set(this.pageId, pageChildren); - - const blockText = new Y.Text(); - - blockText.insert(0, text); - this.textMap.set(blockId, blockText); - - return blockText; - } - - fromJSON (json: FromBlockJSON[]) { - - this.fromJSONChildren(json, this.pageId); - - return this.doc; - } - - private fromJSONChildren (children: FromBlockJSON[], parentId: BlockId) { - const parentChildren = this.childrenMap.get(parentId) ?? new Y.Array(); - - for (const child of children) { - const blockId = nanoid(8); - const block = new Y.Map(); - - block.set(YjsEditorKey.block_id, blockId); - block.set(YjsEditorKey.block_type, child.type); - block.set(YjsEditorKey.block_children, blockId); - block.set(YjsEditorKey.block_external_id, blockId); - block.set(YjsEditorKey.block_external_type, YjsEditorKey.text); - block.set(YjsEditorKey.block_parent, parentId); - block.set(YjsEditorKey.block_data, JSON.stringify(child.data)); - this.blocks.set(blockId, block); - - parentChildren.push([blockId]); - if (!this.childrenMap.has(parentId)) { - this.childrenMap.set(parentId, parentChildren); - } - - const blockText = new Y.Text(); - - blockText.applyDelta(child.text); - - this.textMap.set(blockId, blockText); - - const blockChildren = new Y.Array(); - - this.childrenMap.set(blockId, blockChildren); - - this.fromJSONChildren(child.children, blockId); - } - } - - toJSON () { - return this.toJSONChildren(this.pageId); - } - - private toJSONChildren (parentId: BlockId): FromBlockJSON[] { - const parentChildren = this.childrenMap.get(parentId) ?? []; - const children = []; - - for (const childId of parentChildren) { - const child = this.blocks.get(childId); - - children.push({ - type: child.get(YjsEditorKey.block_type), - data: JSON.parse(child.get(YjsEditorKey.block_data)), - text: this.textMap.get(childId)?.toDelta() || [], - children: this.toJSONChildren(childId), - }); - } - - return children; - } -} diff --git a/frontend/appflowy_web_app/deploy/Dockerfile b/frontend/appflowy_web_app/deploy/Dockerfile deleted file mode 100644 index 85f9d33b28..0000000000 --- a/frontend/appflowy_web_app/deploy/Dockerfile +++ /dev/null @@ -1,32 +0,0 @@ -FROM oven/bun:latest - -WORKDIR /app - -RUN apt-get update && \ - apt-get install -y nginx supervisor - -RUN bun install cheerio pino pino-pretty - -COPY . . - -COPY supervisord.conf /app/supervisord.conf - -RUN addgroup --system nginx && \ - adduser --system --no-create-home --disabled-login --ingroup nginx nginx - -RUN apt-get clean && rm -rf /var/lib/apt/lists/* - - -COPY dist /usr/share/nginx/html/ - -COPY nginx.conf /etc/nginx/nginx.conf - -COPY start.sh /app/start.sh - -RUN chmod +x /app/start.sh -RUN chmod +x /app/supervisord.conf - - -EXPOSE 80 - -CMD ["supervisord", "-c", "/app/supervisord.conf"] \ No newline at end of file diff --git a/frontend/appflowy_web_app/deploy/deploy.sh b/frontend/appflowy_web_app/deploy/deploy.sh deleted file mode 100644 index 772862c246..0000000000 --- a/frontend/appflowy_web_app/deploy/deploy.sh +++ /dev/null @@ -1,28 +0,0 @@ -if [ -z "$1" ]; then - echo "No port number provided" - exit 1 -fi - -PORT=$1 - -echo "Starting deployment on port $PORT" - -rm -rf deploy - -tar -xzf build-output.tar.gz - -rm -rf build-output.tar.gz - -mv dist deploy/dist - -mv .env deploy/.env - -cd deploy - -docker system prune -f - -docker build -t appflowy-web-app-"$PORT" . - -docker rm -f appflowy-web-app-"$PORT" || true - -docker run -d --env-file .env -p "$PORT":80 --restart always --name appflowy-web-app-"$PORT" appflowy-web-app-"$PORT" \ No newline at end of file diff --git a/frontend/appflowy_web_app/deploy/nginx.conf b/frontend/appflowy_web_app/deploy/nginx.conf deleted file mode 100644 index 311a118f9c..0000000000 --- a/frontend/appflowy_web_app/deploy/nginx.conf +++ /dev/null @@ -1,111 +0,0 @@ -# nginx.conf -user nginx; -worker_processes auto; - -error_log /var/log/nginx/error.log notice; -pid /var/run/nginx.pid; - -events { - worker_connections 1024; -} - -http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for"'; - - access_log /var/log/nginx/access.log main; - - sendfile on; - #tcp_nopush on; - - keepalive_timeout 65; - - gzip on; - - gzip_static on; - - gzip_http_version 1.0; - - gzip_comp_level 5; - - gzip_vary on; - - gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript application/wasm; - - # Existing server block for HTTP - server { - listen 80; - server_name localhost; - - location / { - proxy_pass http://localhost:3000; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection 'upgrade'; - proxy_set_header Host $host; - proxy_cache_bypass $http_upgrade; - } - - location /static/ { - root /usr/share/nginx/html; - expires 30d; - access_log off; - - } - - location /appflowy.svg { - root /usr/share/nginx/html; - expires 30d; - access_log off; - } - - location /appflowy.ico { - root /usr/share/nginx/html; - expires 30d; - access_log off; - } - - location /og-image.png { - root /usr/share/nginx/html; - expires 30d; - access_log off; - } - - location /covers/ { - root /usr/share/nginx/html; - expires 30d; - access_log off; - } - - location /af_icons/ { - root /usr/share/nginx/html; - expires 30d; - access_log off; - } - - location /.well-known/apple-app-site-association { - default_type application/json; - add_header Cache-Control "public, max-age=3600"; - } - - location /.well-known/assetlinks.json { - default_type application/json; - add_header Cache-Control "public, max-age=3600"; - } - - error_page 404 /404.html; - location = /404.html { - root /usr/share/nginx/html; - } - - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root /usr/share/nginx/html; - } - - } -} \ No newline at end of file diff --git a/frontend/appflowy_web_app/deploy/server.ts b/frontend/appflowy_web_app/deploy/server.ts deleted file mode 100644 index db79e5c1fb..0000000000 --- a/frontend/appflowy_web_app/deploy/server.ts +++ /dev/null @@ -1,255 +0,0 @@ -import path from 'path'; -import * as fs from 'fs'; -import pino from 'pino'; -import { type CheerioAPI, load } from 'cheerio'; -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-expect-error -import { fetch } from 'bun'; - -const distDir = path.join(__dirname, 'dist'); -const indexPath = path.join(distDir, 'index.html'); -const baseURL = process.env.AF_BASE_URL as string; -const defaultSite = 'https://appflowy.io'; - -const setOrUpdateMetaTag = ($: CheerioAPI, selector: string, attribute: string, content: string) => { - if ($(selector).length === 0) { - $('head').append(``); - } else { - $(selector).attr('content', content); - } -}; - -const logger = pino({ - transport: { - target: 'pino-pretty', - options: { - colorize: true, - translateTime: 'SYS:standard', - destination: `${__dirname}/pino-logger.log`, - }, - }, - level: 'info', -}); - -const logRequestTimer = (req: Request) => { - const start = Date.now(); - const pathname = new URL(req.url).pathname; - - logger.info(`Incoming request: ${pathname}`); - return () => { - const duration = Date.now() - start; - - logger.info(`Request for ${pathname} took ${duration}ms`); - }; -}; - -const fetchMetaData = async (namespace: string, publishName?: string) => { - let url = `${baseURL}/api/workspace/published/${namespace}`; - - if (publishName) { - url += `/${publishName}`; - } - - logger.info(`Fetching meta data from ${url}`); - try { - const response = await fetch(url, { - verbose: true, - }); - - if (!response.ok) { - throw new Error(`HTTP error! Status: ${response.status}`); - } - - return response.json(); - } catch (error) { - logger.error(`Error fetching meta data ${error}`); - return null; - } -}; - -const createServer = async (req: Request) => { - const timer = logRequestTimer(req); - const reqUrl = new URL(req.url); - const hostname = req.headers.get('host'); - - logger.info(`Request URL: ${hostname}${reqUrl.pathname}`); - - if (reqUrl.pathname === '/') { - timer(); - return new Response(null, { - status: 302, - headers: { - Location: '/app', - }, - }); - } - - if (['/after-payment', '/login', '/as-template', '/app', '/accept-invitation', '/import'].some(item => reqUrl.pathname.startsWith(item))) { - timer(); - const htmlData = fs.readFileSync(indexPath, 'utf8'); - const $ = load(htmlData); - - let title, description; - - if (reqUrl.pathname === '/after-payment') { - title = 'Payment Success | AppFlowy'; - description = 'Payment success on AppFlowy'; - } - - if (reqUrl.pathname === '/login') { - title = 'Login | AppFlowy'; - description = 'Login to AppFlowy'; - } - - if (title) $('title').text(title); - if (description) setOrUpdateMetaTag($, 'meta[name="description"]', 'name', description); - - return new Response($.html(), { - headers: { 'Content-Type': 'text/html' }, - }); - } - - const [namespace, publishName] = reqUrl.pathname.slice(1).split('/'); - - logger.info(`Namespace: ${namespace}, Publish Name: ${publishName}`); - - if (req.method === 'GET') { - if (namespace === '') { - timer(); - return new Response(null, { - status: 302, - headers: { - Location: defaultSite, - }, - }); - } - - let metaData; - - try { - const data = await fetchMetaData(namespace, publishName); - - if (publishName) { - metaData = data; - } else { - - const publishInfo = data?.data?.info; - - if (publishInfo) { - const newURL = `/${encodeURIComponent(publishInfo.namespace)}/${encodeURIComponent(publishInfo.publish_name)}`; - - logger.info(`Redirecting to default page in: ${JSON.stringify(publishInfo)}`); - timer(); - return new Response(null, { - status: 302, - headers: { - Location: newURL, - }, - }); - } - } - } catch (error) { - logger.error(`Error fetching meta data: ${error}`); - } - - const htmlData = fs.readFileSync(indexPath, 'utf8'); - const $ = load(htmlData); - - const description = 'Write, share, and publish docs quickly on AppFlowy.\nGet started for free.'; - let title = 'AppFlowy'; - const url = `https://${hostname}${reqUrl.pathname}`; - let image = '/og-image.png'; - let favicon = '/appflowy.ico'; - - try { - if (metaData && metaData.view) { - const view = metaData.view; - const emoji = view.icon?.ty === 0 && view.icon?.value; - const titleList = []; - - if (emoji) { - const emojiCode = emoji.codePointAt(0).toString(16); // Convert emoji to hex code - const baseUrl = 'https://raw.githubusercontent.com/googlefonts/noto-emoji/main/svg/emoji_u'; - - favicon = `${baseUrl}${emojiCode}.svg`; - } - - if (view.name) { - titleList.push(view.name); - titleList.push('|'); - } - - titleList.push('AppFlowy'); - title = titleList.join(' '); - - try { - const cover = view.extra ? JSON.parse(view.extra)?.cover : null; - - if (cover) { - if (['unsplash', 'custom'].includes(cover.type)) { - image = cover.value; - } else if (cover.type === 'built_in') { - image = `/covers/m_cover_image_${cover.value}.png`; - } - } - } catch (_) { - // Do nothing - } - } - } catch (error) { - logger.error(`Error injecting meta data: ${error}`); - } - - $('title').text(title); - $('link[rel="icon"]').attr('href', favicon); - $('link[rel="canonical"]').attr('href', url); - setOrUpdateMetaTag($, 'meta[name="description"]', 'name', description); - setOrUpdateMetaTag($, 'meta[property="og:title"]', 'property', title); - setOrUpdateMetaTag($, 'meta[property="og:description"]', 'property', description); - setOrUpdateMetaTag($, 'meta[property="og:image"]', 'property', image); - setOrUpdateMetaTag($, 'meta[property="og:url"]', 'property', url); - setOrUpdateMetaTag($, 'meta[property="og:site_name"]', 'property', 'AppFlowy'); - setOrUpdateMetaTag($, 'meta[property="og:type"]', 'property', 'website'); - setOrUpdateMetaTag($, 'meta[name="twitter:card"]', 'name', 'summary_large_image'); - setOrUpdateMetaTag($, 'meta[name="twitter:title"]', 'name', title); - setOrUpdateMetaTag($, 'meta[name="twitter:description"]', 'name', description); - setOrUpdateMetaTag($, 'meta[name="twitter:image"]', 'name', image); - setOrUpdateMetaTag($, 'meta[name="twitter:site"]', 'name', '@appflowy'); - - timer(); - return new Response($.html(), { - headers: { 'Content-Type': 'text/html' }, - }); - } else { - timer(); - logger.error({ message: 'Method not allowed', method: req.method }); - return new Response('Method not allowed', { status: 405 }); - } -}; - -declare const Bun: { - serve: (options: { port: number; fetch: typeof createServer; error: (err: Error) => Response }) => void; -}; - -const start = () => { - try { - // eslint-disable-next-line @typescript-eslint/no-var-requires - Bun.serve({ - port: 3000, - fetch: createServer, - error: (err) => { - logger.error(`Internal Server Error: ${err}`); - return new Response('Internal Server Error', { status: 500 }); - }, - }); - logger.info('Server is running on port 3000'); - logger.info(`Base URL: ${baseURL}`); - } catch (err) { - logger.error(err); - process.exit(1); - } -}; - -start(); - -export {}; diff --git a/frontend/appflowy_web_app/deploy/start.sh b/frontend/appflowy_web_app/deploy/start.sh deleted file mode 100644 index eba0f53018..0000000000 --- a/frontend/appflowy_web_app/deploy/start.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - - - -# Start the nginx server -service nginx start - -# Start the frontend server -bun run server.ts - -tail -f /dev/null - diff --git a/frontend/appflowy_web_app/deploy/supervisord.conf b/frontend/appflowy_web_app/deploy/supervisord.conf deleted file mode 100644 index 1484fd39e5..0000000000 --- a/frontend/appflowy_web_app/deploy/supervisord.conf +++ /dev/null @@ -1,9 +0,0 @@ -[supervisord] -nodaemon=true - -[program:bun] -command=sh /app/start.sh -autostart=true -autorestart=true -stderr_logfile=/var/log/bun.err.log -stdout_logfile=/var/log/bun.out.log diff --git a/frontend/appflowy_web_app/index.html b/frontend/appflowy_web_app/index.html deleted file mode 100644 index cbb2c55e60..0000000000 --- a/frontend/appflowy_web_app/index.html +++ /dev/null @@ -1,115 +0,0 @@ - - - - - - - - AppFlowy - - - - - - - - - - - - - - <%- cdnLinks %> - - - -
- - - - - - - diff --git a/frontend/appflowy_web_app/jest.config.cjs b/frontend/appflowy_web_app/jest.config.cjs deleted file mode 100644 index 211176e5ee..0000000000 --- a/frontend/appflowy_web_app/jest.config.cjs +++ /dev/null @@ -1,42 +0,0 @@ -const { compilerOptions } = require('./tsconfig.json'); -const { pathsToModuleNameMapper } = require('ts-jest'); -const esModules = ['lodash-es', 'nanoid'].join('|'); - -/** @type {import('ts-jest').JestConfigWithTsJest} */ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'jsdom', - roots: [''], - modulePaths: [compilerOptions.baseUrl], - moduleNameMapper: { - ...pathsToModuleNameMapper(compilerOptions.paths), - '^lodash-es(/(.*)|$)': 'lodash$1', - '^nanoid(/(.*)|$)': 'nanoid$1', - '^dayjs$': '/node_modules/dayjs/dayjs.min.js', - }, - 'transform': { - '^.+\\.(j|t)sx?$': 'ts-jest', - '(.*)/node_modules/nanoid/.+\\.(j|t)sx?$': 'ts-jest', - }, - 'transformIgnorePatterns': [`/node_modules/(?!${esModules})`], - testMatch: ['**/*.test.ts', '**/*.test.tsx'], - coverageDirectory: '/coverage/jest', - collectCoverage: true, - coverageProvider: 'v8', - coveragePathIgnorePatterns: [ - '/cypress/', - '/coverage/', - '/node_modules/', - '/__tests__/', - '/__mocks__/', - '/__fixtures__/', - '/__helpers__/', - '/__utils__/', - '/__constants__/', - '/__types__/', - '/__mocks__/', - '/__stubs__/', - '/__fixtures__/', - '/application/folder-yjs/', - ], -}; \ No newline at end of file diff --git a/frontend/appflowy_web_app/package.json b/frontend/appflowy_web_app/package.json deleted file mode 100644 index 0849f37454..0000000000 --- a/frontend/appflowy_web_app/package.json +++ /dev/null @@ -1,197 +0,0 @@ -{ - "name": "appflowy_web_app", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "pnpm run sync:i18n && vite", - "dev:tauri": "pnpm run sync:i18n && vite", - "build": "pnpm run sync:i18n && vite build", - "build:tauri": "vite build", - "lint:tauri": "pnpm run sync:i18n && tsc --noEmit && eslint --ext .js,.ts,.tsx . --ignore-path .eslintignore", - "lint": "pnpm run sync:i18n && tsc --noEmit --project tsconfig.web.json && eslint --ext .js,.ts,.tsx . --ignore-path .eslintignore.web", - "start": "vite preview --port 3000", - "tauri:dev": "tauri dev", - "css:variables": "node scripts/generateTailwindColors.cjs", - "sync:i18n": "node scripts/i18n.cjs", - "link:client-api": "rm -rf node_modules/.vite && node scripts/create-symlink.cjs", - "analyze": "cross-env ANALYZE_MODE=true vite build", - "cypress:open": "cypress open", - "test": "pnpm run test:unit && pnpm run test:components", - "test:components": "cypress run --component --browser chrome --headless", - "test:unit": "jest --coverage", - "test:cy": "cypress run", - "coverage": "pnpm run test:unit && pnpm run test:components" - }, - "dependencies": { - "@appflowyinc/editor": "^0.0.39", - "@atlaskit/primitives": "^5.5.3", - "@emoji-mart/data": "^1.1.2", - "@emoji-mart/react": "^1.1.1", - "@emotion/react": "^11.10.6", - "@emotion/styled": "^11.10.6", - "@floating-ui/react": "^0.26.27", - "@jest/globals": "^29.7.0", - "@mui/icons-material": "^5.11.11", - "@mui/material": "6.0.0-alpha.2", - "@mui/x-date-pickers-pro": "^6.18.2", - "@reduxjs/toolkit": "2.0.0", - "@slate-yjs/core": "^1.0.2", - "@tauri-apps/api": "^1.5.3", - "@types/react-swipeable-views": "^0.13.4", - "async-retry": "^1.3.3", - "axios": "^1.6.8", - "colorthief": "^2.4.0", - "dayjs": "^1.11.9", - "decimal.js": "^10.4.3", - "dexie": "^4.0.7", - "dexie-react-hooks": "^1.1.7", - "dompurify": "^3.1.7", - "emoji-mart": "^5.5.2", - "emoji-regex": "^10.2.1", - "escape-string-regexp": "^5.0.0", - "events": "^3.3.0", - "google-protobuf": "^3.15.12", - "hast-util-to-mdast": "^10.1.0", - "highlight.js": "^11.10.0", - "html-parse-stringify": "^3.0.1", - "i18next": "^22.4.10", - "i18next-browser-languagedetector": "^7.0.1", - "i18next-resources-to-backend": "^1.1.4", - "is-hotkey": "^0.2.0", - "jest": "^29.5.0", - "js-base64": "^3.7.5", - "js-md5": "^0.8.3", - "katex": "^0.16.7", - "lightgallery": "^2.7.2", - "lodash-es": "^4.17.21", - "mermaid": "^11.4.1", - "nanoid": "^4.0.0", - "notistack": "^3.0.1", - "numeral": "^2.0.6", - "prismjs": "^1.29.0", - "protoc-gen-ts": "0.8.7", - "quill": "^1.3.7", - "quill-delta": "^5.1.0", - "react": "^18.2.0", - "react-beautiful-dnd": "^13.1.1", - "react-big-calendar": "^1.8.5", - "react-color": "^2.19.3", - "react-custom-scrollbars": "^4.2.1", - "react-custom-scrollbars-2": "^4.5.0", - "react-datepicker": "^4.23.0", - "react-dom": "^18.2.0", - "react-error-boundary": "^4.0.13", - "react-helmet": "^6.1.0", - "react-hook-form": "^7.52.2", - "react-hot-toast": "^2.4.1", - "react-i18next": "^14.1.0", - "react-katex": "^3.0.1", - "react-measure": "^2.5.2", - "react-redux": "^8.0.5", - "react-router-dom": "^6.22.3", - "react-swipeable-views": "^0.14.0", - "react-transition-group": "^4.4.5", - "react-virtualized-auto-sizer": "^1.0.20", - "react-vtree": "^2.0.4", - "react-window": "^1.8.10", - "react-zoom-pan-pinch": "^3.6.1", - "react18-input-otp": "^1.1.2", - "redux": "^4.2.1", - "rehype-parse": "^9.0.1", - "rxjs": "^7.8.0", - "sass": "^1.70.0", - "slate": "^0.101.4", - "slate-history": "^0.100.0", - "slate-react": "^0.101.3", - "smooth-scroll-into-view-if-needed": "^2.0.2", - "ts-results": "^3.3.0", - "unified": "^11.0.5", - "unist": "^0.0.1", - "unsplash-js": "^7.0.19", - "utf8": "^3.0.0", - "validator": "^13.11.0", - "vite-plugin-wasm": "^3.3.0", - "y-indexeddb": "9.0.12", - "yjs": "14.0.0-1" - }, - "devDependencies": { - "@babel/preset-env": "^7.24.7", - "@babel/preset-react": "^7.24.7", - "@babel/preset-typescript": "^7.24.7", - "@cypress/code-coverage": "^3.12.39", - "@istanbuljs/nyc-config-babel": "^3.0.0", - "@istanbuljs/nyc-config-typescript": "^1.0.2", - "@svgr/plugin-svgo": "^8.0.1", - "@tauri-apps/cli": "^1.5.11", - "@testing-library/react": "^16.0.0", - "@types/cypress-image-snapshot": "^3.1.9", - "@types/dompurify": "^3.0.5", - "@types/google-protobuf": "^3.15.12", - "@types/is-hotkey": "^0.1.7", - "@types/jest": "^29.5.3", - "@types/katex": "^0.16.0", - "@types/lodash-es": "^4.17.11", - "@types/node": "^20.11.30", - "@types/numeral": "^2.0.5", - "@types/prismjs": "^1.26.0", - "@types/quill": "^2.0.10", - "@types/react": "^18.2.66", - "@types/react-beautiful-dnd": "^13.1.3", - "@types/react-big-calendar": "^1.8.9", - "@types/react-color": "^3.0.6", - "@types/react-custom-scrollbars": "^4.0.13", - "@types/react-datepicker": "^4.19.3", - "@types/react-dom": "^18.2.22", - "@types/react-helmet": "^6.1.11", - "@types/react-katex": "^3.0.0", - "@types/react-measure": "^2.0.12", - "@types/react-transition-group": "^4.4.6", - "@types/react-window": "^1.8.8", - "@types/utf8": "^3.0.1", - "@types/uuid": "^9.0.1", - "@types/validator": "^13.11.9", - "@typescript-eslint/eslint-plugin": "^7.2.0", - "@typescript-eslint/parser": "^7.2.0", - "@vitejs/plugin-react": "^4.2.1", - "autoprefixer": "^10.4.13", - "axios-mock-adapter": "^2.0.0", - "babel-jest": "^29.6.2", - "chalk": "^4.1.2", - "cheerio": "1.0.0-rc.12", - "cross-env": "^7.0.3", - "cypress": "^13.7.2", - "cypress-image-snapshot": "^4.0.1", - "cypress-real-events": "^1.13.0", - "eslint": "^8.57.0", - "eslint-plugin-react": "^7.32.2", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-refresh": "^0.4.6", - "istanbul-lib-coverage": "^3.2.2", - "jest-environment-jsdom": "^29.6.2", - "jest-node-exports-resolver": "^1.1.6", - "nyc": "^15.1.0", - "pino": "^9.2.0", - "pino-pretty": "^11.2.1", - "postcss": "^8.4.21", - "prettier": "2.8.4", - "prettier-plugin-tailwindcss": "^0.2.2", - "rollup-plugin-visualizer": "^5.12.0", - "style-dictionary": "^3.9.2", - "tailwindcss": "^3.2.7", - "ts-jest": "^29.1.1", - "ts-node-dev": "^2.0.0", - "tsconfig-paths-jest": "^0.0.1", - "typescript": "4.9.5", - "uuid": "^9.0.0", - "vite": "^5.2.0", - "vite-plugin-compression2": "^1.0.0", - "vite-plugin-externals": "^0.6.2", - "vite-plugin-html": "^3.2.2", - "vite-plugin-importer": "^0.2.5", - "vite-plugin-istanbul": "^6.0.2", - "vite-plugin-svgr": "^3.2.0", - "vite-plugin-terminal": "^1.2.0", - "vite-plugin-total-bundle-size": "^1.0.7" - } -} diff --git a/frontend/appflowy_web_app/pnpm-lock.yaml b/frontend/appflowy_web_app/pnpm-lock.yaml deleted file mode 100644 index 9211639bfa..0000000000 --- a/frontend/appflowy_web_app/pnpm-lock.yaml +++ /dev/null @@ -1,14847 +0,0 @@ -lockfileVersion: '6.0' - -dependencies: - '@appflowyinc/editor': - specifier: ^0.0.39 - version: 0.0.39(@types/react-dom@18.2.22)(@types/react@18.2.66)(i18next-resources-to-backend@1.2.1)(i18next@22.5.1)(react-dom@18.2.0)(react-i18next@14.1.2)(react@18.2.0)(slate-history@0.100.0)(slate-react@0.101.6)(slate@0.101.5) - '@atlaskit/primitives': - specifier: ^5.5.3 - version: 5.7.0(@types/react@18.2.66)(react@18.2.0) - '@emoji-mart/data': - specifier: ^1.1.2 - version: 1.2.1 - '@emoji-mart/react': - specifier: ^1.1.1 - version: 1.1.1(emoji-mart@5.6.0)(react@18.2.0) - '@emotion/react': - specifier: ^11.10.6 - version: 11.11.4(@types/react@18.2.66)(react@18.2.0) - '@emotion/styled': - specifier: ^11.10.6 - version: 11.11.5(@emotion/react@11.11.4)(@types/react@18.2.66)(react@18.2.0) - '@floating-ui/react': - specifier: ^0.26.27 - version: 0.26.27(react-dom@18.2.0)(react@18.2.0) - '@jest/globals': - specifier: ^29.7.0 - version: 29.7.0 - '@mui/icons-material': - specifier: ^5.11.11 - version: 5.15.18(@mui/material@6.0.0-alpha.2)(@types/react@18.2.66)(react@18.2.0) - '@mui/material': - specifier: 6.0.0-alpha.2 - version: 6.0.0-alpha.2(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@mui/x-date-pickers-pro': - specifier: ^6.18.2 - version: 6.20.0(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@mui/material@6.0.0-alpha.2)(@mui/system@5.15.15)(@types/react@18.2.66)(dayjs@1.11.9)(react-dom@18.2.0)(react@18.2.0) - '@reduxjs/toolkit': - specifier: 2.0.0 - version: 2.0.0(react-redux@8.1.3)(react@18.2.0) - '@slate-yjs/core': - specifier: ^1.0.2 - version: 1.0.2(slate@0.101.5)(yjs@14.0.0-1) - '@tauri-apps/api': - specifier: ^1.5.3 - version: 1.5.6 - '@types/react-swipeable-views': - specifier: ^0.13.4 - version: 0.13.5 - async-retry: - specifier: ^1.3.3 - version: 1.3.3 - axios: - specifier: ^1.6.8 - version: 1.7.2 - colorthief: - specifier: ^2.4.0 - version: 2.4.0 - dayjs: - specifier: ^1.11.9 - version: 1.11.9 - decimal.js: - specifier: ^10.4.3 - version: 10.4.3 - dexie: - specifier: ^4.0.7 - version: 4.0.7 - dexie-react-hooks: - specifier: ^1.1.7 - version: 1.1.7(@types/react@18.2.66)(dexie@4.0.7)(react@18.2.0) - dompurify: - specifier: ^3.1.7 - version: 3.1.7 - emoji-mart: - specifier: ^5.5.2 - version: 5.6.0 - emoji-regex: - specifier: ^10.2.1 - version: 10.3.0 - escape-string-regexp: - specifier: ^5.0.0 - version: 5.0.0 - events: - specifier: ^3.3.0 - version: 3.3.0 - google-protobuf: - specifier: ^3.15.12 - version: 3.21.2 - hast-util-to-mdast: - specifier: ^10.1.0 - version: 10.1.0 - highlight.js: - specifier: ^11.10.0 - version: 11.10.0 - html-parse-stringify: - specifier: ^3.0.1 - version: 3.0.1 - i18next: - specifier: ^22.4.10 - version: 22.5.1 - i18next-browser-languagedetector: - specifier: ^7.0.1 - version: 7.2.1 - i18next-resources-to-backend: - specifier: ^1.1.4 - version: 1.2.1 - is-hotkey: - specifier: ^0.2.0 - version: 0.2.0 - jest: - specifier: ^29.5.0 - version: 29.5.0(@types/node@20.11.30) - js-base64: - specifier: ^3.7.5 - version: 3.7.7 - js-md5: - specifier: ^0.8.3 - version: 0.8.3 - katex: - specifier: ^0.16.7 - version: 0.16.10 - lightgallery: - specifier: ^2.7.2 - version: 2.7.2 - lodash-es: - specifier: ^4.17.21 - version: 4.17.21 - mermaid: - specifier: ^11.4.1 - version: 11.4.1 - nanoid: - specifier: ^4.0.0 - version: 4.0.2 - notistack: - specifier: ^3.0.1 - version: 3.0.1(csstype@3.1.3)(react-dom@18.2.0)(react@18.2.0) - numeral: - specifier: ^2.0.6 - version: 2.0.6 - prismjs: - specifier: ^1.29.0 - version: 1.29.0 - protoc-gen-ts: - specifier: 0.8.7 - version: 0.8.7 - quill: - specifier: ^1.3.7 - version: 1.3.7 - quill-delta: - specifier: ^5.1.0 - version: 5.1.0 - react: - specifier: ^18.2.0 - version: 18.2.0 - react-beautiful-dnd: - specifier: ^13.1.1 - version: 13.1.1(react-dom@18.2.0)(react@18.2.0) - react-big-calendar: - specifier: ^1.8.5 - version: 1.12.2(react-dom@18.2.0)(react@18.2.0) - react-color: - specifier: ^2.19.3 - version: 2.19.3(react@18.2.0) - react-custom-scrollbars: - specifier: ^4.2.1 - version: 4.2.1(react-dom@18.2.0)(react@18.2.0) - react-custom-scrollbars-2: - specifier: ^4.5.0 - version: 4.5.0(react-dom@18.2.0)(react@18.2.0) - react-datepicker: - specifier: ^4.23.0 - version: 4.25.0(react-dom@18.2.0)(react@18.2.0) - react-dom: - specifier: ^18.2.0 - version: 18.2.0(react@18.2.0) - react-error-boundary: - specifier: ^4.0.13 - version: 4.0.13(react@18.2.0) - react-helmet: - specifier: ^6.1.0 - version: 6.1.0(react@18.2.0) - react-hook-form: - specifier: ^7.52.2 - version: 7.52.2(react@18.2.0) - react-hot-toast: - specifier: ^2.4.1 - version: 2.4.1(csstype@3.1.3)(react-dom@18.2.0)(react@18.2.0) - react-i18next: - specifier: ^14.1.0 - version: 14.1.2(i18next@22.5.1)(react-dom@18.2.0)(react@18.2.0) - react-katex: - specifier: ^3.0.1 - version: 3.0.1(prop-types@15.8.1)(react@18.2.0) - react-measure: - specifier: ^2.5.2 - version: 2.5.2(react-dom@18.2.0)(react@18.2.0) - react-redux: - specifier: ^8.0.5 - version: 8.1.3(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1) - react-router-dom: - specifier: ^6.22.3 - version: 6.23.1(react-dom@18.2.0)(react@18.2.0) - react-swipeable-views: - specifier: ^0.14.0 - version: 0.14.0(react@18.2.0) - react-transition-group: - specifier: ^4.4.5 - version: 4.4.5(react-dom@18.2.0)(react@18.2.0) - react-virtualized-auto-sizer: - specifier: ^1.0.20 - version: 1.0.24(react-dom@18.2.0)(react@18.2.0) - react-vtree: - specifier: ^2.0.4 - version: 2.0.4(@types/react-window@1.8.8)(react-dom@18.2.0)(react-window@1.8.10)(react@18.2.0) - react-window: - specifier: ^1.8.10 - version: 1.8.10(react-dom@18.2.0)(react@18.2.0) - react-zoom-pan-pinch: - specifier: ^3.6.1 - version: 3.6.1(react-dom@18.2.0)(react@18.2.0) - react18-input-otp: - specifier: ^1.1.2 - version: 1.1.4(react-dom@18.2.0)(react@18.2.0) - redux: - specifier: ^4.2.1 - version: 4.2.1 - rehype-parse: - specifier: ^9.0.1 - version: 9.0.1 - rxjs: - specifier: ^7.8.0 - version: 7.8.0 - sass: - specifier: ^1.70.0 - version: 1.77.2 - slate: - specifier: ^0.101.4 - version: 0.101.5 - slate-history: - specifier: ^0.100.0 - version: 0.100.0(slate@0.101.5) - slate-react: - specifier: ^0.101.3 - version: 0.101.6(react-dom@18.2.0)(react@18.2.0)(slate@0.101.5) - smooth-scroll-into-view-if-needed: - specifier: ^2.0.2 - version: 2.0.2 - ts-results: - specifier: ^3.3.0 - version: 3.3.0 - unified: - specifier: ^11.0.5 - version: 11.0.5 - unist: - specifier: ^0.0.1 - version: 0.0.1 - unsplash-js: - specifier: ^7.0.19 - version: 7.0.19 - utf8: - specifier: ^3.0.0 - version: 3.0.0 - validator: - specifier: ^13.11.0 - version: 13.12.0 - vite-plugin-wasm: - specifier: ^3.3.0 - version: 3.3.0(vite@5.2.0) - y-indexeddb: - specifier: 9.0.12 - version: 9.0.12(yjs@14.0.0-1) - yjs: - specifier: 14.0.0-1 - version: 14.0.0-1 - -devDependencies: - '@babel/preset-env': - specifier: ^7.24.7 - version: 7.24.7(@babel/core@7.24.3) - '@babel/preset-react': - specifier: ^7.24.7 - version: 7.24.7(@babel/core@7.24.3) - '@babel/preset-typescript': - specifier: ^7.24.7 - version: 7.24.7(@babel/core@7.24.3) - '@cypress/code-coverage': - specifier: ^3.12.39 - version: 3.12.39(@babel/core@7.24.3)(@babel/preset-env@7.24.7)(babel-loader@9.1.3)(cypress@13.7.2)(webpack@5.91.0) - '@istanbuljs/nyc-config-babel': - specifier: ^3.0.0 - version: 3.0.0(@babel/register@7.24.6)(babel-plugin-istanbul@6.1.1) - '@istanbuljs/nyc-config-typescript': - specifier: ^1.0.2 - version: 1.0.2(nyc@15.1.0) - '@svgr/plugin-svgo': - specifier: ^8.0.1 - version: 8.0.1(@svgr/core@8.1.0)(typescript@4.9.5) - '@tauri-apps/cli': - specifier: ^1.5.11 - version: 1.5.11 - '@testing-library/react': - specifier: ^16.0.0 - version: 16.0.0(@testing-library/dom@10.1.0)(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@types/cypress-image-snapshot': - specifier: ^3.1.9 - version: 3.1.9 - '@types/dompurify': - specifier: ^3.0.5 - version: 3.0.5 - '@types/google-protobuf': - specifier: ^3.15.12 - version: 3.15.12 - '@types/is-hotkey': - specifier: ^0.1.7 - version: 0.1.7 - '@types/jest': - specifier: ^29.5.3 - version: 29.5.3 - '@types/katex': - specifier: ^0.16.0 - version: 0.16.0 - '@types/lodash-es': - specifier: ^4.17.11 - version: 4.17.11 - '@types/node': - specifier: ^20.11.30 - version: 20.11.30 - '@types/numeral': - specifier: ^2.0.5 - version: 2.0.5 - '@types/prismjs': - specifier: ^1.26.0 - version: 1.26.0 - '@types/quill': - specifier: ^2.0.10 - version: 2.0.10 - '@types/react': - specifier: ^18.2.66 - version: 18.2.66 - '@types/react-beautiful-dnd': - specifier: ^13.1.3 - version: 13.1.3 - '@types/react-big-calendar': - specifier: ^1.8.9 - version: 1.8.9 - '@types/react-color': - specifier: ^3.0.6 - version: 3.0.6 - '@types/react-custom-scrollbars': - specifier: ^4.0.13 - version: 4.0.13 - '@types/react-datepicker': - specifier: ^4.19.3 - version: 4.19.3(react-dom@18.2.0)(react@18.2.0) - '@types/react-dom': - specifier: ^18.2.22 - version: 18.2.22 - '@types/react-helmet': - specifier: ^6.1.11 - version: 6.1.11 - '@types/react-katex': - specifier: ^3.0.0 - version: 3.0.0 - '@types/react-measure': - specifier: ^2.0.12 - version: 2.0.12 - '@types/react-transition-group': - specifier: ^4.4.6 - version: 4.4.6 - '@types/react-window': - specifier: ^1.8.8 - version: 1.8.8 - '@types/utf8': - specifier: ^3.0.1 - version: 3.0.1 - '@types/uuid': - specifier: ^9.0.1 - version: 9.0.1 - '@types/validator': - specifier: ^13.11.9 - version: 13.11.9 - '@typescript-eslint/eslint-plugin': - specifier: ^7.2.0 - version: 7.2.0(@typescript-eslint/parser@7.2.0)(eslint@8.57.0)(typescript@4.9.5) - '@typescript-eslint/parser': - specifier: ^7.2.0 - version: 7.2.0(eslint@8.57.0)(typescript@4.9.5) - '@vitejs/plugin-react': - specifier: ^4.2.1 - version: 4.2.1(vite@5.2.0) - autoprefixer: - specifier: ^10.4.13 - version: 10.4.13(postcss@8.4.21) - axios-mock-adapter: - specifier: ^2.0.0 - version: 2.0.0(axios@1.7.2) - babel-jest: - specifier: ^29.6.2 - version: 29.6.2(@babel/core@7.24.3) - chalk: - specifier: ^4.1.2 - version: 4.1.2 - cheerio: - specifier: 1.0.0-rc.12 - version: 1.0.0-rc.12 - cross-env: - specifier: ^7.0.3 - version: 7.0.3 - cypress: - specifier: ^13.7.2 - version: 13.7.2 - cypress-image-snapshot: - specifier: ^4.0.1 - version: 4.0.1(cypress@13.7.2)(jest@29.5.0) - cypress-real-events: - specifier: ^1.13.0 - version: 1.13.0(cypress@13.7.2) - eslint: - specifier: ^8.57.0 - version: 8.57.0 - eslint-plugin-react: - specifier: ^7.32.2 - version: 7.32.2(eslint@8.57.0) - eslint-plugin-react-hooks: - specifier: ^4.6.0 - version: 4.6.0(eslint@8.57.0) - eslint-plugin-react-refresh: - specifier: ^0.4.6 - version: 0.4.6(eslint@8.57.0) - istanbul-lib-coverage: - specifier: ^3.2.2 - version: 3.2.2 - jest-environment-jsdom: - specifier: ^29.6.2 - version: 29.6.2 - jest-node-exports-resolver: - specifier: ^1.1.6 - version: 1.1.6 - nyc: - specifier: ^15.1.0 - version: 15.1.0 - pino: - specifier: ^9.2.0 - version: 9.2.0 - pino-pretty: - specifier: ^11.2.1 - version: 11.2.1 - postcss: - specifier: ^8.4.21 - version: 8.4.21 - prettier: - specifier: 2.8.4 - version: 2.8.4 - prettier-plugin-tailwindcss: - specifier: ^0.2.2 - version: 0.2.2(prettier@2.8.4) - rollup-plugin-visualizer: - specifier: ^5.12.0 - version: 5.12.0 - style-dictionary: - specifier: ^3.9.2 - version: 3.9.2 - tailwindcss: - specifier: ^3.2.7 - version: 3.2.7(postcss@8.4.21) - ts-jest: - specifier: ^29.1.1 - version: 29.1.1(@babel/core@7.24.3)(babel-jest@29.6.2)(jest@29.5.0)(typescript@4.9.5) - ts-node-dev: - specifier: ^2.0.0 - version: 2.0.0(@types/node@20.11.30)(typescript@4.9.5) - tsconfig-paths-jest: - specifier: ^0.0.1 - version: 0.0.1 - typescript: - specifier: 4.9.5 - version: 4.9.5 - uuid: - specifier: ^9.0.0 - version: 9.0.0 - vite: - specifier: ^5.2.0 - version: 5.2.0(@types/node@20.11.30)(sass@1.77.2) - vite-plugin-compression2: - specifier: ^1.0.0 - version: 1.0.0 - vite-plugin-externals: - specifier: ^0.6.2 - version: 0.6.2(vite@5.2.0) - vite-plugin-html: - specifier: ^3.2.2 - version: 3.2.2(vite@5.2.0) - vite-plugin-importer: - specifier: ^0.2.5 - version: 0.2.5 - vite-plugin-istanbul: - specifier: ^6.0.2 - version: 6.0.2(vite@5.2.0) - vite-plugin-svgr: - specifier: ^3.2.0 - version: 3.2.0(typescript@4.9.5)(vite@5.2.0) - vite-plugin-terminal: - specifier: ^1.2.0 - version: 1.2.0(vite@5.2.0) - vite-plugin-total-bundle-size: - specifier: ^1.0.7 - version: 1.0.7(vite@5.2.0) - -packages: - - /@aashutoshrathi/word-wrap@1.2.6: - resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} - engines: {node: '>=0.10.0'} - dev: true - - /@alloc/quick-lru@5.2.0: - resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} - engines: {node: '>=10'} - dev: false - - /@ampproject/remapping@2.3.0: - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 - - /@antfu/install-pkg@0.4.1: - resolution: {integrity: sha512-T7yB5QNG29afhWVkVq7XeIMBa5U/vs9mX69YqayXypPRmYzUmzwnYltplHmPtZ4HPCn+sQKeXW8I47wCbuBOjw==} - dependencies: - package-manager-detector: 0.2.6 - tinyexec: 0.3.1 - dev: false - - /@antfu/utils@0.7.10: - resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==} - dev: false - - /@appflowyinc/editor@0.0.39(@types/react-dom@18.2.22)(@types/react@18.2.66)(i18next-resources-to-backend@1.2.1)(i18next@22.5.1)(react-dom@18.2.0)(react-i18next@14.1.2)(react@18.2.0)(slate-history@0.100.0)(slate-react@0.101.6)(slate@0.101.5): - resolution: {integrity: sha512-Cbitz/yNcioB+QS3K06Xzt/7lN+AnlAbzZLQ4BPw6dPLvTFbE8f+b6a7QkxN/EU2WKgc1qMfHI+m4UAU7v5tvg==} - peerDependencies: - i18next: ^22.4.10 - i18next-resources-to-backend: ^1.2.1 - react: ^18.3.1 - react-dom: ^18.3.1 - react-i18next: ^14.1.0 - slate: ^0.112.0 - slate-history: ^0.110.3 - slate-react: ^0.112.0 - dependencies: - '@radix-ui/react-label': 2.1.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-popover': 1.1.4(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-select': 2.1.4(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-separator': 1.1.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-slot': 1.1.1(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-switch': 1.1.2(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-tooltip': 1.1.6(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - class-variance-authority: 0.7.1 - clsx: 2.1.1 - i18next: 22.5.1 - i18next-resources-to-backend: 1.2.1 - is-hotkey: 0.2.0 - lucide-react: 0.468.0(react@18.2.0) - mdast-util-from-markdown: 2.0.2 - prismjs: 1.29.0 - quill-delta: 5.1.0 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-i18next: 14.1.2(i18next@22.5.1)(react-dom@18.2.0)(react@18.2.0) - sass: 1.83.0 - slate: 0.101.5 - slate-history: 0.100.0(slate@0.101.5) - slate-react: 0.101.6(react-dom@18.2.0)(react@18.2.0)(slate@0.101.5) - tailwind-merge: 2.5.5 - tailwindcss: 3.4.17 - tailwindcss-animate: 1.0.7(tailwindcss@3.4.17) - transitivePeerDependencies: - - '@types/react' - - '@types/react-dom' - - supports-color - - ts-node - dev: false - - /@atlaskit/analytics-next-stable-react-context@1.0.1(react@18.2.0): - resolution: {integrity: sha512-iO6+hIp09dF4iAZQarVz3vKY1kM5Ij5CExYcK9jgc2q+OH8nv8n+BPFeJTdzGOGopmbUZn5Opj9pYQvge1Gr4Q==} - peerDependencies: - react: ^16.8.0 - dependencies: - react: 18.2.0 - tslib: 2.6.2 - dev: false - - /@atlaskit/analytics-next@9.3.0(react@18.2.0): - resolution: {integrity: sha512-mR5CndP92k2gFl8sWu4DJZZEpEQ4bnp5Z3fWCZE1oySiOKK8iM+KzKH4FMCaSUGOhWW6/5VeuXCcXvaogaAmsA==} - peerDependencies: - react: ^16.8.0 - dependencies: - '@atlaskit/analytics-next-stable-react-context': 1.0.1(react@18.2.0) - '@atlaskit/platform-feature-flags': 0.2.5 - '@babel/runtime': 7.24.1 - prop-types: 15.8.1 - react: 18.2.0 - use-memo-one: 1.1.3(react@18.2.0) - dev: false - - /@atlaskit/app-provider@1.3.2(react@18.2.0): - resolution: {integrity: sha512-tvyMNrydTyu5yJK78zUjqbJwgNRdW5nQ31imWFav5PvWXwc36lfGiTXRX/JIxJNBC3rBJ0gLAyrrb9YMzyWcTw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ~18.2.0 - dependencies: - '@atlaskit/tokens': 1.49.1(react@18.2.0) - '@babel/runtime': 7.24.1 - bind-event-listener: 3.0.0 - react: 18.2.0 - transitivePeerDependencies: - - supports-color - dev: false - - /@atlaskit/css@0.1.0(react@18.2.0): - resolution: {integrity: sha512-FQfiLoYJrwTYhjSpa+RA8omPAPlJ5rl0OCJ0NAkMXRGx1o8ItNBW5EcRBwW0wUHaBOZ4oFS5EUshk185E/G/zQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ~18.2.0 - dependencies: - '@atlaskit/tokens': 1.49.1(react@18.2.0) - '@babel/runtime': 7.24.1 - '@compiled/react': 0.17.1(react@18.2.0) - react: 18.2.0 - transitivePeerDependencies: - - supports-color - dev: false - - /@atlaskit/ds-lib@2.3.1(react@18.2.0): - resolution: {integrity: sha512-DVUE3hYLhdEZy4NnsxqiCqKC5Ym3CM/DGRQlnSPcABFNL0N0FfTXso3pLpkJnMZBtEnd2pn13mPJ2VQlSISRuw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ~18.2.0 - dependencies: - '@babel/runtime': 7.24.1 - bind-event-listener: 3.0.0 - react: 18.2.0 - dev: false - - /@atlaskit/interaction-context@2.1.4(react@18.2.0): - resolution: {integrity: sha512-MTuHN8wLYBPADE83Q+9KF5BcKyMW9/FkmA+lB/XnHwYIL86sMPzMSTM0DPG7crq/JI0JM0jlyY3Xzz0Aba7G+A==} - peerDependencies: - react: ^16.8.0 - dependencies: - '@babel/runtime': 7.24.1 - react: 18.2.0 - dev: false - - /@atlaskit/platform-feature-flags@0.2.5: - resolution: {integrity: sha512-0fD2aDxn2mE59D4acUhVib+YF2HDYuuPH50aYwpQdcV/CsVkAaJsMKy8WhWSulcRFeMYp72kfIfdy0qGdRB7Uw==} - dependencies: - '@babel/runtime': 7.26.0 - dev: false - - /@atlaskit/primitives@5.7.0(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-eCLyHN1BllNpwqA2YqCmYpqwoiNVcW3R6bHrpKmsW8uvPE/+Bd45hOiPwvCPJUPyK1ZNMfnkegWKkoxcmjMYIQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - dependencies: - '@atlaskit/analytics-next': 9.3.0(react@18.2.0) - '@atlaskit/app-provider': 1.3.2(react@18.2.0) - '@atlaskit/css': 0.1.0(react@18.2.0) - '@atlaskit/ds-lib': 2.3.1(react@18.2.0) - '@atlaskit/interaction-context': 2.1.4(react@18.2.0) - '@atlaskit/tokens': 1.49.1(react@18.2.0) - '@atlaskit/visually-hidden': 1.3.0(@types/react@18.2.66)(react@18.2.0) - '@babel/runtime': 7.24.1 - '@emotion/react': 11.11.4(@types/react@18.2.66)(react@18.2.0) - '@emotion/serialize': 1.1.4 - bind-event-listener: 3.0.0 - react: 18.2.0 - tiny-invariant: 1.3.3 - transitivePeerDependencies: - - '@types/react' - - supports-color - dev: false - - /@atlaskit/tokens@1.49.1(react@18.2.0): - resolution: {integrity: sha512-3SuhRMPUTU6b+nv0zVoGsNoqrUMtwQ/4iBbKhwaylRITanFxlxBwzW8XCCnn4sp1S2JupiT5BksI0h6jRoKN9Q==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ~18.2.0 - dependencies: - '@atlaskit/ds-lib': 2.3.1(react@18.2.0) - '@atlaskit/platform-feature-flags': 0.2.5 - '@babel/runtime': 7.24.1 - '@babel/traverse': 7.24.1 - '@babel/types': 7.24.0 - bind-event-listener: 3.0.0 - react: 18.2.0 - transitivePeerDependencies: - - supports-color - dev: false - - /@atlaskit/visually-hidden@1.3.0(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-iOHCxRnhNV3gnqOHuyLOnsFibfHpr1T28XUPYZjtN9bDQbn1GdSDYLoIHnLK+2enqdILirsuUWi93mWM3dCCwg==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ~18.2.0 - dependencies: - '@babel/runtime': 7.24.1 - '@emotion/react': 11.14.0(@types/react@18.2.66)(react@18.2.0) - react: 18.2.0 - transitivePeerDependencies: - - '@types/react' - - supports-color - dev: false - - /@babel/code-frame@7.24.2: - resolution: {integrity: sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/highlight': 7.24.2 - picocolors: 1.1.1 - - /@babel/code-frame@7.24.7: - resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/highlight': 7.24.7 - picocolors: 1.1.1 - - /@babel/compat-data@7.24.1: - resolution: {integrity: sha512-Pc65opHDliVpRHuKfzI+gSA4zcgr65O4cl64fFJIWEEh8JoHIHh0Oez1Eo8Arz8zq/JhgKodQaxEwUPRtZylVA==} - engines: {node: '>=6.9.0'} - - /@babel/compat-data@7.24.7: - resolution: {integrity: sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==} - engines: {node: '>=6.9.0'} - dev: true - - /@babel/core@7.24.3: - resolution: {integrity: sha512-5FcvN1JHw2sHJChotgx8Ek0lyuh4kCKelgMTTqhYJJtloNvUfpAFMeNQUtdlIaktwrSV9LtCdqwk48wL2wBacQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.24.2 - '@babel/generator': 7.24.1 - '@babel/helper-compilation-targets': 7.23.6 - '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.3) - '@babel/helpers': 7.24.1 - '@babel/parser': 7.24.1 - '@babel/template': 7.24.0 - '@babel/traverse': 7.24.1 - '@babel/types': 7.24.0 - convert-source-map: 2.0.0 - debug: 4.3.4(supports-color@8.1.1) - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - /@babel/generator@7.24.1: - resolution: {integrity: sha512-DfCRfZsBcrPEHUfuBMgbJ1Ut01Y/itOs+hY2nFLgqsqXd52/iSiVq5TITtUasIUgm+IIKdY2/1I7auiQOEeC9A==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.24.0 - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 - jsesc: 2.5.2 - - /@babel/generator@7.24.7: - resolution: {integrity: sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.24.7 - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 - jsesc: 2.5.2 - - /@babel/helper-annotate-as-pure@7.24.7: - resolution: {integrity: sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.24.7 - dev: true - - /@babel/helper-builder-binary-assignment-operator-visitor@7.24.7: - resolution: {integrity: sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/helper-compilation-targets@7.23.6: - resolution: {integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/compat-data': 7.24.1 - '@babel/helper-validator-option': 7.23.5 - browserslist: 4.23.0 - lru-cache: 5.1.1 - semver: 6.3.1 - - /@babel/helper-compilation-targets@7.24.7: - resolution: {integrity: sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/compat-data': 7.24.7 - '@babel/helper-validator-option': 7.24.7 - browserslist: 4.23.0 - lru-cache: 5.1.1 - semver: 6.3.1 - dev: true - - /@babel/helper-create-class-features-plugin@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-kTkaDl7c9vO80zeX1rJxnuRpEsD5tA81yh11X1gQo+PhSti3JS+7qeZo9U4RHobKRiFPKaGK3svUAeb8D0Q7eg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-function-name': 7.24.7 - '@babel/helper-member-expression-to-functions': 7.24.7 - '@babel/helper-optimise-call-expression': 7.24.7 - '@babel/helper-replace-supers': 7.24.7(@babel/core@7.24.3) - '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 - '@babel/helper-split-export-declaration': 7.24.7 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/helper-create-regexp-features-plugin@7.24.6(@babel/core@7.24.3): - resolution: {integrity: sha512-C875lFBIWWwyv6MHZUG9HmRrlTDgOsLWZfYR0nW69gaKJNe0/Mpxx5r0EID2ZdHQkdUmQo2t0uNckTL08/1BgA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-annotate-as-pure': 7.24.7 - regexpu-core: 5.3.2 - semver: 6.3.1 - dev: true - - /@babel/helper-create-regexp-features-plugin@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-03TCmXy2FtXJEZfbXDTSqq1fRJArk7lX9DOFC/47VthYcxyIOx+eXQmdo6DOQvrbpIix+KfXwvuXdFDZHxt+rA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-annotate-as-pure': 7.24.7 - regexpu-core: 5.3.2 - semver: 6.3.1 - dev: true - - /@babel/helper-define-polyfill-provider@0.6.2(@babel/core@7.24.3): - resolution: {integrity: sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - debug: 4.3.7 - lodash.debounce: 4.0.8 - resolve: 1.22.8 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/helper-environment-visitor@7.22.20: - resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} - engines: {node: '>=6.9.0'} - - /@babel/helper-environment-visitor@7.24.7: - resolution: {integrity: sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.24.7 - - /@babel/helper-function-name@7.23.0: - resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.24.0 - '@babel/types': 7.24.0 - - /@babel/helper-function-name@7.24.7: - resolution: {integrity: sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.24.7 - '@babel/types': 7.24.7 - - /@babel/helper-hoist-variables@7.22.5: - resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.24.0 - - /@babel/helper-hoist-variables@7.24.7: - resolution: {integrity: sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.24.7 - - /@babel/helper-member-expression-to-functions@7.24.7: - resolution: {integrity: sha512-LGeMaf5JN4hAT471eJdBs/GK1DoYIJ5GCtZN/EsL6KUiiDZOvO/eKE11AMZJa2zP4zk4qe9V2O/hxAmkRc8p6w==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/helper-module-imports@7.24.3: - resolution: {integrity: sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.24.0 - - /@babel/helper-module-imports@7.24.7: - resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - transitivePeerDependencies: - - supports-color - - /@babel/helper-module-transforms@7.23.3(@babel/core@7.24.3): - resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-module-imports': 7.24.3 - '@babel/helper-simple-access': 7.22.5 - '@babel/helper-split-export-declaration': 7.22.6 - '@babel/helper-validator-identifier': 7.22.20 - - /@babel/helper-module-transforms@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-module-imports': 7.24.7 - '@babel/helper-simple-access': 7.24.7 - '@babel/helper-split-export-declaration': 7.24.7 - '@babel/helper-validator-identifier': 7.24.7 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/helper-optimise-call-expression@7.24.7: - resolution: {integrity: sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.24.7 - dev: true - - /@babel/helper-plugin-utils@7.24.0: - resolution: {integrity: sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==} - engines: {node: '>=6.9.0'} - - /@babel/helper-plugin-utils@7.24.7: - resolution: {integrity: sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==} - engines: {node: '>=6.9.0'} - - /@babel/helper-remap-async-to-generator@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-9pKLcTlZ92hNZMQfGCHImUpDOlAgkkpqalWEeftW5FBya75k8Li2ilerxkM/uBEj01iBZXcCIB/bwvDYgWyibA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-wrap-function': 7.24.7 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/helper-replace-supers@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-qTAxxBM81VEyoAY0TtLrx1oAEJc09ZK67Q9ljQToqCnA+55eNwCORaxlKyu+rNfX86o8OXRUSNUnrtsAZXM9sg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-member-expression-to-functions': 7.24.7 - '@babel/helper-optimise-call-expression': 7.24.7 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/helper-simple-access@7.22.5: - resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.24.0 - - /@babel/helper-simple-access@7.24.7: - resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/helper-skip-transparent-expression-wrappers@7.24.7: - resolution: {integrity: sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/helper-split-export-declaration@7.22.6: - resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.24.0 - - /@babel/helper-split-export-declaration@7.24.7: - resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.24.7 - - /@babel/helper-string-parser@7.24.1: - resolution: {integrity: sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==} - engines: {node: '>=6.9.0'} - - /@babel/helper-string-parser@7.24.6: - resolution: {integrity: sha512-WdJjwMEkmBicq5T9fm/cHND3+UlFa2Yj8ALLgmoSQAJZysYbBjw+azChSGPN4DSPLXOcooGRvDwZWMcF/mLO2Q==} - engines: {node: '>=6.9.0'} - dev: true - - /@babel/helper-string-parser@7.24.7: - resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==} - engines: {node: '>=6.9.0'} - - /@babel/helper-validator-identifier@7.22.20: - resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} - engines: {node: '>=6.9.0'} - - /@babel/helper-validator-identifier@7.24.7: - resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} - engines: {node: '>=6.9.0'} - - /@babel/helper-validator-option@7.23.5: - resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==} - engines: {node: '>=6.9.0'} - - /@babel/helper-validator-option@7.24.7: - resolution: {integrity: sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==} - engines: {node: '>=6.9.0'} - dev: true - - /@babel/helper-wrap-function@7.24.7: - resolution: {integrity: sha512-N9JIYk3TD+1vq/wn77YnJOqMtfWhNewNE+DJV4puD2X7Ew9J4JvrzrFDfTfyv5EgEXVy9/Wt8QiOErzEmv5Ifw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-function-name': 7.24.7 - '@babel/template': 7.24.7 - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/helpers@7.24.1: - resolution: {integrity: sha512-BpU09QqEe6ZCHuIHFphEFgvNSrubve1FtyMton26ekZ85gRGi6LrTF7zArARp2YvyFxloeiRmtSCq5sjh1WqIg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.24.0 - '@babel/traverse': 7.24.1 - '@babel/types': 7.24.0 - transitivePeerDependencies: - - supports-color - - /@babel/highlight@7.24.2: - resolution: {integrity: sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-validator-identifier': 7.22.20 - chalk: 2.4.2 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - /@babel/highlight@7.24.7: - resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-validator-identifier': 7.24.7 - chalk: 2.4.2 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - /@babel/parser@7.24.1: - resolution: {integrity: sha512-Zo9c7N3xdOIQrNip7Lc9wvRPzlRtovHVE4lkz8WEDr7uYh/GMQhSiIgFxGIArRHYdJE5kxtZjAf8rT0xhdLCzg==} - engines: {node: '>=6.0.0'} - hasBin: true - dependencies: - '@babel/types': 7.24.0 - - /@babel/parser@7.24.7: - resolution: {integrity: sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==} - engines: {node: '>=6.0.0'} - hasBin: true - dependencies: - '@babel/types': 7.24.7 - - /@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-TiT1ss81W80eQsN+722OaeQMY/G4yTb4G9JrqeiDADs3N8lbPMGldWi9x8tyqCW5NLx1Jh2AvkE6r6QvEltMMQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-unaQgZ/iRu/By6tsjMZzpeBZjChYfLYry6HrEXPoz3KmfF0sVBQ1l8zKMQ4xRGLWVsjuvB8nQfjNP/DcfEOCsg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.13.0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 - '@babel/plugin-transform-optional-chaining': 7.24.7(@babel/core@7.24.3) - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-utA4HuR6F4Vvcr+o4DnjL8fCOlgRFGbeeBEGNg3ZTrLFw6VWG5XmUrvcQ0FjIYMU2ST4XcR2Wsp7t9qOAPnxMg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.24.3): - resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - dev: true - - /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.24.3): - resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - - /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.24.3): - resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.0 - - /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.24.3): - resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - - /@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.24.3): - resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.24.3): - resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.24.3): - resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-syntax-import-assertions@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-syntax-import-attributes@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.24.3): - resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.0 - - /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.24.3): - resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.0 - - /@babel/plugin-syntax-jsx@7.24.1(@babel/core@7.24.3): - resolution: {integrity: sha512-2eCtxZXf+kbkMIsXS4poTvT4Yu5rXiRa+9xGVT56raghjmBTKMpFNc9R4IDiB4emao9eO22Ox7CxuJG7BgExqA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.0 - - /@babel/plugin-syntax-jsx@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.24.3): - resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - - /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.24.3): - resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.0 - - /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.24.3): - resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.0 - - /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.24.3): - resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.0 - - /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.24.3): - resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.0 - - /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.24.3): - resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.0 - - /@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.24.3): - resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.24.3): - resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.0 - - /@babel/plugin-syntax-typescript@7.24.1(@babel/core@7.24.3): - resolution: {integrity: sha512-Yhnmvy5HZEnHUty6i++gcfH1/l68AHnItFHnaCv6hn9dNh0hQvvQJsxpi4BMBFN5DLeHBuucT/0DgzXif/OyRw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.0 - - /@babel/plugin-syntax-typescript@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.24.3): - resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-create-regexp-features-plugin': 7.24.6(@babel/core@7.24.3) - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-transform-arrow-functions@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-transform-async-generator-functions@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-o+iF77e3u7ZS4AoAuJvapz9Fm001PuD2V3Lp6OSE4FYQke+cSewYtnek+THqGRWyQloRCyvWL1OkyfNEl9vr/g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-remap-async-to-generator': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.3) - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/plugin-transform-async-to-generator@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-module-imports': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-remap-async-to-generator': 7.24.7(@babel/core@7.24.3) - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/plugin-transform-block-scoped-functions@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-transform-block-scoping@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-Nd5CvgMbWc+oWzBsuaMcbwjJWAcp5qzrbg69SZdHSP7AMY0AbWFqFO0WTFCA1jxhMCwodRwvRec8k0QUbZk7RQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-transform-class-properties@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.24.3) - '@babel/helper-plugin-utils': 7.24.7 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/plugin-transform-class-static-block@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.12.0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.24.3) - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.24.3) - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/plugin-transform-classes@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-CFbbBigp8ln4FU6Bpy6g7sE8B/WmCmzvivzUC6xDAdWVsjYTXijpuuGJmYkAaoWAzcItGKT3IOAbxRItZ5HTjw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-function-name': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-replace-supers': 7.24.7(@babel/core@7.24.3) - '@babel/helper-split-export-declaration': 7.24.7 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/plugin-transform-computed-properties@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/template': 7.24.7 - dev: true - - /@babel/plugin-transform-destructuring@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-19eJO/8kdCQ9zISOf+SEUJM/bAUIsvY3YDnXZTupUCQ8LgrWnsG/gFB9dvXqdXnRXMAM8fvt7b0CBKQHNGy1mw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-transform-dotall-regex@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.3) - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-transform-duplicate-keys@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-transform-dynamic-import@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.3) - dev: true - - /@babel/plugin-transform-exponentiation-operator@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-builder-binary-assignment-operator-visitor': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/plugin-transform-export-namespace-from@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.3) - dev: true - - /@babel/plugin-transform-for-of@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/plugin-transform-function-name@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-U9FcnA821YoILngSmYkW6FjyQe2TyZD5pHt4EVIhmcTkrJw/3KqcrRSxuOo5tFZJi7TE19iDyI1u+weTI7bn2w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-function-name': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-transform-json-strings@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.3) - dev: true - - /@babel/plugin-transform-literals@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-vcwCbb4HDH+hWi8Pqenwnjy+UiklO4Kt1vfspcQYFhJdpthSnW8XvWGyDZWKNVrVbVViI/S7K9PDJZiUmP2fYQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-transform-logical-assignment-operators@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.3) - dev: true - - /@babel/plugin-transform-member-expression-literals@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-transform-modules-amd@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.3) - '@babel/helper-plugin-utils': 7.24.7 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/plugin-transform-modules-commonjs@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-iFI8GDxtevHJ/Z22J5xQpVqFLlMNstcLXh994xifFwxxGslr2ZXXLWgtBeLctOD63UFDArdvN6Tg8RFw+aEmjQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.3) - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-simple-access': 7.24.7 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/plugin-transform-modules-systemjs@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-GYQE0tW7YoaN13qFh3O1NCY4MPkUiAH3fiF7UcV/I3ajmDKEdG3l+UOcbAm4zUE3gnvUU+Eni7XrVKo9eO9auw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-hoist-variables': 7.24.7 - '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.3) - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-validator-identifier': 7.24.7 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/plugin-transform-modules-umd@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.3) - '@babel/helper-plugin-utils': 7.24.7 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/plugin-transform-named-capturing-groups-regex@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.3) - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-transform-new-target@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-transform-nullish-coalescing-operator@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.3) - dev: true - - /@babel/plugin-transform-numeric-separator@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.3) - dev: true - - /@babel/plugin-transform-object-rest-spread@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.3) - '@babel/plugin-transform-parameters': 7.24.7(@babel/core@7.24.3) - dev: true - - /@babel/plugin-transform-object-super@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-replace-supers': 7.24.7(@babel/core@7.24.3) - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/plugin-transform-optional-catch-binding@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.3) - dev: true - - /@babel/plugin-transform-optional-chaining@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-tK+0N9yd4j+x/4hxF3F0e0fu/VdcxU18y5SevtyM/PCFlQvXbR0Zmlo2eBrKtVipGNFzpq56o8WsIIKcJFUCRQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.3) - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/plugin-transform-parameters@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-transform-private-methods@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.24.3) - '@babel/helper-plugin-utils': 7.24.7 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/plugin-transform-private-property-in-object@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.24.3) - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.24.3) - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/plugin-transform-property-literals@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-transform-react-display-name@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-H/Snz9PFxKsS1JLI4dJLtnJgCJRoo0AUm3chP6NYr+9En1JMKloheEiLIhlp5MDVznWo+H3AAC1Mc8lmUEpsgg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-transform-react-jsx-development@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-QG9EnzoGn+Qar7rxuW+ZOsbWOt56FvvI93xInqsZDC5fsekx1AlIO4KIJ5M+D0p0SqSH156EpmZyXq630B8OlQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/plugin-transform-react-jsx': 7.24.7(@babel/core@7.24.3) - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/plugin-transform-react-jsx-self@7.24.1(@babel/core@7.24.3): - resolution: {integrity: sha512-kDJgnPujTmAZ/9q2CN4m2/lRsUUPDvsG3+tSHWUJIzMGTt5U/b/fwWd3RO3n+5mjLrsBrVa5eKFRVSQbi3dF1w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.0 - dev: true - - /@babel/plugin-transform-react-jsx-source@7.24.1(@babel/core@7.24.3): - resolution: {integrity: sha512-1v202n7aUq4uXAieRTKcwPzNyphlCuqHHDcdSNc+vdhoTEZcFMh+L5yZuCmGaIO7bs1nJUNfHB89TZyoL48xNA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.0 - dev: true - - /@babel/plugin-transform-react-jsx@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-+Dj06GDZEFRYvclU6k4bme55GKBEWUmByM/eoKuqg4zTNQHiApWRhQph5fxQB2wAEFvRzL1tOEj1RJ19wJrhoA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-module-imports': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.24.3) - '@babel/types': 7.24.7 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/plugin-transform-react-pure-annotations@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-PLgBVk3fzbmEjBJ/u8kFzOqS9tUeDjiaWud/rRym/yjCo/M9cASPlnrd2ZmmZpQT40fOOrvR8jh+n8jikrOhNA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-transform-regenerator@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - regenerator-transform: 0.15.2 - dev: true - - /@babel/plugin-transform-reserved-words@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-transform-shorthand-properties@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-transform-spread@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/plugin-transform-sticky-regex@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-transform-template-literals@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-transform-typeof-symbol@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-VtR8hDy7YLB7+Pet9IarXjg/zgCMSF+1mNS/EQEiEaUPoFXCVsHG64SIxcaaI2zJgRiv+YmgaQESUfWAdbjzgg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-transform-typescript@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-iLD3UNkgx2n/HrjBesVbYX6j0yqn/sJktvbtKKgcaLIQ4bTTQ8obAypc1VpyHPD2y4Phh9zHOaAt8e/L14wCpw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.24.3) - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-typescript': 7.24.7(@babel/core@7.24.3) - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/plugin-transform-unicode-escapes@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-transform-unicode-property-regex@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.3) - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-transform-unicode-regex@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.3) - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/plugin-transform-unicode-sets-regex@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.3) - '@babel/helper-plugin-utils': 7.24.7 - dev: true - - /@babel/preset-env@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-1YZNsc+y6cTvWlDHidMBsQZrZfEFjRIo/BZCT906PMdzOyXtSLTgqGdrpcuTDCXyd11Am5uQULtDIcCfnTc8fQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/compat-data': 7.24.7 - '@babel/core': 7.24.3 - '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-validator-option': 7.24.7 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.24.3) - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.3) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.24.3) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.24.3) - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.3) - '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.3) - '@babel/plugin-syntax-import-assertions': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-syntax-import-attributes': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.3) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.3) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.3) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.3) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.3) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.3) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.3) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.3) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.24.3) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.3) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.24.3) - '@babel/plugin-transform-arrow-functions': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-async-generator-functions': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-async-to-generator': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-block-scoped-functions': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-block-scoping': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-class-properties': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-class-static-block': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-classes': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-computed-properties': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-destructuring': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-dotall-regex': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-duplicate-keys': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-dynamic-import': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-exponentiation-operator': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-export-namespace-from': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-for-of': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-function-name': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-json-strings': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-literals': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-logical-assignment-operators': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-member-expression-literals': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-modules-amd': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-modules-commonjs': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-modules-systemjs': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-modules-umd': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-named-capturing-groups-regex': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-new-target': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-nullish-coalescing-operator': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-numeric-separator': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-object-rest-spread': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-object-super': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-optional-catch-binding': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-optional-chaining': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-parameters': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-private-methods': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-private-property-in-object': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-property-literals': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-regenerator': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-reserved-words': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-shorthand-properties': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-spread': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-sticky-regex': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-template-literals': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-typeof-symbol': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-unicode-escapes': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-unicode-property-regex': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-unicode-regex': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-unicode-sets-regex': 7.24.7(@babel/core@7.24.3) - '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.24.3) - babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.24.3) - babel-plugin-polyfill-corejs3: 0.10.4(@babel/core@7.24.3) - babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.24.3) - core-js-compat: 3.37.1 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.24.3): - resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} - peerDependencies: - '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/types': 7.24.6 - esutils: 2.0.3 - dev: true - - /@babel/preset-react@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-AAH4lEkpmzFWrGVlHaxJB7RLH21uPQ9+He+eFLWHmF9IuFQVugz8eAsamaW0DXRrTfco5zj1wWtpdcXJUOfsag==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-validator-option': 7.24.7 - '@babel/plugin-transform-react-display-name': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-react-jsx': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-react-jsx-development': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-react-pure-annotations': 7.24.7(@babel/core@7.24.3) - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/preset-typescript@7.24.7(@babel/core@7.24.3): - resolution: {integrity: sha512-SyXRe3OdWwIwalxDg5UtJnJQO+YPcTfwiIY2B0Xlddh9o7jpWLvv8X1RthIeDOxQ+O1ML5BLPCONToObyVQVuQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-validator-option': 7.24.7 - '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-modules-commonjs': 7.24.7(@babel/core@7.24.3) - '@babel/plugin-transform-typescript': 7.24.7(@babel/core@7.24.3) - transitivePeerDependencies: - - supports-color - dev: true - - /@babel/register@7.24.6(@babel/core@7.24.3): - resolution: {integrity: sha512-WSuFCc2wCqMeXkz/i3yfAAsxwWflEgbVkZzivgAmXl/MxrXeoYFZOOPllbC8R8WTF7u61wSRQtDVZ1879cdu6w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - clone-deep: 4.0.1 - find-cache-dir: 2.1.0 - make-dir: 2.1.0 - pirates: 4.0.6 - source-map-support: 0.5.21 - dev: true - - /@babel/regjsgen@0.8.0: - resolution: {integrity: sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==} - dev: true - - /@babel/runtime@7.0.0: - resolution: {integrity: sha512-7hGhzlcmg01CvH1EHdSPVXYX1aJ8KCEyz6I9xYIi/asDtzBPMyMhVibhM/K6g/5qnKBwjZtp10bNZIEFTRW1MA==} - dependencies: - regenerator-runtime: 0.12.1 - dev: false - - /@babel/runtime@7.24.1: - resolution: {integrity: sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==} - engines: {node: '>=6.9.0'} - dependencies: - regenerator-runtime: 0.14.1 - dev: false - - /@babel/runtime@7.24.6: - resolution: {integrity: sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw==} - engines: {node: '>=6.9.0'} - dependencies: - regenerator-runtime: 0.14.1 - - /@babel/runtime@7.26.0: - resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} - engines: {node: '>=6.9.0'} - dependencies: - regenerator-runtime: 0.14.1 - - /@babel/template@7.24.0: - resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.24.2 - '@babel/parser': 7.24.1 - '@babel/types': 7.24.0 - - /@babel/template@7.24.7: - resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.24.7 - '@babel/parser': 7.24.7 - '@babel/types': 7.24.7 - - /@babel/traverse@7.24.1: - resolution: {integrity: sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.24.2 - '@babel/generator': 7.24.1 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-function-name': 7.23.0 - '@babel/helper-hoist-variables': 7.22.5 - '@babel/helper-split-export-declaration': 7.22.6 - '@babel/parser': 7.24.1 - '@babel/types': 7.24.0 - debug: 4.3.7 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - - /@babel/traverse@7.24.7: - resolution: {integrity: sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.24.7 - '@babel/generator': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-function-name': 7.24.7 - '@babel/helper-hoist-variables': 7.24.7 - '@babel/helper-split-export-declaration': 7.24.7 - '@babel/parser': 7.24.7 - '@babel/types': 7.24.7 - debug: 4.3.7 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - - /@babel/types@7.24.0: - resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-string-parser': 7.24.1 - '@babel/helper-validator-identifier': 7.22.20 - to-fast-properties: 2.0.0 - - /@babel/types@7.24.6: - resolution: {integrity: sha512-WaMsgi6Q8zMgMth93GvWPXkhAIEobfsIkLTacoVZoK1J0CevIPGYY2Vo5YvJGqyHqXM6P4ppOYGsIRU8MM9pFQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-string-parser': 7.24.6 - '@babel/helper-validator-identifier': 7.24.7 - to-fast-properties: 2.0.0 - dev: true - - /@babel/types@7.24.7: - resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-string-parser': 7.24.7 - '@babel/helper-validator-identifier': 7.24.7 - to-fast-properties: 2.0.0 - - /@bcoe/v8-coverage@0.2.3: - resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - - /@braintree/sanitize-url@7.1.0: - resolution: {integrity: sha512-o+UlMLt49RvtCASlOMW0AkHnabN9wR9rwCCherxO0yG4Npy34GkvrAqdXQvrhNs+jh+gkK8gB8Lf05qL/O7KWg==} - dev: false - - /@chevrotain/cst-dts-gen@11.0.3: - resolution: {integrity: sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==} - dependencies: - '@chevrotain/gast': 11.0.3 - '@chevrotain/types': 11.0.3 - lodash-es: 4.17.21 - dev: false - - /@chevrotain/gast@11.0.3: - resolution: {integrity: sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==} - dependencies: - '@chevrotain/types': 11.0.3 - lodash-es: 4.17.21 - dev: false - - /@chevrotain/regexp-to-ast@11.0.3: - resolution: {integrity: sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==} - dev: false - - /@chevrotain/types@11.0.3: - resolution: {integrity: sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==} - dev: false - - /@chevrotain/utils@11.0.3: - resolution: {integrity: sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==} - dev: false - - /@colors/colors@1.5.0: - resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} - engines: {node: '>=0.1.90'} - requiresBuild: true - dev: true - optional: true - - /@compiled/react@0.17.1(react@18.2.0): - resolution: {integrity: sha512-1CzTOrwNHOUmz9QGYHv8R8J6ejUyaNYiaUN6/dIM0Wu3G5CIam0KgsqvRikfGPrTtBfAQYMmdI9ytzxUKYwJrg==} - peerDependencies: - react: '>= 16.12.0' - dependencies: - csstype: 3.1.3 - react: 18.2.0 - dev: false - - /@cspotcode/source-map-support@0.8.1: - resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} - engines: {node: '>=12'} - dependencies: - '@jridgewell/trace-mapping': 0.3.9 - dev: true - - /@cypress/code-coverage@3.12.39(@babel/core@7.24.3)(@babel/preset-env@7.24.7)(babel-loader@9.1.3)(cypress@13.7.2)(webpack@5.91.0): - resolution: {integrity: sha512-ja7I/GRmkSAW9e3O7pideWcNUEHao0WT6sRyXQEURoxkJUASJssJ7Kb/bd3eMYmkUCiD5CRFqWR5BGF4mWVaUw==} - peerDependencies: - '@babel/core': ^7.0.1 - '@babel/preset-env': ^7.0.0 - babel-loader: ^8.3 || ^9 - cypress: '*' - webpack: ^4 || ^5 - dependencies: - '@babel/core': 7.24.3 - '@babel/preset-env': 7.24.7(@babel/core@7.24.3) - '@cypress/webpack-preprocessor': 6.0.1(@babel/core@7.24.3)(@babel/preset-env@7.24.7)(babel-loader@9.1.3)(webpack@5.91.0) - babel-loader: 9.1.3(@babel/core@7.24.3)(webpack@5.91.0) - chalk: 4.1.2 - cypress: 13.7.2 - dayjs: 1.11.10 - debug: 4.3.4(supports-color@8.1.1) - execa: 4.1.0 - globby: 11.1.0 - istanbul-lib-coverage: 3.2.2 - js-yaml: 4.1.0 - nyc: 15.1.0 - webpack: 5.91.0 - transitivePeerDependencies: - - supports-color - dev: true - - /@cypress/request@3.0.1: - resolution: {integrity: sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==} - engines: {node: '>= 6'} - dependencies: - aws-sign2: 0.7.0 - aws4: 1.12.0 - caseless: 0.12.0 - combined-stream: 1.0.8 - extend: 3.0.2 - forever-agent: 0.6.1 - form-data: 2.3.3 - http-signature: 1.3.6 - is-typedarray: 1.0.0 - isstream: 0.1.2 - json-stringify-safe: 5.0.1 - mime-types: 2.1.35 - performance-now: 2.1.0 - qs: 6.10.4 - safe-buffer: 5.1.2 - tough-cookie: 4.1.3 - tunnel-agent: 0.6.0 - uuid: 8.3.2 - dev: true - - /@cypress/webpack-preprocessor@6.0.1(@babel/core@7.24.3)(@babel/preset-env@7.24.7)(babel-loader@9.1.3)(webpack@5.91.0): - resolution: {integrity: sha512-WVNeFVSnFKxE3WZNRIriduTgqJRpevaiJIPlfqYTTzfXRD7X1Pv4woDE+G4caPV9bJqVKmVFiwzrXMRNeJxpxA==} - peerDependencies: - '@babel/core': ^7.0.1 - '@babel/preset-env': ^7.0.0 - babel-loader: ^8.3 || ^9 - webpack: ^4 || ^5 - dependencies: - '@babel/core': 7.24.3 - '@babel/preset-env': 7.24.7(@babel/core@7.24.3) - babel-loader: 9.1.3(@babel/core@7.24.3)(webpack@5.91.0) - bluebird: 3.7.1 - debug: 4.3.4(supports-color@8.1.1) - lodash: 4.17.21 - webpack: 5.91.0 - transitivePeerDependencies: - - supports-color - dev: true - - /@cypress/xvfb@1.2.4(supports-color@8.1.1): - resolution: {integrity: sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==} - dependencies: - debug: 3.2.7(supports-color@8.1.1) - lodash.once: 4.1.1 - transitivePeerDependencies: - - supports-color - dev: true - - /@emoji-mart/data@1.2.1: - resolution: {integrity: sha512-no2pQMWiBy6gpBEiqGeU77/bFejDqUTRY7KX+0+iur13op3bqUsXdnwoZs6Xb1zbv0gAj5VvS1PWoUUckSr5Dw==} - dev: false - - /@emoji-mart/react@1.1.1(emoji-mart@5.6.0)(react@18.2.0): - resolution: {integrity: sha512-NMlFNeWgv1//uPsvLxvGQoIerPuVdXwK/EUek8OOkJ6wVOWPUizRBJU0hDqWZCOROVpfBgCemaC3m6jDOXi03g==} - peerDependencies: - emoji-mart: ^5.2 - react: ^16.8 || ^17 || ^18 - dependencies: - emoji-mart: 5.6.0 - react: 18.2.0 - dev: false - - /@emotion/babel-plugin@11.11.0: - resolution: {integrity: sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==} - dependencies: - '@babel/helper-module-imports': 7.24.3 - '@babel/runtime': 7.24.1 - '@emotion/hash': 0.9.1 - '@emotion/memoize': 0.8.1 - '@emotion/serialize': 1.1.4 - babel-plugin-macros: 3.1.0 - convert-source-map: 1.9.0 - escape-string-regexp: 4.0.0 - find-root: 1.1.0 - source-map: 0.5.7 - stylis: 4.2.0 - dev: false - - /@emotion/babel-plugin@11.13.5: - resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==} - dependencies: - '@babel/helper-module-imports': 7.24.7 - '@babel/runtime': 7.26.0 - '@emotion/hash': 0.9.2 - '@emotion/memoize': 0.9.0 - '@emotion/serialize': 1.3.3 - babel-plugin-macros: 3.1.0 - convert-source-map: 1.9.0 - escape-string-regexp: 4.0.0 - find-root: 1.1.0 - source-map: 0.5.7 - stylis: 4.2.0 - transitivePeerDependencies: - - supports-color - dev: false - - /@emotion/cache@11.11.0: - resolution: {integrity: sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ==} - dependencies: - '@emotion/memoize': 0.8.1 - '@emotion/sheet': 1.2.2 - '@emotion/utils': 1.2.1 - '@emotion/weak-memoize': 0.3.1 - stylis: 4.2.0 - dev: false - - /@emotion/cache@11.14.0: - resolution: {integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==} - dependencies: - '@emotion/memoize': 0.9.0 - '@emotion/sheet': 1.4.0 - '@emotion/utils': 1.4.2 - '@emotion/weak-memoize': 0.4.0 - stylis: 4.2.0 - dev: false - - /@emotion/hash@0.9.1: - resolution: {integrity: sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==} - dev: false - - /@emotion/hash@0.9.2: - resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} - dev: false - - /@emotion/is-prop-valid@1.2.2: - resolution: {integrity: sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==} - dependencies: - '@emotion/memoize': 0.8.1 - dev: false - - /@emotion/memoize@0.8.1: - resolution: {integrity: sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==} - dev: false - - /@emotion/memoize@0.9.0: - resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==} - dev: false - - /@emotion/react@11.11.4(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw==} - peerDependencies: - '@types/react': '*' - react: '>=16.8.0' - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.24.1 - '@emotion/babel-plugin': 11.11.0 - '@emotion/cache': 11.11.0 - '@emotion/serialize': 1.1.4 - '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0) - '@emotion/utils': 1.2.1 - '@emotion/weak-memoize': 0.3.1 - '@types/react': 18.2.66 - hoist-non-react-statics: 3.3.2 - react: 18.2.0 - dev: false - - /@emotion/react@11.14.0(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==} - peerDependencies: - '@types/react': '*' - react: '>=16.8.0' - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.26.0 - '@emotion/babel-plugin': 11.13.5 - '@emotion/cache': 11.14.0 - '@emotion/serialize': 1.3.3 - '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@18.2.0) - '@emotion/utils': 1.4.2 - '@emotion/weak-memoize': 0.4.0 - '@types/react': 18.2.66 - hoist-non-react-statics: 3.3.2 - react: 18.2.0 - transitivePeerDependencies: - - supports-color - dev: false - - /@emotion/serialize@1.1.4: - resolution: {integrity: sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ==} - dependencies: - '@emotion/hash': 0.9.1 - '@emotion/memoize': 0.8.1 - '@emotion/unitless': 0.8.1 - '@emotion/utils': 1.2.1 - csstype: 3.1.3 - dev: false - - /@emotion/serialize@1.3.3: - resolution: {integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==} - dependencies: - '@emotion/hash': 0.9.2 - '@emotion/memoize': 0.9.0 - '@emotion/unitless': 0.10.0 - '@emotion/utils': 1.4.2 - csstype: 3.1.3 - dev: false - - /@emotion/sheet@1.2.2: - resolution: {integrity: sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA==} - dev: false - - /@emotion/sheet@1.4.0: - resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==} - dev: false - - /@emotion/styled@11.11.5(@emotion/react@11.11.4)(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-/ZjjnaNKvuMPxcIiUkf/9SHoG4Q196DRl1w82hQ3WCsjo1IUR8uaGWrC6a87CrYAW0Kb/pK7hk8BnLgLRi9KoQ==} - peerDependencies: - '@emotion/react': ^11.0.0-rc.0 - '@types/react': '*' - react: '>=16.8.0' - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.24.1 - '@emotion/babel-plugin': 11.11.0 - '@emotion/is-prop-valid': 1.2.2 - '@emotion/react': 11.11.4(@types/react@18.2.66)(react@18.2.0) - '@emotion/serialize': 1.1.4 - '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0) - '@emotion/utils': 1.2.1 - '@types/react': 18.2.66 - react: 18.2.0 - dev: false - - /@emotion/unitless@0.10.0: - resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==} - dev: false - - /@emotion/unitless@0.8.1: - resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==} - dev: false - - /@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@18.2.0): - resolution: {integrity: sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==} - peerDependencies: - react: '>=16.8.0' - dependencies: - react: 18.2.0 - dev: false - - /@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@18.2.0): - resolution: {integrity: sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==} - peerDependencies: - react: '>=16.8.0' - dependencies: - react: 18.2.0 - dev: false - - /@emotion/utils@1.2.1: - resolution: {integrity: sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg==} - dev: false - - /@emotion/utils@1.4.2: - resolution: {integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==} - dev: false - - /@emotion/weak-memoize@0.3.1: - resolution: {integrity: sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww==} - dev: false - - /@emotion/weak-memoize@0.4.0: - resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==} - dev: false - - /@esbuild/aix-ppc64@0.20.2: - resolution: {integrity: sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [aix] - requiresBuild: true - optional: true - - /@esbuild/android-arm64@0.20.2: - resolution: {integrity: sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - requiresBuild: true - optional: true - - /@esbuild/android-arm@0.20.2: - resolution: {integrity: sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - requiresBuild: true - optional: true - - /@esbuild/android-x64@0.20.2: - resolution: {integrity: sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - requiresBuild: true - optional: true - - /@esbuild/darwin-arm64@0.20.2: - resolution: {integrity: sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - optional: true - - /@esbuild/darwin-x64@0.20.2: - resolution: {integrity: sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - requiresBuild: true - optional: true - - /@esbuild/freebsd-arm64@0.20.2: - resolution: {integrity: sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true - optional: true - - /@esbuild/freebsd-x64@0.20.2: - resolution: {integrity: sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - optional: true - - /@esbuild/linux-arm64@0.20.2: - resolution: {integrity: sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - requiresBuild: true - optional: true - - /@esbuild/linux-arm@0.20.2: - resolution: {integrity: sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - requiresBuild: true - optional: true - - /@esbuild/linux-ia32@0.20.2: - resolution: {integrity: sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - requiresBuild: true - optional: true - - /@esbuild/linux-loong64@0.20.2: - resolution: {integrity: sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - requiresBuild: true - optional: true - - /@esbuild/linux-mips64el@0.20.2: - resolution: {integrity: sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - requiresBuild: true - optional: true - - /@esbuild/linux-ppc64@0.20.2: - resolution: {integrity: sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - requiresBuild: true - optional: true - - /@esbuild/linux-riscv64@0.20.2: - resolution: {integrity: sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - requiresBuild: true - optional: true - - /@esbuild/linux-s390x@0.20.2: - resolution: {integrity: sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - requiresBuild: true - optional: true - - /@esbuild/linux-x64@0.20.2: - resolution: {integrity: sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - requiresBuild: true - optional: true - - /@esbuild/netbsd-x64@0.20.2: - resolution: {integrity: sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - requiresBuild: true - optional: true - - /@esbuild/openbsd-x64@0.20.2: - resolution: {integrity: sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - requiresBuild: true - optional: true - - /@esbuild/sunos-x64@0.20.2: - resolution: {integrity: sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - requiresBuild: true - optional: true - - /@esbuild/win32-arm64@0.20.2: - resolution: {integrity: sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - requiresBuild: true - optional: true - - /@esbuild/win32-ia32@0.20.2: - resolution: {integrity: sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - requiresBuild: true - optional: true - - /@esbuild/win32-x64@0.20.2: - resolution: {integrity: sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - requiresBuild: true - optional: true - - /@eslint-community/eslint-utils@4.4.0(eslint@8.57.0): - resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - dependencies: - eslint: 8.57.0 - eslint-visitor-keys: 3.4.3 - dev: true - - /@eslint-community/regexpp@4.10.0: - resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - dev: true - - /@eslint/eslintrc@2.1.4: - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - ajv: 6.12.6 - debug: 4.3.4(supports-color@8.1.1) - espree: 9.6.1 - globals: 13.24.0 - ignore: 5.3.1 - import-fresh: 3.3.0 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - dev: true - - /@eslint/js@8.57.0: - resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true - - /@floating-ui/core@1.6.2: - resolution: {integrity: sha512-+2XpQV9LLZeanU4ZevzRnGFg2neDeKHgFLjP6YLW+tly0IvrhqT4u8enLGjLH3qeh85g19xY5rsAusfwTdn5lg==} - dependencies: - '@floating-ui/utils': 0.2.2 - dev: false - - /@floating-ui/dom@1.6.5: - resolution: {integrity: sha512-Nsdud2X65Dz+1RHjAIP0t8z5e2ff/IRbei6BqFrl1urT8sDVzM1HMQ+R0XcU5ceRfyO3I6ayeqIfh+6Wb8LGTw==} - dependencies: - '@floating-ui/core': 1.6.2 - '@floating-ui/utils': 0.2.2 - dev: false - - /@floating-ui/react-dom@2.1.0(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-lNzj5EQmEKn5FFKc04+zasr09h/uX8RtJRNj5gUXsSQIXHVWTVh+hVAg1vOMCexkX8EgvemMvIFpQfkosnVNyA==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - dependencies: - '@floating-ui/dom': 1.6.5 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@floating-ui/react-dom@2.1.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - dependencies: - '@floating-ui/dom': 1.6.5 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@floating-ui/react@0.26.27(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-jLP72x0Kr2CgY6eTYi/ra3VA9LOkTo4C+DUTrbFgFOExKy3omYVmwMjNKqxAHdsnyLS96BIDLcO2SlnsNf8KUQ==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - dependencies: - '@floating-ui/react-dom': 2.1.2(react-dom@18.2.0)(react@18.2.0) - '@floating-ui/utils': 0.2.8 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - tabbable: 6.2.0 - dev: false - - /@floating-ui/utils@0.2.2: - resolution: {integrity: sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw==} - dev: false - - /@floating-ui/utils@0.2.8: - resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==} - dev: false - - /@humanwhocodes/config-array@0.11.14: - resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} - engines: {node: '>=10.10.0'} - dependencies: - '@humanwhocodes/object-schema': 2.0.2 - debug: 4.3.4(supports-color@8.1.1) - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - dev: true - - /@humanwhocodes/module-importer@1.0.1: - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - dev: true - - /@humanwhocodes/object-schema@2.0.2: - resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==} - dev: true - - /@iconify/types@2.0.0: - resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} - dev: false - - /@iconify/utils@2.1.33: - resolution: {integrity: sha512-jP9h6v/g0BIZx0p7XGJJVtkVnydtbgTgt9mVNcGDYwaa7UhdHdI9dvoq+gKj9sijMSJKxUPEG2JyjsgXjxL7Kw==} - dependencies: - '@antfu/install-pkg': 0.4.1 - '@antfu/utils': 0.7.10 - '@iconify/types': 2.0.0 - debug: 4.3.7 - kolorist: 1.8.0 - local-pkg: 0.5.1 - mlly: 1.7.3 - transitivePeerDependencies: - - supports-color - dev: false - - /@icons/material@0.2.4(react@18.2.0): - resolution: {integrity: sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==} - peerDependencies: - react: '*' - dependencies: - react: 18.2.0 - dev: false - - /@isaacs/cliui@8.0.2: - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - dependencies: - string-width: 5.1.2 - string-width-cjs: /string-width@4.2.3 - strip-ansi: 7.1.0 - strip-ansi-cjs: /strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: /wrap-ansi@7.0.0 - - /@istanbuljs/load-nyc-config@1.1.0: - resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} - engines: {node: '>=8'} - dependencies: - camelcase: 5.3.1 - find-up: 4.1.0 - get-package-type: 0.1.0 - js-yaml: 3.14.1 - resolve-from: 5.0.0 - - /@istanbuljs/nyc-config-babel@3.0.0(@babel/register@7.24.6)(babel-plugin-istanbul@6.1.1): - resolution: {integrity: sha512-mPnSPXfTRWCzYsT64PnuPlce6/hGMCdVVMgU2FenXipbUd+FDwUlqlTihXxpxWzcNVOp8M+L1t/kIcgoC8A7hg==} - engines: {node: '>=8'} - peerDependencies: - '@babel/register': '*' - babel-plugin-istanbul: '>=5' - dependencies: - '@babel/register': 7.24.6(@babel/core@7.24.3) - babel-plugin-istanbul: 6.1.1 - dev: true - - /@istanbuljs/nyc-config-typescript@1.0.2(nyc@15.1.0): - resolution: {integrity: sha512-iKGIyMoyJuFnJRSVTZ78POIRvNnwZaWIf8vG4ZS3rQq58MMDrqEX2nnzx0R28V2X8JvmKYiqY9FP2hlJsm8A0w==} - engines: {node: '>=8'} - peerDependencies: - nyc: '>=15' - dependencies: - '@istanbuljs/schema': 0.1.3 - nyc: 15.1.0 - dev: true - - /@istanbuljs/schema@0.1.3: - resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} - engines: {node: '>=8'} - - /@jest/console@29.7.0: - resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.6.3 - '@types/node': 20.11.30 - chalk: 4.1.2 - jest-message-util: 29.7.0 - jest-util: 29.7.0 - slash: 3.0.0 - - /@jest/core@29.7.0: - resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - dependencies: - '@jest/console': 29.7.0 - '@jest/reporters': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 20.11.30 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - ci-info: 3.9.0 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.11.30) - jest-haste-map: 29.7.0 - jest-message-util: 29.7.0 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-resolve-dependencies: 29.7.0 - jest-runner: 29.7.0 - jest-runtime: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - jest-watcher: 29.7.0 - micromatch: 4.0.5 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-ansi: 6.0.1 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - ts-node - - /@jest/environment@29.7.0: - resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/fake-timers': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 20.11.30 - jest-mock: 29.7.0 - - /@jest/expect-utils@29.7.0: - resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - jest-get-type: 29.6.3 - - /@jest/expect@29.7.0: - resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - expect: 29.7.0 - jest-snapshot: 29.7.0 - transitivePeerDependencies: - - supports-color - - /@jest/fake-timers@29.7.0: - resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.6.3 - '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.11.30 - jest-message-util: 29.7.0 - jest-mock: 29.7.0 - jest-util: 29.7.0 - - /@jest/globals@29.7.0: - resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/environment': 29.7.0 - '@jest/expect': 29.7.0 - '@jest/types': 29.6.3 - jest-mock: 29.7.0 - transitivePeerDependencies: - - supports-color - - /@jest/reporters@29.7.0: - resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - dependencies: - '@bcoe/v8-coverage': 0.2.3 - '@jest/console': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.25 - '@types/node': 20.11.30 - chalk: 4.1.2 - collect-v8-coverage: 1.0.2 - exit: 0.1.2 - glob: 7.2.3 - graceful-fs: 4.2.11 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-instrument: 6.0.2 - istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 4.0.1 - istanbul-reports: 3.1.7 - jest-message-util: 29.7.0 - jest-util: 29.7.0 - jest-worker: 29.7.0 - slash: 3.0.0 - string-length: 4.0.2 - strip-ansi: 6.0.1 - v8-to-istanbul: 9.2.0 - transitivePeerDependencies: - - supports-color - - /@jest/schemas@29.6.3: - resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@sinclair/typebox': 0.27.8 - - /@jest/source-map@29.6.3: - resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jridgewell/trace-mapping': 0.3.25 - callsites: 3.1.0 - graceful-fs: 4.2.11 - - /@jest/test-result@29.7.0: - resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/console': 29.7.0 - '@jest/types': 29.6.3 - '@types/istanbul-lib-coverage': 2.0.6 - collect-v8-coverage: 1.0.2 - - /@jest/test-sequencer@29.7.0: - resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/test-result': 29.7.0 - graceful-fs: 4.2.11 - jest-haste-map: 29.7.0 - slash: 3.0.0 - - /@jest/transform@29.7.0: - resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@babel/core': 7.24.3 - '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.25 - babel-plugin-istanbul: 6.1.1 - chalk: 4.1.2 - convert-source-map: 2.0.0 - fast-json-stable-stringify: 2.1.0 - graceful-fs: 4.2.11 - jest-haste-map: 29.7.0 - jest-regex-util: 29.6.3 - jest-util: 29.7.0 - micromatch: 4.0.5 - pirates: 4.0.6 - slash: 3.0.0 - write-file-atomic: 4.0.2 - transitivePeerDependencies: - - supports-color - - /@jest/types@29.6.3: - resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/schemas': 29.6.3 - '@types/istanbul-lib-coverage': 2.0.6 - '@types/istanbul-reports': 3.0.4 - '@types/node': 20.11.30 - '@types/yargs': 17.0.32 - chalk: 4.1.2 - - /@jridgewell/gen-mapping@0.3.5: - resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} - engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.4.15 - '@jridgewell/trace-mapping': 0.3.25 - - /@jridgewell/resolve-uri@3.1.2: - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - /@jridgewell/set-array@1.2.1: - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} - engines: {node: '>=6.0.0'} - - /@jridgewell/source-map@0.3.6: - resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} - dependencies: - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 - dev: true - - /@jridgewell/sourcemap-codec@1.4.15: - resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - - /@jridgewell/trace-mapping@0.3.25: - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.4.15 - - /@jridgewell/trace-mapping@0.3.9: - resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.4.15 - dev: true - - /@juggle/resize-observer@3.4.0: - resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==} - dev: false - - /@lokesh.dhakar/quantize@1.3.0: - resolution: {integrity: sha512-4KBSyaMj65d8A+2vnzLxtHFu4OmBU4IKO0yLxZ171Itdf9jGV4w+WbG7VsKts2jUdRkFSzsZqpZOz6hTB3qGAw==} - dev: false - - /@mermaid-js/parser@0.3.0: - resolution: {integrity: sha512-HsvL6zgE5sUPGgkIDlmAWR1HTNHz2Iy11BAWPTa4Jjabkpguy4Ze2gzfLrg6pdRuBvFwgUYyxiaNqZwrEEXepA==} - dependencies: - langium: 3.0.0 - dev: false - - /@mui/base@5.0.0-beta.40(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.24.1 - '@floating-ui/react-dom': 2.1.0(react-dom@18.2.0)(react@18.2.0) - '@mui/types': 7.2.14(@types/react@18.2.66) - '@mui/utils': 5.15.14(@types/react@18.2.66)(react@18.2.0) - '@popperjs/core': 2.11.8 - '@types/react': 18.2.66 - clsx: 2.1.1 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@mui/base@5.0.0-beta.42(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-fWRiUJVCHCPF+mxd5drn08bY2qRw3jj5f1SSQdUXmaJ/yKpk23ys8MgLO2KGVTRtbks/+ctRfgffGPbXifj0Ug==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.24.6 - '@floating-ui/react-dom': 2.1.0(react-dom@18.2.0)(react@18.2.0) - '@mui/types': 7.2.14(@types/react@18.2.66) - '@mui/utils': 6.0.0-alpha.8(@types/react@18.2.66)(react@18.2.0) - '@popperjs/core': 2.11.8 - '@types/react': 18.2.66 - clsx: 2.1.1 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@mui/core-downloads-tracker@6.0.0-dev.240424162023-9968b4889d: - resolution: {integrity: sha512-doh3M3U7HUGSBIWGe1yvesSbfDguMRjP0N09ogWSBM2hovXAlgULhMgcRTepAZLLwfRxFII0bCohq6B9NqoKuw==} - dev: false - - /@mui/icons-material@5.15.18(@mui/material@6.0.0-alpha.2)(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-jGhyw02TSLM0NgW+MDQRLLRUD/K4eN9rlK2pTBTL1OtzyZmQ8nB060zK1wA0b7cVrIiG+zyrRmNAvGWXwm2N9Q==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@mui/material': ^5.0.0 - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.24.1 - '@mui/material': 6.0.0-alpha.2(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@types/react': 18.2.66 - react: 18.2.0 - dev: false - - /@mui/material@6.0.0-alpha.2(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-p1GpE1a7dQTns0yp0anSNX/Bh1xafTdUCt0roTyqEuL/3hCBKTURE/9/CDttwwQ+Q8oDm5KcsdtXJXJh1ts6Kw==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@emotion/react': ^11.5.0 - '@emotion/styled': ^11.3.0 - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.24.6 - '@emotion/react': 11.11.4(@types/react@18.2.66)(react@18.2.0) - '@emotion/styled': 11.11.5(@emotion/react@11.11.4)(@types/react@18.2.66)(react@18.2.0) - '@mui/base': 5.0.0-beta.42(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@mui/core-downloads-tracker': 6.0.0-dev.240424162023-9968b4889d - '@mui/system': 6.0.0-dev.240424162023-9968b4889d(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@types/react@18.2.66)(react@18.2.0) - '@mui/types': 7.2.14(@types/react@18.2.66) - '@mui/utils': 6.0.0-alpha.8(@types/react@18.2.66)(react@18.2.0) - '@types/react': 18.2.66 - '@types/react-transition-group': 4.4.10 - clsx: 2.1.1 - csstype: 3.1.3 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-is: 18.2.0 - react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0) - dev: false - - /@mui/private-theming@5.15.14(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-UH0EiZckOWcxiXLX3Jbb0K7rC8mxTr9L9l6QhOZxYc4r8FHUkefltV9VDGLrzCaWh30SQiJvAEd7djX3XXY6Xw==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.26.0 - '@mui/utils': 5.15.14(@types/react@18.2.66)(react@18.2.0) - '@types/react': 18.2.66 - prop-types: 15.8.1 - react: 18.2.0 - dev: false - - /@mui/private-theming@6.0.0-alpha.8(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-0iN+hK/OZTaiVfjFYDgWEc/frRB7Z1hfBsSJBniM4KPZnrdeHIArP+3TdYzRT0avh30O2KNkBNk0GG95BnUVEg==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.26.0 - '@mui/utils': 6.2.1(@types/react@18.2.66)(react@18.2.0) - '@types/react': 18.2.66 - prop-types: 15.8.1 - react: 18.2.0 - dev: false - - /@mui/styled-engine@5.15.14(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(react@18.2.0): - resolution: {integrity: sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@emotion/react': ^11.4.1 - '@emotion/styled': ^11.3.0 - react: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - dependencies: - '@babel/runtime': 7.26.0 - '@emotion/cache': 11.14.0 - '@emotion/react': 11.11.4(@types/react@18.2.66)(react@18.2.0) - '@emotion/styled': 11.11.5(@emotion/react@11.11.4)(@types/react@18.2.66)(react@18.2.0) - csstype: 3.1.3 - prop-types: 15.8.1 - react: 18.2.0 - dev: false - - /@mui/styled-engine@6.0.0-alpha.8(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(react@18.2.0): - resolution: {integrity: sha512-7zJYgbjZRQpGN1SGmLDOgRpJZB26JjPSeqml5m+jA4wAsIONm2im+GHfki4nE3ay0uj1S555OMeNpaQ+sG9LkA==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@emotion/react': ^11.4.1 - '@emotion/styled': ^11.3.0 - react: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - dependencies: - '@babel/runtime': 7.26.0 - '@emotion/cache': 11.14.0 - '@emotion/react': 11.11.4(@types/react@18.2.66)(react@18.2.0) - '@emotion/styled': 11.11.5(@emotion/react@11.11.4)(@types/react@18.2.66)(react@18.2.0) - csstype: 3.1.3 - prop-types: 15.8.1 - react: 18.2.0 - dev: false - - /@mui/system@5.15.15(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-aulox6N1dnu5PABsfxVGOZffDVmlxPOVgj56HrUnJE8MCSh8lOvvkd47cebIVQQYAjpwieXQXiDPj5pwM40jTQ==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@emotion/react': ^11.5.0 - '@emotion/styled': ^11.3.0 - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.26.0 - '@emotion/react': 11.11.4(@types/react@18.2.66)(react@18.2.0) - '@emotion/styled': 11.11.5(@emotion/react@11.11.4)(@types/react@18.2.66)(react@18.2.0) - '@mui/private-theming': 5.15.14(@types/react@18.2.66)(react@18.2.0) - '@mui/styled-engine': 5.15.14(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(react@18.2.0) - '@mui/types': 7.2.20(@types/react@18.2.66) - '@mui/utils': 5.15.14(@types/react@18.2.66)(react@18.2.0) - '@types/react': 18.2.66 - clsx: 2.1.1 - csstype: 3.1.3 - prop-types: 15.8.1 - react: 18.2.0 - dev: false - - /@mui/system@6.0.0-dev.240424162023-9968b4889d(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-Y3yCFUHN1xMK62hJJBqzZb1YQvHNaHc7JUX01eU6QTPojtIbGMF2jCOP/EQw77/byahNbxeLoAIQx10F0IR3Rw==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@emotion/react': ^11.5.0 - '@emotion/styled': ^11.3.0 - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.24.6 - '@emotion/react': 11.11.4(@types/react@18.2.66)(react@18.2.0) - '@emotion/styled': 11.11.5(@emotion/react@11.11.4)(@types/react@18.2.66)(react@18.2.0) - '@mui/private-theming': 6.0.0-alpha.8(@types/react@18.2.66)(react@18.2.0) - '@mui/styled-engine': 6.0.0-alpha.8(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(react@18.2.0) - '@mui/types': 7.2.14(@types/react@18.2.66) - '@mui/utils': 6.0.0-alpha.8(@types/react@18.2.66)(react@18.2.0) - '@types/react': 18.2.66 - clsx: 2.1.1 - csstype: 3.1.3 - prop-types: 15.8.1 - react: 18.2.0 - dev: false - - /@mui/types@7.2.14(@types/react@18.2.66): - resolution: {integrity: sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ==} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@types/react': 18.2.66 - dev: false - - /@mui/types@7.2.20(@types/react@18.2.66): - resolution: {integrity: sha512-straFHD7L8v05l/N5vcWk+y7eL9JF0C2mtph/y4BPm3gn2Eh61dDwDB65pa8DLss3WJfDXYC7Kx5yjP0EmXpgw==} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@types/react': 18.2.66 - dev: false - - /@mui/utils@5.15.14(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-0lF/7Hh/ezDv5X7Pry6enMsbYyGKjADzvHyo3Qrc/SSlTsQ1VkbDMbH0m2t3OR5iIVLwMoxwM7yGd+6FCMtTFA==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.24.1 - '@types/prop-types': 15.7.12 - '@types/react': 18.2.66 - prop-types: 15.8.1 - react: 18.2.0 - react-is: 18.2.0 - dev: false - - /@mui/utils@6.0.0-alpha.8(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-X5lg0bh8B6uYt/0HXV+t82HXLTOVFEKcIBmIbJ5El1h9ykXaRTenr8mORxt5UC5w9DHFhkRoI8XiM5qyDuSJVw==} - engines: {node: '>=12.0.0'} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 - react: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.24.6 - '@types/prop-types': 15.7.12 - '@types/react': 18.2.66 - prop-types: 15.8.1 - react: 18.2.0 - react-is: 18.2.0 - dev: false - - /@mui/utils@6.2.1(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-ubLqGIMhKUH2TF/Um+wRzYXgAooQw35th+DPemGrTpgrZHpOgcnUDIDbwsk1e8iQiuJ3mV/ErTtcQrecmlj5cg==} - engines: {node: '>=14.0.0'} - peerDependencies: - '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 - react: ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@babel/runtime': 7.26.0 - '@mui/types': 7.2.20(@types/react@18.2.66) - '@types/prop-types': 15.7.14 - '@types/react': 18.2.66 - clsx: 2.1.1 - prop-types: 15.8.1 - react: 18.2.0 - react-is: 19.0.0 - dev: false - - /@mui/x-date-pickers-pro@6.20.0(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@mui/material@6.0.0-alpha.2)(@mui/system@5.15.15)(@types/react@18.2.66)(dayjs@1.11.9)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-lXbO8xCKRvIKgu2R8EAGhYwN1BkMS9GxTeinKZg5lCGvxTrd5pErQ4xpOxYVQq7wNLphDiY7I/Xf88VekKTNLQ==} - engines: {node: '>=14.0.0'} - peerDependencies: - '@emotion/react': ^11.9.0 - '@emotion/styled': ^11.8.1 - '@mui/material': ^5.8.6 - '@mui/system': ^5.8.0 - date-fns: ^2.25.0 || ^3.2.0 - date-fns-jalali: ^2.13.0-0 - dayjs: ^1.10.7 - luxon: ^3.0.2 - moment: ^2.29.4 - moment-hijri: ^2.1.2 - moment-jalaali: ^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0 - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - date-fns: - optional: true - date-fns-jalali: - optional: true - dayjs: - optional: true - luxon: - optional: true - moment: - optional: true - moment-hijri: - optional: true - moment-jalaali: - optional: true - dependencies: - '@babel/runtime': 7.24.1 - '@emotion/react': 11.11.4(@types/react@18.2.66)(react@18.2.0) - '@emotion/styled': 11.11.5(@emotion/react@11.11.4)(@types/react@18.2.66)(react@18.2.0) - '@mui/base': 5.0.0-beta.40(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@mui/material': 6.0.0-alpha.2(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@mui/system': 5.15.15(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@types/react@18.2.66)(react@18.2.0) - '@mui/utils': 5.15.14(@types/react@18.2.66)(react@18.2.0) - '@mui/x-date-pickers': 6.20.0(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@mui/material@6.0.0-alpha.2)(@mui/system@5.15.15)(@types/react@18.2.66)(dayjs@1.11.9)(react-dom@18.2.0)(react@18.2.0) - '@mui/x-license-pro': 6.10.2(@types/react@18.2.66)(react@18.2.0) - clsx: 2.1.1 - dayjs: 1.11.9 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0) - transitivePeerDependencies: - - '@types/react' - dev: false - - /@mui/x-date-pickers@6.20.0(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@mui/material@6.0.0-alpha.2)(@mui/system@5.15.15)(@types/react@18.2.66)(dayjs@1.11.9)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-q/x3rNmPYMXnx75+3s9pQb1YDtws9y5bwxpxeB3EW88oCp33eS7bvJpeuoCA1LzW/PpVfIRhi5RCyAvrEeTL7Q==} - engines: {node: '>=14.0.0'} - peerDependencies: - '@emotion/react': ^11.9.0 - '@emotion/styled': ^11.8.1 - '@mui/material': ^5.8.6 - '@mui/system': ^5.8.0 - date-fns: ^2.25.0 || ^3.2.0 - date-fns-jalali: ^2.13.0-0 - dayjs: ^1.10.7 - luxon: ^3.0.2 - moment: ^2.29.4 - moment-hijri: ^2.1.2 - moment-jalaali: ^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0 - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@emotion/react': - optional: true - '@emotion/styled': - optional: true - date-fns: - optional: true - date-fns-jalali: - optional: true - dayjs: - optional: true - luxon: - optional: true - moment: - optional: true - moment-hijri: - optional: true - moment-jalaali: - optional: true - dependencies: - '@babel/runtime': 7.24.1 - '@emotion/react': 11.11.4(@types/react@18.2.66)(react@18.2.0) - '@emotion/styled': 11.11.5(@emotion/react@11.11.4)(@types/react@18.2.66)(react@18.2.0) - '@mui/base': 5.0.0-beta.40(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@mui/material': 6.0.0-alpha.2(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@mui/system': 5.15.15(@emotion/react@11.11.4)(@emotion/styled@11.11.5)(@types/react@18.2.66)(react@18.2.0) - '@mui/utils': 5.15.14(@types/react@18.2.66)(react@18.2.0) - '@types/react-transition-group': 4.4.10 - clsx: 2.1.1 - dayjs: 1.11.9 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0) - transitivePeerDependencies: - - '@types/react' - dev: false - - /@mui/x-license-pro@6.10.2(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-Baw3shilU+eHgU+QYKNPFUKvfS5rSyNJ98pQx02E0gKA22hWp/XAt88K1qUfUMPlkPpvg/uci6gviQSSLZkuKw==} - engines: {node: '>=14.0.0'} - peerDependencies: - react: ^17.0.0 || ^18.0.0 - dependencies: - '@babel/runtime': 7.24.1 - '@mui/utils': 5.15.14(@types/react@18.2.66)(react@18.2.0) - react: 18.2.0 - transitivePeerDependencies: - - '@types/react' - dev: false - - /@nodelib/fs.scandir@2.1.5: - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - - /@nodelib/fs.stat@2.0.5: - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - - /@nodelib/fs.walk@1.2.8: - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.17.1 - - /@parcel/watcher-android-arm64@2.5.0: - resolution: {integrity: sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: false - optional: true - - /@parcel/watcher-darwin-arm64@2.5.0: - resolution: {integrity: sha512-hyZ3TANnzGfLpRA2s/4U1kbw2ZI4qGxaRJbBH2DCSREFfubMswheh8TeiC1sGZ3z2jUf3s37P0BBlrD3sjVTUw==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - - /@parcel/watcher-darwin-x64@2.5.0: - resolution: {integrity: sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: false - optional: true - - /@parcel/watcher-freebsd-x64@2.5.0: - resolution: {integrity: sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: false - optional: true - - /@parcel/watcher-linux-arm-glibc@2.5.0: - resolution: {integrity: sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA==} - engines: {node: '>= 10.0.0'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@parcel/watcher-linux-arm-musl@2.5.0: - resolution: {integrity: sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==} - engines: {node: '>= 10.0.0'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@parcel/watcher-linux-arm64-glibc@2.5.0: - resolution: {integrity: sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@parcel/watcher-linux-arm64-musl@2.5.0: - resolution: {integrity: sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@parcel/watcher-linux-x64-glibc@2.5.0: - resolution: {integrity: sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@parcel/watcher-linux-x64-musl@2.5.0: - resolution: {integrity: sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: false - optional: true - - /@parcel/watcher-win32-arm64@2.5.0: - resolution: {integrity: sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: false - optional: true - - /@parcel/watcher-win32-ia32@2.5.0: - resolution: {integrity: sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA==} - engines: {node: '>= 10.0.0'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: false - optional: true - - /@parcel/watcher-win32-x64@2.5.0: - resolution: {integrity: sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: false - optional: true - - /@parcel/watcher@2.5.0: - resolution: {integrity: sha512-i0GV1yJnm2n3Yq1qw6QrUrd/LI9bE8WEBOTtOkpCXHHdyN3TAGgqAK/DAT05z4fq2x04cARXt2pDmjWjL92iTQ==} - engines: {node: '>= 10.0.0'} - requiresBuild: true - dependencies: - detect-libc: 1.0.3 - is-glob: 4.0.3 - micromatch: 4.0.8 - node-addon-api: 7.1.1 - optionalDependencies: - '@parcel/watcher-android-arm64': 2.5.0 - '@parcel/watcher-darwin-arm64': 2.5.0 - '@parcel/watcher-darwin-x64': 2.5.0 - '@parcel/watcher-freebsd-x64': 2.5.0 - '@parcel/watcher-linux-arm-glibc': 2.5.0 - '@parcel/watcher-linux-arm-musl': 2.5.0 - '@parcel/watcher-linux-arm64-glibc': 2.5.0 - '@parcel/watcher-linux-arm64-musl': 2.5.0 - '@parcel/watcher-linux-x64-glibc': 2.5.0 - '@parcel/watcher-linux-x64-musl': 2.5.0 - '@parcel/watcher-win32-arm64': 2.5.0 - '@parcel/watcher-win32-ia32': 2.5.0 - '@parcel/watcher-win32-x64': 2.5.0 - dev: false - optional: true - - /@pkgjs/parseargs@0.11.0: - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - requiresBuild: true - optional: true - - /@polka/url@1.0.0-next.25: - resolution: {integrity: sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==} - dev: true - - /@popperjs/core@2.11.8: - resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} - - /@radix-ui/number@1.1.0: - resolution: {integrity: sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==} - dev: false - - /@radix-ui/primitive@1.1.1: - resolution: {integrity: sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==} - dev: false - - /@radix-ui/react-arrow@1.1.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-NaVpZfmv8SKeZbn4ijN2V3jlHA9ngBG16VnIIm22nUR0Yk8KUALyBxT3KYEUnNuch9sTE8UTsS3whzBgKOL30w==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@types/react': 18.2.66 - '@types/react-dom': 18.2.22 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@radix-ui/react-collection@1.1.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-LwT3pSho9Dljg+wY2KN2mrrh6y3qELfftINERIzBUO9e0N+t0oMTyn3k9iv+ZqgrwGkRnLpNJrsMv9BZlt2yuA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-context': 1.1.1(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-slot': 1.1.1(@types/react@18.2.66)(react@18.2.0) - '@types/react': 18.2.66 - '@types/react-dom': 18.2.22 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@radix-ui/react-compose-refs@1.1.1(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@types/react': 18.2.66 - react: 18.2.0 - dev: false - - /@radix-ui/react-context@1.1.1(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@types/react': 18.2.66 - react: 18.2.0 - dev: false - - /@radix-ui/react-direction@1.1.0(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@types/react': 18.2.66 - react: 18.2.0 - dev: false - - /@radix-ui/react-dismissable-layer@1.1.3(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-use-escape-keydown': 1.1.0(@types/react@18.2.66)(react@18.2.0) - '@types/react': 18.2.66 - '@types/react-dom': 18.2.22 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@radix-ui/react-focus-guards@1.1.1(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@types/react': 18.2.66 - react: 18.2.0 - dev: false - - /@radix-ui/react-focus-scope@1.1.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.2.66)(react@18.2.0) - '@types/react': 18.2.66 - '@types/react-dom': 18.2.22 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@radix-ui/react-id@1.1.0(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.2.66)(react@18.2.0) - '@types/react': 18.2.66 - react: 18.2.0 - dev: false - - /@radix-ui/react-label@2.1.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-UUw5E4e/2+4kFMH7+YxORXGWggtY6sM8WIwh5RZchhLuUg2H1hc98Py+pr8HMz6rdaYrK2t296ZEjYLOCO5uUw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@types/react': 18.2.66 - '@types/react-dom': 18.2.22 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@radix-ui/react-popover@1.1.4(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-aUACAkXx8LaFymDma+HQVji7WhvEhpFJ7+qPz17Nf4lLZqtreGOFRiNQWQmhzp7kEWg9cOyyQJpdIMUMPc/CPw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-context': 1.1.1(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-focus-scope': 1.1.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-id': 1.1.0(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-popper': 1.2.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-portal': 1.1.3(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-slot': 1.1.1(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.2.66)(react@18.2.0) - '@types/react': 18.2.66 - '@types/react-dom': 18.2.22 - aria-hidden: 1.2.4 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-remove-scroll: 2.6.2(@types/react@18.2.66)(react@18.2.0) - dev: false - - /@radix-ui/react-popper@1.2.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@floating-ui/react-dom': 2.1.2(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-arrow': 1.1.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-context': 1.1.1(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-use-rect': 1.1.0(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-use-size': 1.1.0(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/rect': 1.1.0 - '@types/react': 18.2.66 - '@types/react-dom': 18.2.22 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@radix-ui/react-portal@1.1.3(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.2.66)(react@18.2.0) - '@types/react': 18.2.66 - '@types/react-dom': 18.2.22 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@radix-ui/react-presence@1.1.2(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.2.66)(react@18.2.0) - '@types/react': 18.2.66 - '@types/react-dom': 18.2.22 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@radix-ui/react-primitive@2.0.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/react-slot': 1.1.1(@types/react@18.2.66)(react@18.2.0) - '@types/react': 18.2.66 - '@types/react-dom': 18.2.22 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@radix-ui/react-select@2.1.4(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-pOkb2u8KgO47j/h7AylCj7dJsm69BXcjkrvTqMptFqsE2i0p8lHkfgneXKjAgPzBMivnoMyt8o4KiV4wYzDdyQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/number': 1.1.0 - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-collection': 1.1.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-context': 1.1.1(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-direction': 1.1.0(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-focus-scope': 1.1.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-id': 1.1.0(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-popper': 1.2.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-portal': 1.1.3(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-slot': 1.1.1(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-use-previous': 1.1.0(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-visually-hidden': 1.1.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@types/react': 18.2.66 - '@types/react-dom': 18.2.22 - aria-hidden: 1.2.4 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-remove-scroll: 2.6.2(@types/react@18.2.66)(react@18.2.0) - dev: false - - /@radix-ui/react-separator@1.1.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-RRiNRSrD8iUiXriq/Y5n4/3iE8HzqgLHsusUSg5jVpU2+3tqcUFPJXHDymwEypunc2sWxDUS3UC+rkZRlHedsw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@types/react': 18.2.66 - '@types/react-dom': 18.2.22 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@radix-ui/react-slot@1.1.1(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.2.66)(react@18.2.0) - '@types/react': 18.2.66 - react: 18.2.0 - dev: false - - /@radix-ui/react-switch@1.1.2(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-zGukiWHjEdBCRyXvKR6iXAQG6qXm2esuAD6kDOi9Cn+1X6ev3ASo4+CsYaD6Fov9r/AQFekqnD/7+V0Cs6/98g==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-context': 1.1.1(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-use-previous': 1.1.0(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-use-size': 1.1.0(@types/react@18.2.66)(react@18.2.0) - '@types/react': 18.2.66 - '@types/react-dom': 18.2.22 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@radix-ui/react-tooltip@1.1.6(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-TLB5D8QLExS1uDn7+wH/bjEmRurNMTzNrtq7IjaS4kjion9NtzsTGkvR5+i7yc9q01Pi2KMM2cN3f8UG4IvvXA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/primitive': 1.1.1 - '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-context': 1.1.1(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-id': 1.1.0(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-popper': 1.2.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-portal': 1.1.3(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@radix-ui/react-slot': 1.1.1(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.2.66)(react@18.2.0) - '@radix-ui/react-visually-hidden': 1.1.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@types/react': 18.2.66 - '@types/react-dom': 18.2.22 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@radix-ui/react-use-callback-ref@1.1.0(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@types/react': 18.2.66 - react: 18.2.0 - dev: false - - /@radix-ui/react-use-controllable-state@1.1.0(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.2.66)(react@18.2.0) - '@types/react': 18.2.66 - react: 18.2.0 - dev: false - - /@radix-ui/react-use-escape-keydown@1.1.0(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.2.66)(react@18.2.0) - '@types/react': 18.2.66 - react: 18.2.0 - dev: false - - /@radix-ui/react-use-layout-effect@1.1.0(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@types/react': 18.2.66 - react: 18.2.0 - dev: false - - /@radix-ui/react-use-previous@1.1.0(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-Z/e78qg2YFnnXcW88A4JmTtm4ADckLno6F7OXotmkQfeuCVaKuYzqAATPhVzl3delXE7CxIV8shofPn3jPc5Og==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@types/react': 18.2.66 - react: 18.2.0 - dev: false - - /@radix-ui/react-use-rect@1.1.0(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@radix-ui/rect': 1.1.0 - '@types/react': 18.2.66 - react: 18.2.0 - dev: false - - /@radix-ui/react-use-size@1.1.0(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-XW3/vWuIXHa+2Uwcc2ABSfcCledmXhhQPlGbfcRXbiUQI5Icjcg19BGCZVKKInYbvUCut/ufbbLLPFC5cbb1hw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.0(@types/react@18.2.66)(react@18.2.0) - '@types/react': 18.2.66 - react: 18.2.0 - dev: false - - /@radix-ui/react-visually-hidden@1.1.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-vVfA2IZ9q/J+gEamvj761Oq1FpWgCDaNOOIfbPVp2MVPLEomUr5+Vf7kJGwQ24YxZSlQVar7Bes8kyTo5Dshpg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) - '@types/react': 18.2.66 - '@types/react-dom': 18.2.22 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /@radix-ui/rect@1.1.0: - resolution: {integrity: sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==} - dev: false - - /@reduxjs/toolkit@2.0.0(react-redux@8.1.3)(react@18.2.0): - resolution: {integrity: sha512-Kq/a+aO28adYdPoNEu9p800MYPKoUc0tlkYfv035Ief9J7MPq8JvmT7UdpYhvXsoMtOdt567KwZjc9H3Rf8yjg==} - peerDependencies: - react: ^16.9.0 || ^17.0.0 || ^18 - react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 - peerDependenciesMeta: - react: - optional: true - react-redux: - optional: true - dependencies: - immer: 10.1.1 - react: 18.2.0 - react-redux: 8.1.3(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1) - redux: 5.0.1 - redux-thunk: 3.1.0(redux@5.0.1) - reselect: 5.1.0 - dev: false - - /@remix-run/router@1.16.1: - resolution: {integrity: sha512-es2g3dq6Nb07iFxGk5GuHN20RwBZOsuDQN7izWIisUcv9r+d2C5jQxqmgkdebXgReWfiyUabcki6Fg77mSNrig==} - engines: {node: '>=14.0.0'} - dev: false - - /@restart/hooks@0.4.16(react@18.2.0): - resolution: {integrity: sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==} - peerDependencies: - react: '>=16.8.0' - dependencies: - dequal: 2.0.3 - react: 18.2.0 - dev: false - - /@rollup/plugin-strip@3.0.4: - resolution: {integrity: sha512-LDRV49ZaavxUo2YoKKMQjCxzCxugu1rCPQa0lDYBOWLj6vtzBMr8DcoJjsmg+s450RbKbe3qI9ZLaSO+O1oNbg==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - dependencies: - '@rollup/pluginutils': 5.1.0 - estree-walker: 2.0.2 - magic-string: 0.30.8 - dev: true - - /@rollup/pluginutils@4.2.1: - resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} - engines: {node: '>= 8.0.0'} - dependencies: - estree-walker: 2.0.2 - picomatch: 2.3.1 - dev: true - - /@rollup/pluginutils@5.1.0: - resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - dependencies: - '@types/estree': 1.0.5 - estree-walker: 2.0.2 - picomatch: 2.3.1 - dev: true - - /@rollup/rollup-android-arm-eabi@4.13.2: - resolution: {integrity: sha512-3XFIDKWMFZrMnao1mJhnOT1h2g0169Os848NhhmGweEcfJ4rCi+3yMCOLG4zA61rbJdkcrM/DjVZm9Hg5p5w7g==} - cpu: [arm] - os: [android] - requiresBuild: true - optional: true - - /@rollup/rollup-android-arm64@4.13.2: - resolution: {integrity: sha512-GdxxXbAuM7Y/YQM9/TwwP+L0omeE/lJAR1J+olu36c3LqqZEBdsIWeQ91KBe6nxwOnb06Xh7JS2U5ooWU5/LgQ==} - cpu: [arm64] - os: [android] - requiresBuild: true - optional: true - - /@rollup/rollup-darwin-arm64@4.13.2: - resolution: {integrity: sha512-mCMlpzlBgOTdaFs83I4XRr8wNPveJiJX1RLfv4hggyIVhfB5mJfN4P8Z6yKh+oE4Luz+qq1P3kVdWrCKcMYrrA==} - cpu: [arm64] - os: [darwin] - requiresBuild: true - optional: true - - /@rollup/rollup-darwin-x64@4.13.2: - resolution: {integrity: sha512-yUoEvnH0FBef/NbB1u6d3HNGyruAKnN74LrPAfDQL3O32e3k3OSfLrPgSJmgb3PJrBZWfPyt6m4ZhAFa2nZp2A==} - cpu: [x64] - os: [darwin] - requiresBuild: true - optional: true - - /@rollup/rollup-linux-arm-gnueabihf@4.13.2: - resolution: {integrity: sha512-GYbLs5ErswU/Xs7aGXqzc3RrdEjKdmoCrgzhJWyFL0r5fL3qd1NPcDKDowDnmcoSiGJeU68/Vy+OMUluRxPiLQ==} - cpu: [arm] - os: [linux] - requiresBuild: true - optional: true - - /@rollup/rollup-linux-arm64-gnu@4.13.2: - resolution: {integrity: sha512-L1+D8/wqGnKQIlh4Zre9i4R4b4noxzH5DDciyahX4oOz62CphY7WDWqJoQ66zNR4oScLNOqQJfNSIAe/6TPUmQ==} - cpu: [arm64] - os: [linux] - requiresBuild: true - optional: true - - /@rollup/rollup-linux-arm64-musl@4.13.2: - resolution: {integrity: sha512-tK5eoKFkXdz6vjfkSTCupUzCo40xueTOiOO6PeEIadlNBkadH1wNOH8ILCPIl8by/Gmb5AGAeQOFeLev7iZDOA==} - cpu: [arm64] - os: [linux] - requiresBuild: true - optional: true - - /@rollup/rollup-linux-powerpc64le-gnu@4.13.2: - resolution: {integrity: sha512-zvXvAUGGEYi6tYhcDmb9wlOckVbuD+7z3mzInCSTACJ4DQrdSLPNUeDIcAQW39M3q6PDquqLWu7pnO39uSMRzQ==} - cpu: [ppc64le] - os: [linux] - requiresBuild: true - optional: true - - /@rollup/rollup-linux-riscv64-gnu@4.13.2: - resolution: {integrity: sha512-C3GSKvMtdudHCN5HdmAMSRYR2kkhgdOfye4w0xzyii7lebVr4riCgmM6lRiSCnJn2w1Xz7ZZzHKuLrjx5620kw==} - cpu: [riscv64] - os: [linux] - requiresBuild: true - optional: true - - /@rollup/rollup-linux-s390x-gnu@4.13.2: - resolution: {integrity: sha512-l4U0KDFwzD36j7HdfJ5/TveEQ1fUTjFFQP5qIt9gBqBgu1G8/kCaq5Ok05kd5TG9F8Lltf3MoYsUMw3rNlJ0Yg==} - cpu: [s390x] - os: [linux] - requiresBuild: true - optional: true - - /@rollup/rollup-linux-x64-gnu@4.13.2: - resolution: {integrity: sha512-xXMLUAMzrtsvh3cZ448vbXqlUa7ZL8z0MwHp63K2IIID2+DeP5iWIT6g1SN7hg1VxPzqx0xZdiDM9l4n9LRU1A==} - cpu: [x64] - os: [linux] - requiresBuild: true - optional: true - - /@rollup/rollup-linux-x64-musl@4.13.2: - resolution: {integrity: sha512-M/JYAWickafUijWPai4ehrjzVPKRCyDb1SLuO+ZyPfoXgeCEAlgPkNXewFZx0zcnoIe3ay4UjXIMdXQXOZXWqA==} - cpu: [x64] - os: [linux] - requiresBuild: true - optional: true - - /@rollup/rollup-win32-arm64-msvc@4.13.2: - resolution: {integrity: sha512-2YWwoVg9KRkIKaXSh0mz3NmfurpmYoBBTAXA9qt7VXk0Xy12PoOP40EFuau+ajgALbbhi4uTj3tSG3tVseCjuA==} - cpu: [arm64] - os: [win32] - requiresBuild: true - optional: true - - /@rollup/rollup-win32-ia32-msvc@4.13.2: - resolution: {integrity: sha512-2FSsE9aQ6OWD20E498NYKEQLneShWes0NGMPQwxWOdws35qQXH+FplabOSP5zEe1pVjurSDOGEVCE2agFwSEsw==} - cpu: [ia32] - os: [win32] - requiresBuild: true - optional: true - - /@rollup/rollup-win32-x64-msvc@4.13.2: - resolution: {integrity: sha512-7h7J2nokcdPePdKykd8wtc8QqqkqxIrUz7MHj6aNr8waBRU//NLDVnNjQnqQO6fqtjrtCdftpbTuOKAyrAQETQ==} - cpu: [x64] - os: [win32] - requiresBuild: true - optional: true - - /@sinclair/typebox@0.27.8: - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - - /@sinonjs/commons@3.0.1: - resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} - dependencies: - type-detect: 4.0.8 - - /@sinonjs/fake-timers@10.3.0: - resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} - dependencies: - '@sinonjs/commons': 3.0.1 - - /@slate-yjs/core@1.0.2(slate@0.101.5)(yjs@14.0.0-1): - resolution: {integrity: sha512-X0hLFJbQu9c1ItWBaNuEn0pqcXYK76KCp8C4Gvy/VaTQVMo1VgAb2WiiJ0Je/AyuIYEPPSTNVOcyrGHwgA7e6Q==} - peerDependencies: - slate: '>=0.70.0' - yjs: ^13.5.29 - dependencies: - slate: 0.101.5 - y-protocols: 1.0.6(yjs@14.0.0-1) - yjs: 14.0.0-1 - dev: false - - /@svgr/babel-plugin-add-jsx-attribute@7.0.0(@babel/core@7.24.3): - resolution: {integrity: sha512-khWbXesWIP9v8HuKCl2NU2HNAyqpSQ/vkIl36Nbn4HIwEYSRWL0H7Gs6idJdha2DkpFDWlsqMELvoCE8lfFY6Q==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - dev: true - - /@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.24.3): - resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - dev: true - - /@svgr/babel-plugin-remove-jsx-attribute@7.0.0(@babel/core@7.24.3): - resolution: {integrity: sha512-iiZaIvb3H/c7d3TH2HBeK91uI2rMhZNwnsIrvd7ZwGLkFw6mmunOCoVnjdYua662MqGFxlN9xTq4fv9hgR4VXQ==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - dev: true - - /@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.24.3): - resolution: {integrity: sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - dev: true - - /@svgr/babel-plugin-remove-jsx-empty-expression@7.0.0(@babel/core@7.24.3): - resolution: {integrity: sha512-sQQmyo+qegBx8DfFc04PFmIO1FP1MHI1/QEpzcIcclo5OAISsOJPW76ZIs0bDyO/DBSJEa/tDa1W26pVtt0FRw==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - dev: true - - /@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.24.3): - resolution: {integrity: sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - dev: true - - /@svgr/babel-plugin-replace-jsx-attribute-value@7.0.0(@babel/core@7.24.3): - resolution: {integrity: sha512-i6MaAqIZXDOJeikJuzocByBf8zO+meLwfQ/qMHIjCcvpnfvWf82PFvredEZElErB5glQFJa2KVKk8N2xV6tRRA==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - dev: true - - /@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.24.3): - resolution: {integrity: sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - dev: true - - /@svgr/babel-plugin-svg-dynamic-title@7.0.0(@babel/core@7.24.3): - resolution: {integrity: sha512-BoVSh6ge3SLLpKC0pmmN9DFlqgFy4NxNgdZNLPNJWBUU7TQpDWeBuyVuDW88iXydb5Cv0ReC+ffa5h3VrKfk1w==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - dev: true - - /@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.24.3): - resolution: {integrity: sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - dev: true - - /@svgr/babel-plugin-svg-em-dimensions@7.0.0(@babel/core@7.24.3): - resolution: {integrity: sha512-tNDcBa+hYn0gO+GkP/AuNKdVtMufVhU9fdzu+vUQsR18RIJ9RWe7h/pSBY338RO08wArntwbDk5WhQBmhf2PaA==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - dev: true - - /@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.24.3): - resolution: {integrity: sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - dev: true - - /@svgr/babel-plugin-transform-react-native-svg@7.0.0(@babel/core@7.24.3): - resolution: {integrity: sha512-qw54u8ljCJYL2KtBOjI5z7Nzg8LnSvQOP5hPKj77H4VQL4+HdKbAT5pnkkZLmHKYwzsIHSYKXxHouD8zZamCFQ==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - dev: true - - /@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.24.3): - resolution: {integrity: sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - dev: true - - /@svgr/babel-plugin-transform-svg-component@7.0.0(@babel/core@7.24.3): - resolution: {integrity: sha512-CcFECkDj98daOg9jE3Bh3uyD9kzevCAnZ+UtzG6+BQG/jOQ2OA3jHnX6iG4G1MCJkUQFnUvEv33NvQfqrb/F3A==} - engines: {node: '>=12'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - dev: true - - /@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.24.3): - resolution: {integrity: sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==} - engines: {node: '>=12'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - dev: true - - /@svgr/babel-preset@7.0.0(@babel/core@7.24.3): - resolution: {integrity: sha512-EX/NHeFa30j5UjldQGVQikuuQNHUdGmbh9kEpBKofGUtF0GUPJ4T4rhoYiqDAOmBOxojyot36JIFiDUHUK1ilQ==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@svgr/babel-plugin-add-jsx-attribute': 7.0.0(@babel/core@7.24.3) - '@svgr/babel-plugin-remove-jsx-attribute': 7.0.0(@babel/core@7.24.3) - '@svgr/babel-plugin-remove-jsx-empty-expression': 7.0.0(@babel/core@7.24.3) - '@svgr/babel-plugin-replace-jsx-attribute-value': 7.0.0(@babel/core@7.24.3) - '@svgr/babel-plugin-svg-dynamic-title': 7.0.0(@babel/core@7.24.3) - '@svgr/babel-plugin-svg-em-dimensions': 7.0.0(@babel/core@7.24.3) - '@svgr/babel-plugin-transform-react-native-svg': 7.0.0(@babel/core@7.24.3) - '@svgr/babel-plugin-transform-svg-component': 7.0.0(@babel/core@7.24.3) - dev: true - - /@svgr/babel-preset@8.1.0(@babel/core@7.24.3): - resolution: {integrity: sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==} - engines: {node: '>=14'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.24.3 - '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.24.3) - '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.24.3) - '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.24.3) - '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.24.3) - '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.24.3) - '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.24.3) - '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.24.3) - '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.24.3) - dev: true - - /@svgr/core@7.0.0(typescript@4.9.5): - resolution: {integrity: sha512-ztAoxkaKhRVloa3XydohgQQCb0/8x9T63yXovpmHzKMkHO6pkjdsIAWKOS4bE95P/2quVh1NtjSKlMRNzSBffw==} - engines: {node: '>=14'} - dependencies: - '@babel/core': 7.24.3 - '@svgr/babel-preset': 7.0.0(@babel/core@7.24.3) - camelcase: 6.3.0 - cosmiconfig: 8.3.6(typescript@4.9.5) - transitivePeerDependencies: - - supports-color - - typescript - dev: true - - /@svgr/core@8.1.0(typescript@4.9.5): - resolution: {integrity: sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==} - engines: {node: '>=14'} - dependencies: - '@babel/core': 7.24.3 - '@svgr/babel-preset': 8.1.0(@babel/core@7.24.3) - camelcase: 6.3.0 - cosmiconfig: 8.3.6(typescript@4.9.5) - snake-case: 3.0.4 - transitivePeerDependencies: - - supports-color - - typescript - dev: true - - /@svgr/hast-util-to-babel-ast@7.0.0: - resolution: {integrity: sha512-42Ej9sDDEmsJKjrfQ1PHmiDiHagh/u9AHO9QWbeNx4KmD9yS5d1XHmXUNINfUcykAU+4431Cn+k6Vn5mWBYimQ==} - engines: {node: '>=14'} - dependencies: - '@babel/types': 7.24.0 - entities: 4.5.0 - dev: true - - /@svgr/plugin-jsx@7.0.0: - resolution: {integrity: sha512-SWlTpPQmBUtLKxXWgpv8syzqIU8XgFRvyhfkam2So8b3BE0OS0HPe5UfmlJ2KIC+a7dpuuYovPR2WAQuSyMoPw==} - engines: {node: '>=14'} - dependencies: - '@babel/core': 7.24.3 - '@svgr/babel-preset': 7.0.0(@babel/core@7.24.3) - '@svgr/hast-util-to-babel-ast': 7.0.0 - svg-parser: 2.0.4 - transitivePeerDependencies: - - supports-color - dev: true - - /@svgr/plugin-svgo@8.0.1(@svgr/core@8.1.0)(typescript@4.9.5): - resolution: {integrity: sha512-29OJ1QmJgnohQHDAgAuY2h21xWD6TZiXji+hnx+W635RiXTAlHTbjrZDktfqzkN0bOeQEtNe+xgq73/XeWFfSg==} - engines: {node: '>=14'} - peerDependencies: - '@svgr/core': '*' - dependencies: - '@svgr/core': 8.1.0(typescript@4.9.5) - cosmiconfig: 8.3.6(typescript@4.9.5) - deepmerge: 4.3.1 - svgo: 3.2.0 - transitivePeerDependencies: - - typescript - dev: true - - /@tauri-apps/api@1.5.6: - resolution: {integrity: sha512-LH5ToovAHnDVe5Qa9f/+jW28I6DeMhos8bNDtBOmmnaDpPmJmYLyHdeDblAWWWYc7KKRDg9/66vMuKyq0WIeFA==} - engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'} - dev: false - - /@tauri-apps/cli-darwin-arm64@1.5.11: - resolution: {integrity: sha512-2NLSglDb5VfvTbMtmOKWyD+oaL/e8Z/ZZGovHtUFyUSFRabdXc6cZOlcD1BhFvYkHqm+TqGaz5qtPR5UbqDs8A==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /@tauri-apps/cli-darwin-x64@1.5.11: - resolution: {integrity: sha512-/RQllHiJRH2fJOCudtZlaUIjofkHzP3zZgxi71ZUm7Fy80smU5TDfwpwOvB0wSVh0g/ciDjMArCSTo0MRvL+ag==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /@tauri-apps/cli-linux-arm-gnueabihf@1.5.11: - resolution: {integrity: sha512-IlBuBPKmMm+a5LLUEK6a21UGr9ZYd6zKuKLq6IGM4tVweQa8Sf2kP2Nqs74dMGIUrLmMs0vuqdURpykQg+z4NQ==} - engines: {node: '>= 10'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@tauri-apps/cli-linux-arm64-gnu@1.5.11: - resolution: {integrity: sha512-w+k1bNHCU/GbmXshtAhyTwqosThUDmCEFLU4Zkin1vl2fuAtQry2RN7thfcJFepblUGL/J7yh3Q/0+BCjtspKQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@tauri-apps/cli-linux-arm64-musl@1.5.11: - resolution: {integrity: sha512-PN6/dl+OfYQ/qrAy4HRAfksJ2AyWQYn2IA/2Wwpaa7SDRz2+hzwTQkvajuvy0sQ5L2WCG7ymFYRYMbpC6Hk9Pg==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@tauri-apps/cli-linux-x64-gnu@1.5.11: - resolution: {integrity: sha512-MTVXLi89Nj7Apcvjezw92m7ZqIDKT5SFKZtVPCg6RoLUBTzko/BQoXYIRWmdoz2pgkHDUHgO2OMJ8oKzzddXbw==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@tauri-apps/cli-linux-x64-musl@1.5.11: - resolution: {integrity: sha512-kwzAjqFpz7rvTs7WGZLy/a5nS5t15QKr3E9FG95MNF0exTl3d29YoAUAe1Mn0mOSrTJ9Z+vYYAcI/QdcsGBP+w==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /@tauri-apps/cli-win32-arm64-msvc@1.5.11: - resolution: {integrity: sha512-L+5NZ/rHrSUrMxjj6YpFYCXp6wHnq8c8SfDTBOX8dO8x+5283/vftb4vvuGIsLS4UwUFXFnLt3XQr44n84E67Q==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@tauri-apps/cli-win32-ia32-msvc@1.5.11: - resolution: {integrity: sha512-oVlD9IVewrY0lZzTdb71kNXkjdgMqFq+ohb67YsJb4Rf7o8A9DTlFds1XLCe3joqLMm4M+gvBKD7YnGIdxQ9vA==} - engines: {node: '>= 10'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@tauri-apps/cli-win32-x64-msvc@1.5.11: - resolution: {integrity: sha512-1CexcqUFCis5ypUIMOKllxUBrna09McbftWENgvVXMfA+SP+yPDPAVb8fIvUcdTIwR/yHJwcIucmTB4anww4vg==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /@tauri-apps/cli@1.5.11: - resolution: {integrity: sha512-B475D7phZrq5sZ3kDABH4g2mEoUIHtnIO+r4ZGAAfsjMbZCwXxR/jlMGTEL+VO3YzjpF7gQe38IzB4vLBbVppw==} - engines: {node: '>= 10'} - hasBin: true - optionalDependencies: - '@tauri-apps/cli-darwin-arm64': 1.5.11 - '@tauri-apps/cli-darwin-x64': 1.5.11 - '@tauri-apps/cli-linux-arm-gnueabihf': 1.5.11 - '@tauri-apps/cli-linux-arm64-gnu': 1.5.11 - '@tauri-apps/cli-linux-arm64-musl': 1.5.11 - '@tauri-apps/cli-linux-x64-gnu': 1.5.11 - '@tauri-apps/cli-linux-x64-musl': 1.5.11 - '@tauri-apps/cli-win32-arm64-msvc': 1.5.11 - '@tauri-apps/cli-win32-ia32-msvc': 1.5.11 - '@tauri-apps/cli-win32-x64-msvc': 1.5.11 - dev: true - - /@testing-library/dom@10.1.0: - resolution: {integrity: sha512-wdsYKy5zupPyLCW2Je5DLHSxSfbIp6h80WoHOQc+RPtmPGA52O9x5MJEkv92Sjonpq+poOAtUKhh1kBGAXBrNA==} - engines: {node: '>=18'} - dependencies: - '@babel/code-frame': 7.24.7 - '@babel/runtime': 7.26.0 - '@types/aria-query': 5.0.4 - aria-query: 5.3.0 - chalk: 4.1.2 - dom-accessibility-api: 0.5.16 - lz-string: 1.5.0 - pretty-format: 27.5.1 - dev: true - - /@testing-library/react@16.0.0(@testing-library/dom@10.1.0)(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-guuxUKRWQ+FgNX0h0NS0FIq3Q3uLtWVpBzcLOggmfMoUpgBnzBzvLLd4fbm6yS8ydJd94cIfY4yP9qUQjM2KwQ==} - engines: {node: '>=18'} - peerDependencies: - '@testing-library/dom': ^10.0.0 - '@types/react': ^18.0.0 - '@types/react-dom': ^18.0.0 - react: ^18.0.0 - react-dom: ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - dependencies: - '@babel/runtime': 7.24.6 - '@testing-library/dom': 10.1.0 - '@types/react': 18.2.66 - '@types/react-dom': 18.2.22 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: true - - /@tootallnate/once@2.0.0: - resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} - engines: {node: '>= 10'} - dev: true - - /@trysound/sax@0.2.0: - resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} - engines: {node: '>=10.13.0'} - dev: true - - /@tsconfig/node10@1.0.11: - resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} - dev: true - - /@tsconfig/node12@1.0.11: - resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} - dev: true - - /@tsconfig/node14@1.0.3: - resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} - dev: true - - /@tsconfig/node16@1.0.4: - resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - dev: true - - /@types/aria-query@5.0.4: - resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} - dev: true - - /@types/babel__core@7.20.5: - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - dependencies: - '@babel/parser': 7.24.1 - '@babel/types': 7.24.0 - '@types/babel__generator': 7.6.8 - '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.20.5 - - /@types/babel__generator@7.6.8: - resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} - dependencies: - '@babel/types': 7.24.0 - - /@types/babel__template@7.4.4: - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - dependencies: - '@babel/parser': 7.24.1 - '@babel/types': 7.24.0 - - /@types/babel__traverse@7.20.5: - resolution: {integrity: sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==} - dependencies: - '@babel/types': 7.24.0 - - /@types/cypress-image-snapshot@3.1.9: - resolution: {integrity: sha512-vcFgB8AfuM0dtupCHAGgapUbjRNWmMDtpzzpXz5gexWOHW4y/n10XRi5/y5Xpi0Ztku+X4dnRVMqQa5IDcy3Ig==} - dev: true - - /@types/d3-array@3.2.1: - resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==} - dev: false - - /@types/d3-axis@3.0.6: - resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} - dependencies: - '@types/d3-selection': 3.0.11 - dev: false - - /@types/d3-brush@3.0.6: - resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} - dependencies: - '@types/d3-selection': 3.0.11 - dev: false - - /@types/d3-chord@3.0.6: - resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} - dev: false - - /@types/d3-color@3.1.3: - resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} - dev: false - - /@types/d3-contour@3.0.6: - resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} - dependencies: - '@types/d3-array': 3.2.1 - '@types/geojson': 7946.0.14 - dev: false - - /@types/d3-delaunay@6.0.4: - resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} - dev: false - - /@types/d3-dispatch@3.0.6: - resolution: {integrity: sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==} - dev: false - - /@types/d3-drag@3.0.7: - resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} - dependencies: - '@types/d3-selection': 3.0.11 - dev: false - - /@types/d3-dsv@3.0.7: - resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} - dev: false - - /@types/d3-ease@3.0.2: - resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} - dev: false - - /@types/d3-fetch@3.0.7: - resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} - dependencies: - '@types/d3-dsv': 3.0.7 - dev: false - - /@types/d3-force@3.0.10: - resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} - dev: false - - /@types/d3-format@3.0.4: - resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} - dev: false - - /@types/d3-geo@3.1.0: - resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} - dependencies: - '@types/geojson': 7946.0.14 - dev: false - - /@types/d3-hierarchy@3.1.7: - resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} - dev: false - - /@types/d3-interpolate@3.0.4: - resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} - dependencies: - '@types/d3-color': 3.1.3 - dev: false - - /@types/d3-path@3.1.0: - resolution: {integrity: sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==} - dev: false - - /@types/d3-polygon@3.0.2: - resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==} - dev: false - - /@types/d3-quadtree@3.0.6: - resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} - dev: false - - /@types/d3-random@3.0.3: - resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==} - dev: false - - /@types/d3-scale-chromatic@3.1.0: - resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} - dev: false - - /@types/d3-scale@4.0.8: - resolution: {integrity: sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==} - dependencies: - '@types/d3-time': 3.0.4 - dev: false - - /@types/d3-selection@3.0.11: - resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} - dev: false - - /@types/d3-shape@3.1.6: - resolution: {integrity: sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==} - dependencies: - '@types/d3-path': 3.1.0 - dev: false - - /@types/d3-time-format@4.0.3: - resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} - dev: false - - /@types/d3-time@3.0.4: - resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} - dev: false - - /@types/d3-timer@3.0.2: - resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} - dev: false - - /@types/d3-transition@3.0.9: - resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} - dependencies: - '@types/d3-selection': 3.0.11 - dev: false - - /@types/d3-zoom@3.0.8: - resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} - dependencies: - '@types/d3-interpolate': 3.0.4 - '@types/d3-selection': 3.0.11 - dev: false - - /@types/d3@7.4.3: - resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} - dependencies: - '@types/d3-array': 3.2.1 - '@types/d3-axis': 3.0.6 - '@types/d3-brush': 3.0.6 - '@types/d3-chord': 3.0.6 - '@types/d3-color': 3.1.3 - '@types/d3-contour': 3.0.6 - '@types/d3-delaunay': 6.0.4 - '@types/d3-dispatch': 3.0.6 - '@types/d3-drag': 3.0.7 - '@types/d3-dsv': 3.0.7 - '@types/d3-ease': 3.0.2 - '@types/d3-fetch': 3.0.7 - '@types/d3-force': 3.0.10 - '@types/d3-format': 3.0.4 - '@types/d3-geo': 3.1.0 - '@types/d3-hierarchy': 3.1.7 - '@types/d3-interpolate': 3.0.4 - '@types/d3-path': 3.1.0 - '@types/d3-polygon': 3.0.2 - '@types/d3-quadtree': 3.0.6 - '@types/d3-random': 3.0.3 - '@types/d3-scale': 4.0.8 - '@types/d3-scale-chromatic': 3.1.0 - '@types/d3-selection': 3.0.11 - '@types/d3-shape': 3.1.6 - '@types/d3-time': 3.0.4 - '@types/d3-time-format': 4.0.3 - '@types/d3-timer': 3.0.2 - '@types/d3-transition': 3.0.9 - '@types/d3-zoom': 3.0.8 - dev: false - - /@types/date-arithmetic@4.1.4: - resolution: {integrity: sha512-p9eZ2X9B80iKiTW4ukVj8B4K6q9/+xFtQ5MGYA5HWToY9nL4EkhV9+6ftT2VHpVMEZb5Tv00Iel516bVdO+yRw==} - dev: true - - /@types/debug@4.1.12: - resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} - dependencies: - '@types/ms': 0.7.34 - dev: false - - /@types/dompurify@3.0.5: - resolution: {integrity: sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==} - dependencies: - '@types/trusted-types': 2.0.7 - dev: true - - /@types/eslint-scope@3.7.7: - resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} - dependencies: - '@types/eslint': 8.56.10 - '@types/estree': 1.0.5 - dev: true - - /@types/eslint@8.56.10: - resolution: {integrity: sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==} - dependencies: - '@types/estree': 1.0.5 - '@types/json-schema': 7.0.15 - dev: true - - /@types/estree@1.0.5: - resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - - /@types/geojson@7946.0.14: - resolution: {integrity: sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==} - dev: false - - /@types/google-protobuf@3.15.12: - resolution: {integrity: sha512-40um9QqwHjRS92qnOaDpL7RmDK15NuZYo9HihiJRbYkMQZlWnuH8AdvbMy8/o6lgLmKbDUKa+OALCltHdbOTpQ==} - dev: true - - /@types/graceful-fs@4.1.9: - resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} - dependencies: - '@types/node': 20.11.30 - - /@types/hast@3.0.4: - resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} - dependencies: - '@types/unist': 3.0.3 - dev: false - - /@types/hoist-non-react-statics@3.3.5: - resolution: {integrity: sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==} - dependencies: - '@types/react': 18.2.66 - hoist-non-react-statics: 3.3.2 - dev: false - - /@types/is-hotkey@0.1.10: - resolution: {integrity: sha512-RvC8KMw5BCac1NvRRyaHgMMEtBaZ6wh0pyPTBu7izn4Sj/AX9Y4aXU5c7rX8PnM/knsuUpC1IeoBkANtxBypsQ==} - dev: false - - /@types/is-hotkey@0.1.7: - resolution: {integrity: sha512-yB5C7zcOM7idwYZZ1wKQ3pTfjA9BbvFqRWvKB46GFddxnJtHwi/b9y84ykQtxQPg5qhdpg4Q/kWU3EGoCTmLzQ==} - dev: true - - /@types/istanbul-lib-coverage@2.0.6: - resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} - - /@types/istanbul-lib-report@3.0.3: - resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} - dependencies: - '@types/istanbul-lib-coverage': 2.0.6 - - /@types/istanbul-reports@3.0.4: - resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} - dependencies: - '@types/istanbul-lib-report': 3.0.3 - - /@types/jest@29.5.3: - resolution: {integrity: sha512-1Nq7YrO/vJE/FYnqYyw0FS8LdrjExSgIiHyKg7xPpn+yi8Q4huZryKnkJatN1ZRH89Kw2v33/8ZMB7DuZeSLlA==} - dependencies: - expect: 29.7.0 - pretty-format: 29.7.0 - dev: true - - /@types/jsdom@20.0.1: - resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==} - dependencies: - '@types/node': 20.11.30 - '@types/tough-cookie': 4.0.5 - parse5: 7.1.2 - dev: true - - /@types/json-schema@7.0.15: - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - dev: true - - /@types/katex@0.16.0: - resolution: {integrity: sha512-hz+S3nV6Mym5xPbT9fnO8dDhBFQguMYpY0Ipxv06JMi1ORgnEM4M1ymWDUhUNer3ElLmT583opRo4RzxKmh9jw==} - dev: true - - /@types/lodash-es@4.17.11: - resolution: {integrity: sha512-eCw8FYAWHt2DDl77s+AMLLzPn310LKohruumpucZI4oOFJkIgnlaJcy23OKMJxx4r9PeTF13Gv6w+jqjWQaYUg==} - dependencies: - '@types/lodash': 4.17.0 - dev: true - - /@types/lodash@4.17.0: - resolution: {integrity: sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==} - - /@types/mdast@4.0.4: - resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} - dependencies: - '@types/unist': 3.0.3 - dev: false - - /@types/ms@0.7.34: - resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} - dev: false - - /@types/node@20.11.30: - resolution: {integrity: sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==} - dependencies: - undici-types: 5.26.5 - - /@types/numeral@2.0.5: - resolution: {integrity: sha512-kH8I7OSSwQu9DS9JYdFWbuvhVzvFRoCPCkGxNwoGgaPeDfEPJlcxNvEOypZhQ3XXHsGbfIuYcxcJxKUfJHnRfw==} - dev: true - - /@types/parse-json@4.0.2: - resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} - dev: false - - /@types/prismjs@1.26.0: - resolution: {integrity: sha512-ZTaqn/qSqUuAq1YwvOFQfVW1AR/oQJlLSZVustdjwI+GZ8kr0MSHBj0tsXPW1EqHubx50gtBEjbPGsdZwQwCjQ==} - dev: true - - /@types/prop-types@15.7.12: - resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} - - /@types/prop-types@15.7.14: - resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==} - dev: false - - /@types/quill@2.0.10: - resolution: {integrity: sha512-L6OHONEj2v4NRbWQOsn7j1N0SyzhRR3M4g1M6j/uuIwIsIW2ShWHhwbqNvH8hSmVktzqu0lITfdnqVOQ4qkrhA==} - dependencies: - parchment: 1.1.4 - quill-delta: 4.2.2 - dev: true - - /@types/react-beautiful-dnd@13.1.3: - resolution: {integrity: sha512-BNdmvONKtsrZq3AGrujECQrIn8cDT+fZsxBLXuX3YWY/nHfZinUFx4W88eS0rkcXzuLbXpKOsu/1WCMPMLEpPg==} - dependencies: - '@types/react': 18.2.66 - dev: true - - /@types/react-big-calendar@1.8.9: - resolution: {integrity: sha512-HIHLUxR3PzWHrFdZ00VnCMvDjAh5uzlL0vMC2b7tL3bKaAJsqq9T8h+x0GVeDbZfMfHAd1cs5tZBhVvourNJXQ==} - dependencies: - '@types/date-arithmetic': 4.1.4 - '@types/prop-types': 15.7.12 - '@types/react': 18.2.66 - dev: true - - /@types/react-color@3.0.6: - resolution: {integrity: sha512-OzPIO5AyRmLA7PlOyISlgabpYUa3En74LP8mTMa0veCA719SvYQov4WLMsHvCgXP+L+KI9yGhYnqZafVGG0P4w==} - dependencies: - '@types/react': 18.2.66 - '@types/reactcss': 1.2.12 - dev: true - - /@types/react-custom-scrollbars@4.0.13: - resolution: {integrity: sha512-t+15reWgAE1jXlrhaZoxjuH/SQf+EG0rzAzSCzTIkSiP5CDT7KhoExNPwIa6uUxtPkjc3gdW/ry7GetLEwCfGA==} - dependencies: - '@types/react': 18.2.66 - dev: true - - /@types/react-datepicker@4.19.3(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-85F9eKWu9fGiD9r4KVVMPYAdkJJswR3Wci9PvqplmB6T+D+VbUqPeKtifg96NZ4nEhufjehW+SX4JLrEWVplWw==} - dependencies: - '@popperjs/core': 2.11.8 - '@types/react': 18.2.66 - date-fns: 2.30.0 - react-popper: 2.3.0(@popperjs/core@2.11.8)(react-dom@18.2.0)(react@18.2.0) - transitivePeerDependencies: - - react - - react-dom - dev: true - - /@types/react-dom@18.2.22: - resolution: {integrity: sha512-fHkBXPeNtfvri6gdsMYyW+dW7RXFo6Ad09nLFK0VQWR7yGLai/Cyvyj696gbwYvBnhGtevUG9cET0pmUbMtoPQ==} - dependencies: - '@types/react': 18.2.66 - - /@types/react-helmet@6.1.11: - resolution: {integrity: sha512-0QcdGLddTERotCXo3VFlUSWO3ztraw8nZ6e3zJSgG7apwV5xt+pJUS8ewPBqT4NYB1optGLprNQzFleIY84u/g==} - dependencies: - '@types/react': 18.2.66 - dev: true - - /@types/react-katex@3.0.0: - resolution: {integrity: sha512-AiHHXh71a2M7Z6z1wj6iA23SkiRF9r0neHUdu8zjU/cT3MyLxDefYHbcceKhV/gjDEZgF3YaiNHyPNtoGUjPvg==} - dependencies: - '@types/react': 18.2.66 - dev: true - - /@types/react-measure@2.0.12: - resolution: {integrity: sha512-Y6V11CH6bU7RhqrIdENPwEUZlPXhfXNGylMNnGwq5TAEs2wDoBA3kSVVM/EQ8u72sz5r9ja+7W8M8PIVcS841Q==} - dependencies: - '@types/react': 18.2.66 - dev: true - - /@types/react-redux@7.1.33: - resolution: {integrity: sha512-NF8m5AjWCkert+fosDsN3hAlHzpjSiXlVy9EgQEmLoBhaNXbmyeGs/aj5dQzKuF+/q+S7JQagorGDW8pJ28Hmg==} - dependencies: - '@types/hoist-non-react-statics': 3.3.5 - '@types/react': 18.2.66 - hoist-non-react-statics: 3.3.2 - redux: 4.2.1 - dev: false - - /@types/react-swipeable-views@0.13.5: - resolution: {integrity: sha512-ni6WjO7gBq2xB2Y/ZiRdQOgjGOxIik5ow2s7xKieDq8DxsXTdV46jJslSBVK2yoIJHf6mG3uqNTwxwgzbXRRzg==} - dependencies: - '@types/react': 18.2.66 - dev: false - - /@types/react-transition-group@4.4.10: - resolution: {integrity: sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==} - dependencies: - '@types/react': 18.2.66 - dev: false - - /@types/react-transition-group@4.4.6: - resolution: {integrity: sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==} - dependencies: - '@types/react': 18.2.66 - dev: true - - /@types/react-window@1.8.8: - resolution: {integrity: sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q==} - dependencies: - '@types/react': 18.2.66 - - /@types/react@18.2.66: - resolution: {integrity: sha512-OYTmMI4UigXeFMF/j4uv0lBBEbongSgptPrHBxqME44h9+yNov+oL6Z3ocJKo0WyXR84sQUNeyIp9MRfckvZpg==} - dependencies: - '@types/prop-types': 15.7.12 - '@types/scheduler': 0.23.0 - csstype: 3.1.3 - - /@types/reactcss@1.2.12: - resolution: {integrity: sha512-BrXUQ86/wbbFiZv8h/Q1/Q1XOsaHneYmCb/tHe9+M8XBAAUc2EHfdY0DY22ZZjVSaXr5ix7j+zsqO2eGZub8lQ==} - dependencies: - '@types/react': 18.2.66 - dev: true - - /@types/scheduler@0.23.0: - resolution: {integrity: sha512-YIoDCTH3Af6XM5VuwGG/QL/CJqga1Zm3NkU3HZ4ZHK2fRMPYP1VczsTUqtsf43PH/iJNVlPHAo2oWX7BSdB2Hw==} - - /@types/semver@7.5.8: - resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} - dev: true - - /@types/sinonjs__fake-timers@8.1.1: - resolution: {integrity: sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==} - dev: true - - /@types/sizzle@2.3.8: - resolution: {integrity: sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==} - dev: true - - /@types/stack-utils@2.0.3: - resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} - - /@types/strip-bom@3.0.0: - resolution: {integrity: sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==} - dev: true - - /@types/strip-json-comments@0.0.30: - resolution: {integrity: sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==} - dev: true - - /@types/tough-cookie@4.0.5: - resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} - dev: true - - /@types/trusted-types@2.0.7: - resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} - - /@types/unist@3.0.3: - resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - dev: false - - /@types/use-sync-external-store@0.0.3: - resolution: {integrity: sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==} - dev: false - - /@types/utf8@3.0.1: - resolution: {integrity: sha512-1EkWuw7rT3BMz2HpmcEOr/HL61mWNA6Ulr/KdbXR9AI0A55wD4Qfv8hizd8Q1DnknSIzzDvQmvvY/guvX7jjZA==} - dev: true - - /@types/uuid@9.0.1: - resolution: {integrity: sha512-rFT3ak0/2trgvp4yYZo5iKFEPsET7vKydKF+VRCxlQ9bpheehyAJH89dAkaLEq/j/RZXJIqcgsmPJKUP1Z28HA==} - dev: true - - /@types/validator@13.11.9: - resolution: {integrity: sha512-FCTsikRozryfayPuiI46QzH3fnrOoctTjvOYZkho9BTFLCOZ2rgZJHMOVgCOfttjPJcgOx52EpkY0CMfy87MIw==} - dev: true - - /@types/warning@3.0.3: - resolution: {integrity: sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==} - dev: false - - /@types/yargs-parser@21.0.3: - resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} - - /@types/yargs@17.0.32: - resolution: {integrity: sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==} - dependencies: - '@types/yargs-parser': 21.0.3 - - /@types/yauzl@2.10.3: - resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - requiresBuild: true - dependencies: - '@types/node': 20.11.30 - dev: true - optional: true - - /@typescript-eslint/eslint-plugin@7.2.0(@typescript-eslint/parser@7.2.0)(eslint@8.57.0)(typescript@4.9.5): - resolution: {integrity: sha512-mdekAHOqS9UjlmyF/LSs6AIEvfceV749GFxoBAjwAv0nkevfKHWQFDMcBZWUiIC5ft6ePWivXoS36aKQ0Cy3sw==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - '@typescript-eslint/parser': ^7.0.0 - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@eslint-community/regexpp': 4.10.0 - '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@4.9.5) - '@typescript-eslint/scope-manager': 7.2.0 - '@typescript-eslint/type-utils': 7.2.0(eslint@8.57.0)(typescript@4.9.5) - '@typescript-eslint/utils': 7.2.0(eslint@8.57.0)(typescript@4.9.5) - '@typescript-eslint/visitor-keys': 7.2.0 - debug: 4.3.4(supports-color@8.1.1) - eslint: 8.57.0 - graphemer: 1.4.0 - ignore: 5.3.1 - natural-compare: 1.4.0 - semver: 7.6.0 - ts-api-utils: 1.3.0(typescript@4.9.5) - typescript: 4.9.5 - transitivePeerDependencies: - - supports-color - dev: true - - /@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@4.9.5): - resolution: {integrity: sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/scope-manager': 7.2.0 - '@typescript-eslint/types': 7.2.0 - '@typescript-eslint/typescript-estree': 7.2.0(typescript@4.9.5) - '@typescript-eslint/visitor-keys': 7.2.0 - debug: 4.3.4(supports-color@8.1.1) - eslint: 8.57.0 - typescript: 4.9.5 - transitivePeerDependencies: - - supports-color - dev: true - - /@typescript-eslint/scope-manager@7.2.0: - resolution: {integrity: sha512-Qh976RbQM/fYtjx9hs4XkayYujB/aPwglw2choHmf3zBjB4qOywWSdt9+KLRdHubGcoSwBnXUH2sR3hkyaERRg==} - engines: {node: ^16.0.0 || >=18.0.0} - dependencies: - '@typescript-eslint/types': 7.2.0 - '@typescript-eslint/visitor-keys': 7.2.0 - dev: true - - /@typescript-eslint/type-utils@7.2.0(eslint@8.57.0)(typescript@4.9.5): - resolution: {integrity: sha512-xHi51adBHo9O9330J8GQYQwrKBqbIPJGZZVQTHHmy200hvkLZFWJIFtAG/7IYTWUyun6DE6w5InDReePJYJlJA==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/typescript-estree': 7.2.0(typescript@4.9.5) - '@typescript-eslint/utils': 7.2.0(eslint@8.57.0)(typescript@4.9.5) - debug: 4.3.4(supports-color@8.1.1) - eslint: 8.57.0 - ts-api-utils: 1.3.0(typescript@4.9.5) - typescript: 4.9.5 - transitivePeerDependencies: - - supports-color - dev: true - - /@typescript-eslint/types@7.2.0: - resolution: {integrity: sha512-XFtUHPI/abFhm4cbCDc5Ykc8npOKBSJePY3a3s+lwumt7XWJuzP5cZcfZ610MIPHjQjNsOLlYK8ASPaNG8UiyA==} - engines: {node: ^16.0.0 || >=18.0.0} - dev: true - - /@typescript-eslint/typescript-estree@7.2.0(typescript@4.9.5): - resolution: {integrity: sha512-cyxS5WQQCoBwSakpMrvMXuMDEbhOo9bNHHrNcEWis6XHx6KF518tkF1wBvKIn/tpq5ZpUYK7Bdklu8qY0MsFIA==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - '@typescript-eslint/types': 7.2.0 - '@typescript-eslint/visitor-keys': 7.2.0 - debug: 4.3.4(supports-color@8.1.1) - globby: 11.1.0 - is-glob: 4.0.3 - minimatch: 9.0.3 - semver: 7.6.0 - ts-api-utils: 1.3.0(typescript@4.9.5) - typescript: 4.9.5 - transitivePeerDependencies: - - supports-color - dev: true - - /@typescript-eslint/utils@7.2.0(eslint@8.57.0)(typescript@4.9.5): - resolution: {integrity: sha512-YfHpnMAGb1Eekpm3XRK8hcMwGLGsnT6L+7b2XyRv6ouDuJU1tZir1GS2i0+VXRatMwSI1/UfcyPe53ADkU+IuA==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^8.56.0 - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) - '@types/json-schema': 7.0.15 - '@types/semver': 7.5.8 - '@typescript-eslint/scope-manager': 7.2.0 - '@typescript-eslint/types': 7.2.0 - '@typescript-eslint/typescript-estree': 7.2.0(typescript@4.9.5) - eslint: 8.57.0 - semver: 7.6.0 - transitivePeerDependencies: - - supports-color - - typescript - dev: true - - /@typescript-eslint/visitor-keys@7.2.0: - resolution: {integrity: sha512-c6EIQRHhcpl6+tO8EMR+kjkkV+ugUNXOmeASA1rlzkd8EPIriavpWoiEz1HR/VLhbVIdhqnV6E7JZm00cBDx2A==} - engines: {node: ^16.0.0 || >=18.0.0} - dependencies: - '@typescript-eslint/types': 7.2.0 - eslint-visitor-keys: 3.4.3 - dev: true - - /@ungap/structured-clone@1.2.0: - resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - - /@vitejs/plugin-react@4.2.1(vite@5.2.0): - resolution: {integrity: sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - vite: ^4.2.0 || ^5.0.0 - dependencies: - '@babel/core': 7.24.3 - '@babel/plugin-transform-react-jsx-self': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-transform-react-jsx-source': 7.24.1(@babel/core@7.24.3) - '@types/babel__core': 7.20.5 - react-refresh: 0.14.0 - vite: 5.2.0(@types/node@20.11.30)(sass@1.77.2) - transitivePeerDependencies: - - supports-color - dev: true - - /@webassemblyjs/ast@1.12.1: - resolution: {integrity: sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==} - dependencies: - '@webassemblyjs/helper-numbers': 1.11.6 - '@webassemblyjs/helper-wasm-bytecode': 1.11.6 - dev: true - - /@webassemblyjs/floating-point-hex-parser@1.11.6: - resolution: {integrity: sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==} - dev: true - - /@webassemblyjs/helper-api-error@1.11.6: - resolution: {integrity: sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==} - dev: true - - /@webassemblyjs/helper-buffer@1.12.1: - resolution: {integrity: sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==} - dev: true - - /@webassemblyjs/helper-numbers@1.11.6: - resolution: {integrity: sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==} - dependencies: - '@webassemblyjs/floating-point-hex-parser': 1.11.6 - '@webassemblyjs/helper-api-error': 1.11.6 - '@xtuc/long': 4.2.2 - dev: true - - /@webassemblyjs/helper-wasm-bytecode@1.11.6: - resolution: {integrity: sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==} - dev: true - - /@webassemblyjs/helper-wasm-section@1.12.1: - resolution: {integrity: sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==} - dependencies: - '@webassemblyjs/ast': 1.12.1 - '@webassemblyjs/helper-buffer': 1.12.1 - '@webassemblyjs/helper-wasm-bytecode': 1.11.6 - '@webassemblyjs/wasm-gen': 1.12.1 - dev: true - - /@webassemblyjs/ieee754@1.11.6: - resolution: {integrity: sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==} - dependencies: - '@xtuc/ieee754': 1.2.0 - dev: true - - /@webassemblyjs/leb128@1.11.6: - resolution: {integrity: sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==} - dependencies: - '@xtuc/long': 4.2.2 - dev: true - - /@webassemblyjs/utf8@1.11.6: - resolution: {integrity: sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==} - dev: true - - /@webassemblyjs/wasm-edit@1.12.1: - resolution: {integrity: sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==} - dependencies: - '@webassemblyjs/ast': 1.12.1 - '@webassemblyjs/helper-buffer': 1.12.1 - '@webassemblyjs/helper-wasm-bytecode': 1.11.6 - '@webassemblyjs/helper-wasm-section': 1.12.1 - '@webassemblyjs/wasm-gen': 1.12.1 - '@webassemblyjs/wasm-opt': 1.12.1 - '@webassemblyjs/wasm-parser': 1.12.1 - '@webassemblyjs/wast-printer': 1.12.1 - dev: true - - /@webassemblyjs/wasm-gen@1.12.1: - resolution: {integrity: sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==} - dependencies: - '@webassemblyjs/ast': 1.12.1 - '@webassemblyjs/helper-wasm-bytecode': 1.11.6 - '@webassemblyjs/ieee754': 1.11.6 - '@webassemblyjs/leb128': 1.11.6 - '@webassemblyjs/utf8': 1.11.6 - dev: true - - /@webassemblyjs/wasm-opt@1.12.1: - resolution: {integrity: sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==} - dependencies: - '@webassemblyjs/ast': 1.12.1 - '@webassemblyjs/helper-buffer': 1.12.1 - '@webassemblyjs/wasm-gen': 1.12.1 - '@webassemblyjs/wasm-parser': 1.12.1 - dev: true - - /@webassemblyjs/wasm-parser@1.12.1: - resolution: {integrity: sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==} - dependencies: - '@webassemblyjs/ast': 1.12.1 - '@webassemblyjs/helper-api-error': 1.11.6 - '@webassemblyjs/helper-wasm-bytecode': 1.11.6 - '@webassemblyjs/ieee754': 1.11.6 - '@webassemblyjs/leb128': 1.11.6 - '@webassemblyjs/utf8': 1.11.6 - dev: true - - /@webassemblyjs/wast-printer@1.12.1: - resolution: {integrity: sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==} - dependencies: - '@webassemblyjs/ast': 1.12.1 - '@xtuc/long': 4.2.2 - dev: true - - /@xmldom/xmldom@0.8.10: - resolution: {integrity: sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==} - engines: {node: '>=10.0.0'} - dev: true - - /@xtuc/ieee754@1.2.0: - resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} - dev: true - - /@xtuc/long@4.2.2: - resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} - dev: true - - /abab@2.0.6: - resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} - deprecated: Use your platform's native atob() and btoa() methods instead - dev: true - - /abort-controller@3.0.0: - resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} - engines: {node: '>=6.5'} - dependencies: - event-target-shim: 5.0.1 - dev: true - - /acorn-globals@7.0.1: - resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==} - dependencies: - acorn: 8.11.3 - acorn-walk: 8.3.2 - dev: true - - /acorn-import-assertions@1.9.0(acorn@8.14.0): - resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} - deprecated: package has been renamed to acorn-import-attributes - peerDependencies: - acorn: ^8 - dependencies: - acorn: 8.14.0 - dev: true - - /acorn-jsx@5.3.2(acorn@8.11.3): - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - dependencies: - acorn: 8.11.3 - dev: true - - /acorn-node@1.8.2: - resolution: {integrity: sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A==} - dependencies: - acorn: 7.4.1 - acorn-walk: 7.2.0 - xtend: 4.0.2 - dev: true - - /acorn-walk@7.2.0: - resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==} - engines: {node: '>=0.4.0'} - dev: true - - /acorn-walk@8.3.2: - resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} - engines: {node: '>=0.4.0'} - dev: true - - /acorn@7.4.1: - resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} - engines: {node: '>=0.4.0'} - hasBin: true - dev: true - - /acorn@8.11.3: - resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==} - engines: {node: '>=0.4.0'} - hasBin: true - dev: true - - /acorn@8.14.0: - resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} - engines: {node: '>=0.4.0'} - hasBin: true - - /add-px-to-style@1.0.0: - resolution: {integrity: sha512-YMyxSlXpPjD8uWekCQGuN40lV4bnZagUwqa2m/uFv1z/tNImSk9fnXVMUI5qwME/zzI3MMQRvjZ+69zyfSSyew==} - dev: false - - /agent-base@6.0.2: - resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} - engines: {node: '>= 6.0.0'} - dependencies: - debug: 4.3.7 - transitivePeerDependencies: - - supports-color - dev: true - - /aggregate-error@3.1.0: - resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} - engines: {node: '>=8'} - dependencies: - clean-stack: 2.2.0 - indent-string: 4.0.0 - dev: true - - /ajv-formats@2.1.1(ajv@8.14.0): - resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true - dependencies: - ajv: 8.14.0 - dev: true - - /ajv-keywords@3.5.2(ajv@6.12.6): - resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} - peerDependencies: - ajv: ^6.9.1 - dependencies: - ajv: 6.12.6 - dev: true - - /ajv-keywords@5.1.0(ajv@8.14.0): - resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==} - peerDependencies: - ajv: ^8.8.2 - dependencies: - ajv: 8.14.0 - fast-deep-equal: 3.1.3 - dev: true - - /ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - - /ajv@8.14.0: - resolution: {integrity: sha512-oYs1UUtO97ZO2lJ4bwnWeQW8/zvOIQLGKcvPTsWmvc2SYgBb+upuNS5NxoLaMU4h8Ju3Nbj6Cq8mD2LQoqVKFA==} - dependencies: - fast-deep-equal: 3.1.3 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - uri-js: 4.4.1 - dev: true - - /ansi-colors@4.1.3: - resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} - engines: {node: '>=6'} - dev: true - - /ansi-escapes@4.3.2: - resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} - engines: {node: '>=8'} - dependencies: - type-fest: 0.21.3 - - /ansi-regex@2.1.1: - resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==} - engines: {node: '>=0.10.0'} - dev: true - - /ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - /ansi-regex@6.0.1: - resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} - engines: {node: '>=12'} - - /ansi-styles@2.2.1: - resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==} - engines: {node: '>=0.10.0'} - dev: true - - /ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - dependencies: - color-convert: 1.9.3 - - /ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - dependencies: - color-convert: 2.0.1 - - /ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - - /ansi-styles@6.2.1: - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} - engines: {node: '>=12'} - - /any-promise@1.3.0: - resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - dev: false - - /anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - - /app-path@3.3.0: - resolution: {integrity: sha512-EAgEXkdcxH1cgEePOSsmUtw9ItPl0KTxnh/pj9ZbhvbKbij9x0oX6PWpGnorDr0DS5AosLgoa5n3T/hZmKQpYA==} - engines: {node: '>=8'} - dependencies: - execa: 1.0.0 - dev: true - - /append-transform@2.0.0: - resolution: {integrity: sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==} - engines: {node: '>=8'} - dependencies: - default-require-extensions: 3.0.1 - dev: true - - /arch@2.2.0: - resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==} - dev: true - - /archy@1.0.0: - resolution: {integrity: sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==} - dev: true - - /arg@4.1.3: - resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} - dev: true - - /arg@5.0.2: - resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} - - /argparse@1.0.10: - resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} - dependencies: - sprintf-js: 1.0.3 - - /argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - dev: true - - /aria-hidden@1.2.4: - resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==} - engines: {node: '>=10'} - dependencies: - tslib: 2.6.2 - dev: false - - /aria-query@5.3.0: - resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} - dependencies: - dequal: 2.0.3 - dev: true - - /array-buffer-byte-length@1.0.1: - resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - is-array-buffer: 3.0.4 - dev: true - - /array-includes@3.1.8: - resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-object-atoms: 1.0.0 - get-intrinsic: 1.2.4 - is-string: 1.0.7 - dev: true - - /array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - dev: true - - /array.prototype.flat@1.3.2: - resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-shim-unscopables: 1.0.2 - dev: true - - /array.prototype.flatmap@1.3.2: - resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-shim-unscopables: 1.0.2 - dev: true - - /array.prototype.tosorted@1.1.3: - resolution: {integrity: sha512-/DdH4TiTmOKzyQbp/eadcCVexiCb36xJg7HshYOYJnNZFDj33GEv0P7GxsynpShhq4OLYJzbGcBDkLsDt7MnNg==} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-errors: 1.3.0 - es-shim-unscopables: 1.0.2 - dev: true - - /arraybuffer.prototype.slice@1.0.3: - resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} - engines: {node: '>= 0.4'} - dependencies: - array-buffer-byte-length: 1.0.1 - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-errors: 1.3.0 - get-intrinsic: 1.2.4 - is-array-buffer: 3.0.4 - is-shared-array-buffer: 1.0.3 - dev: true - - /asn1@0.2.6: - resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} - dependencies: - safer-buffer: 2.1.2 - - /assert-plus@1.0.0: - resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} - engines: {node: '>=0.8'} - - /astral-regex@2.0.0: - resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} - engines: {node: '>=8'} - dev: true - - /async-retry@1.3.3: - resolution: {integrity: sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==} - dependencies: - retry: 0.13.1 - dev: false - - /async@3.2.5: - resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==} - dev: true - - /asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - - /at-least-node@1.0.0: - resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} - engines: {node: '>= 4.0.0'} - dev: true - - /atomic-sleep@1.0.0: - resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} - engines: {node: '>=8.0.0'} - dev: true - - /autoprefixer@10.4.13(postcss@8.4.21): - resolution: {integrity: sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==} - engines: {node: ^10 || ^12 || >=14} - hasBin: true - peerDependencies: - postcss: ^8.1.0 - dependencies: - browserslist: 4.23.0 - caniuse-lite: 1.0.30001603 - fraction.js: 4.3.7 - normalize-range: 0.1.2 - picocolors: 1.0.0 - postcss: 8.4.21 - postcss-value-parser: 4.2.0 - dev: true - - /available-typed-arrays@1.0.7: - resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} - engines: {node: '>= 0.4'} - dependencies: - possible-typed-array-names: 1.0.0 - dev: true - - /aws-sign2@0.7.0: - resolution: {integrity: sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==} - - /aws4@1.12.0: - resolution: {integrity: sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==} - - /axios-mock-adapter@2.0.0(axios@1.7.2): - resolution: {integrity: sha512-D/K0J5Zm6KvaMTnsWrBQZWLzKN9GxUFZEa0mx2qeEHXDeTugCoplWehy8y36dj5vuSjhe1u/Dol8cZ8lzzmDew==} - peerDependencies: - axios: '>= 0.17.0' - dependencies: - axios: 1.7.2 - fast-deep-equal: 3.1.3 - is-buffer: 2.0.5 - dev: true - - /axios@1.7.2: - resolution: {integrity: sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==} - dependencies: - follow-redirects: 1.15.6 - form-data: 4.0.0 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - - /b4a@1.6.6: - resolution: {integrity: sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==} - dev: true - - /babel-jest@29.6.2(@babel/core@7.24.3): - resolution: {integrity: sha512-BYCzImLos6J3BH/+HvUCHG1dTf2MzmAB4jaVxHV+29RZLjR29XuYTmsf2sdDwkrb+FczkGo3kOhE7ga6sI0P4A==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@babel/core': ^7.8.0 - dependencies: - '@babel/core': 7.24.3 - '@jest/transform': 29.7.0 - '@types/babel__core': 7.20.5 - babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.24.3) - chalk: 4.1.2 - graceful-fs: 4.2.11 - slash: 3.0.0 - transitivePeerDependencies: - - supports-color - dev: true - - /babel-jest@29.7.0(@babel/core@7.24.3): - resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@babel/core': ^7.8.0 - dependencies: - '@babel/core': 7.24.3 - '@jest/transform': 29.7.0 - '@types/babel__core': 7.20.5 - babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.24.3) - chalk: 4.1.2 - graceful-fs: 4.2.11 - slash: 3.0.0 - transitivePeerDependencies: - - supports-color - - /babel-loader@9.1.3(@babel/core@7.24.3)(webpack@5.91.0): - resolution: {integrity: sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==} - engines: {node: '>= 14.15.0'} - peerDependencies: - '@babel/core': ^7.12.0 - webpack: '>=5' - dependencies: - '@babel/core': 7.24.3 - find-cache-dir: 4.0.0 - schema-utils: 4.2.0 - webpack: 5.91.0 - dev: true - - /babel-plugin-import@1.13.8: - resolution: {integrity: sha512-36babpjra5m3gca44V6tSTomeBlPA7cHUynrE2WiQIm3rEGD9xy28MKsx5IdO45EbnpJY7Jrgd00C6Dwt/l/2Q==} - dependencies: - '@babel/helper-module-imports': 7.24.3 - dev: true - - /babel-plugin-istanbul@6.1.1: - resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} - engines: {node: '>=8'} - dependencies: - '@babel/helper-plugin-utils': 7.24.0 - '@istanbuljs/load-nyc-config': 1.1.0 - '@istanbuljs/schema': 0.1.3 - istanbul-lib-instrument: 5.2.1 - test-exclude: 6.0.0 - transitivePeerDependencies: - - supports-color - - /babel-plugin-jest-hoist@29.6.3: - resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@babel/template': 7.24.0 - '@babel/types': 7.24.0 - '@types/babel__core': 7.20.5 - '@types/babel__traverse': 7.20.5 - - /babel-plugin-macros@3.1.0: - resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} - engines: {node: '>=10', npm: '>=6'} - dependencies: - '@babel/runtime': 7.26.0 - cosmiconfig: 7.1.0 - resolve: 1.22.8 - dev: false - - /babel-plugin-polyfill-corejs2@0.4.11(@babel/core@7.24.3): - resolution: {integrity: sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - dependencies: - '@babel/compat-data': 7.24.7 - '@babel/core': 7.24.3 - '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.24.3) - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - dev: true - - /babel-plugin-polyfill-corejs3@0.10.4(@babel/core@7.24.3): - resolution: {integrity: sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.24.3) - core-js-compat: 3.37.1 - transitivePeerDependencies: - - supports-color - dev: true - - /babel-plugin-polyfill-regenerator@0.6.2(@babel/core@7.24.3): - resolution: {integrity: sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - dependencies: - '@babel/core': 7.24.3 - '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.24.3) - transitivePeerDependencies: - - supports-color - dev: true - - /babel-preset-current-node-syntax@1.0.1(@babel/core@7.24.3): - resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.24.3 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.3) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.24.3) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.24.3) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.3) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.3) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.3) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.3) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.3) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.3) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.3) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.3) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.3) - - /babel-preset-jest@29.6.3(@babel/core@7.24.3): - resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.24.3 - babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.24.3) - - /bail@2.0.2: - resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} - dev: false - - /balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - /bare-events@2.2.2: - resolution: {integrity: sha512-h7z00dWdG0PYOQEvChhOSWvOfkIKsdZGkWr083FgN/HyoQuebSew/cgirYqh9SCuy/hRvxc5Vy6Fw8xAmYHLkQ==} - requiresBuild: true - dev: true - optional: true - - /base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - dev: true - - /bcrypt-pbkdf@1.0.2: - resolution: {integrity: sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==} - dependencies: - tweetnacl: 0.14.5 - - /binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - - /bind-event-listener@3.0.0: - resolution: {integrity: sha512-PJvH288AWQhKs2v9zyfYdPzlPqf5bXbGMmhmUIY9x4dAUGIWgomO771oBQNwJnMQSnUIXhKu6sgzpBRXTlvb8Q==} - dev: false - - /blob-util@2.0.2: - resolution: {integrity: sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==} - dev: true - - /bluebird@3.7.1: - resolution: {integrity: sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==} - dev: true - - /bluebird@3.7.2: - resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} - dev: true - - /boolbase@1.0.0: - resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - dev: true - - /brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - - /brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} - dependencies: - balanced-match: 1.0.2 - - /braces@3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} - engines: {node: '>=8'} - dependencies: - fill-range: 7.1.1 - - /braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} - dependencies: - fill-range: 7.1.1 - - /browserify-zlib@0.1.4: - resolution: {integrity: sha512-19OEpq7vWgsH6WkvkBJQDFvJS1uPcbFOQ4v9CU839dO+ZZXUZO6XpE6hNCqvlIIj+4fZvRiJ6DsAQ382GwiyTQ==} - dependencies: - pako: 0.2.9 - dev: true - - /browserslist@4.23.0: - resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - dependencies: - caniuse-lite: 1.0.30001603 - electron-to-chromium: 1.4.722 - node-releases: 2.0.14 - update-browserslist-db: 1.0.13(browserslist@4.23.0) - - /bs-logger@0.2.6: - resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} - engines: {node: '>= 6'} - dependencies: - fast-json-stable-stringify: 2.1.0 - dev: true - - /bser@2.1.1: - resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} - dependencies: - node-int64: 0.4.0 - - /buffer-crc32@0.2.13: - resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} - dev: true - - /buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - - /buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - dev: true - - /buffer@6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - dev: true - - /cachedir@2.4.0: - resolution: {integrity: sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==} - engines: {node: '>=6'} - dev: true - - /caching-transform@4.0.0: - resolution: {integrity: sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==} - engines: {node: '>=8'} - dependencies: - hasha: 5.2.2 - make-dir: 3.1.0 - package-hash: 4.0.0 - write-file-atomic: 3.0.3 - dev: true - - /call-bind@1.0.7: - resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} - engines: {node: '>= 0.4'} - dependencies: - es-define-property: 1.0.0 - es-errors: 1.3.0 - function-bind: 1.1.2 - get-intrinsic: 1.2.4 - set-function-length: 1.2.2 - - /callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - - /camel-case@4.1.2: - resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} - dependencies: - pascal-case: 3.1.2 - tslib: 2.6.2 - dev: true - - /camelcase-css@2.0.1: - resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} - engines: {node: '>= 6'} - - /camelcase@5.3.1: - resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} - engines: {node: '>=6'} - - /camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} - - /caniuse-lite@1.0.30001603: - resolution: {integrity: sha512-iL2iSS0eDILMb9n5yKQoTBim9jMZ0Yrk8g0N9K7UzYyWnfIKzXBZD5ngpM37ZcL/cv0Mli8XtVMRYMQAfFpi5Q==} - - /capital-case@1.0.4: - resolution: {integrity: sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==} - dependencies: - no-case: 3.0.4 - tslib: 2.6.2 - upper-case-first: 2.0.2 - dev: true - - /caseless@0.12.0: - resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} - - /ccount@2.0.1: - resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - dev: false - - /chalk@1.1.3: - resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==} - engines: {node: '>=0.10.0'} - dependencies: - ansi-styles: 2.2.1 - escape-string-regexp: 1.0.5 - has-ansi: 2.0.0 - strip-ansi: 3.0.1 - supports-color: 2.0.0 - dev: true - - /chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 - - /chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - - /chalk@5.3.0: - resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - dev: true - - /change-case@4.1.2: - resolution: {integrity: sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==} - dependencies: - camel-case: 4.1.2 - capital-case: 1.0.4 - constant-case: 3.0.4 - dot-case: 3.0.4 - header-case: 2.0.4 - no-case: 3.0.4 - param-case: 3.0.4 - pascal-case: 3.1.2 - path-case: 3.0.4 - sentence-case: 3.0.4 - snake-case: 3.0.4 - tslib: 2.6.2 - dev: true - - /char-regex@1.0.2: - resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} - engines: {node: '>=10'} - - /character-entities-html4@2.1.0: - resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} - dev: false - - /character-entities-legacy@3.0.0: - resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} - dev: false - - /character-entities@2.0.2: - resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} - dev: false - - /check-more-types@2.24.0: - resolution: {integrity: sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==} - engines: {node: '>= 0.8.0'} - dev: true - - /cheerio-select@2.1.0: - resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} - dependencies: - boolbase: 1.0.0 - css-select: 5.1.0 - css-what: 6.1.0 - domelementtype: 2.3.0 - domhandler: 5.0.3 - domutils: 3.1.0 - dev: true - - /cheerio@1.0.0-rc.12: - resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==} - engines: {node: '>= 6'} - dependencies: - cheerio-select: 2.1.0 - dom-serializer: 2.0.0 - domhandler: 5.0.3 - domutils: 3.1.0 - htmlparser2: 8.0.2 - parse5: 7.1.2 - parse5-htmlparser2-tree-adapter: 7.0.0 - dev: true - - /chevrotain-allstar@0.3.1(chevrotain@11.0.3): - resolution: {integrity: sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==} - peerDependencies: - chevrotain: ^11.0.0 - dependencies: - chevrotain: 11.0.3 - lodash-es: 4.17.21 - dev: false - - /chevrotain@11.0.3: - resolution: {integrity: sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==} - dependencies: - '@chevrotain/cst-dts-gen': 11.0.3 - '@chevrotain/gast': 11.0.3 - '@chevrotain/regexp-to-ast': 11.0.3 - '@chevrotain/types': 11.0.3 - '@chevrotain/utils': 11.0.3 - lodash-es: 4.17.21 - dev: false - - /chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} - dependencies: - anymatch: 3.1.3 - braces: 3.0.2 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - - /chokidar@4.0.2: - resolution: {integrity: sha512-/b57FK+bblSU+dfewfFe0rT1YjVDfOmeLQwCAuC+vwvgLkXboATqqmy+Ipux6JrF6L5joe5CBnFOw+gLWH6yKg==} - engines: {node: '>= 14.16.0'} - dependencies: - readdirp: 4.0.2 - dev: false - - /chrome-trace-event@1.0.4: - resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} - engines: {node: '>=6.0'} - dev: true - - /ci-info@3.9.0: - resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} - engines: {node: '>=8'} - - /cjs-module-lexer@1.2.3: - resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==} - - /class-variance-authority@0.7.1: - resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} - dependencies: - clsx: 2.1.1 - dev: false - - /classnames@2.5.1: - resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} - dev: false - - /clean-css@5.3.3: - resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==} - engines: {node: '>= 10.0'} - dependencies: - source-map: 0.6.1 - dev: true - - /clean-stack@2.2.0: - resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} - engines: {node: '>=6'} - dev: true - - /cli-cursor@3.1.0: - resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} - engines: {node: '>=8'} - dependencies: - restore-cursor: 3.1.0 - dev: true - - /cli-table3@0.6.4: - resolution: {integrity: sha512-Lm3L0p+/npIQWNIiyF/nAn7T5dnOwR3xNTHXYEBFBFVPXzCVNZ5lqEC/1eo/EVfpDsQ1I+TX4ORPQgp+UI0CRw==} - engines: {node: 10.* || >= 12.*} - dependencies: - string-width: 4.2.3 - optionalDependencies: - '@colors/colors': 1.5.0 - dev: true - - /cli-truncate@2.1.0: - resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} - engines: {node: '>=8'} - dependencies: - slice-ansi: 3.0.0 - string-width: 4.2.3 - dev: true - - /cliui@6.0.0: - resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 6.2.0 - dev: true - - /cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - - /clone-deep@4.0.1: - resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==} - engines: {node: '>=6'} - dependencies: - is-plain-object: 2.0.4 - kind-of: 6.0.3 - shallow-clone: 3.0.1 - dev: true - - /clone@2.1.2: - resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} - engines: {node: '>=0.8'} - dev: false - - /clsx@1.2.1: - resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} - engines: {node: '>=6'} - dev: false - - /clsx@2.1.1: - resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} - engines: {node: '>=6'} - dev: false - - /co@4.6.0: - resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} - engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - - /collect-v8-coverage@1.0.2: - resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} - - /color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - dependencies: - color-name: 1.1.3 - - /color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - dependencies: - color-name: 1.1.4 - - /color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - - /color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - /colorette@2.0.20: - resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} - dev: true - - /colorthief@2.4.0: - resolution: {integrity: sha512-0U48RGNRo5fVO+yusBwgp+d3augWSorXabnqXUu9SabEhCpCgZJEUjUTTI41OOBBYuMMxawa3177POT6qLfLeQ==} - dependencies: - '@lokesh.dhakar/quantize': 1.3.0 - get-pixels: 3.3.3 - dev: false - - /combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - dependencies: - delayed-stream: 1.0.0 - - /comma-separated-tokens@2.0.3: - resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} - dev: false - - /commander@2.20.3: - resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - dev: true - - /commander@4.1.1: - resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} - engines: {node: '>= 6'} - dev: false - - /commander@6.2.1: - resolution: {integrity: sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==} - engines: {node: '>= 6'} - dev: true - - /commander@7.2.0: - resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} - engines: {node: '>= 10'} - - /commander@8.3.0: - resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} - engines: {node: '>= 12'} - - /common-path-prefix@3.0.0: - resolution: {integrity: sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==} - dev: true - - /common-tags@1.8.2: - resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} - engines: {node: '>=4.0.0'} - dev: true - - /commondir@1.0.1: - resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} - dev: true - - /compute-scroll-into-view@3.1.0: - resolution: {integrity: sha512-rj8l8pD4bJ1nx+dAkMhV1xB5RuZEyVysfxJqB1pRchh1KVvwOv9b7CGB8ZfjTImVv2oF+sYMUkMZq6Na5Ftmbg==} - dev: false - - /concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - - /confbox@0.1.8: - resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - dev: false - - /connect-history-api-fallback@1.6.0: - resolution: {integrity: sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==} - engines: {node: '>=0.8'} - dev: true - - /consola@2.15.3: - resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} - dev: true - - /constant-case@3.0.4: - resolution: {integrity: sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==} - dependencies: - no-case: 3.0.4 - tslib: 2.6.2 - upper-case: 2.0.2 - dev: true - - /convert-source-map@1.9.0: - resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} - - /convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - - /core-js-compat@3.37.1: - resolution: {integrity: sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==} - dependencies: - browserslist: 4.23.0 - dev: true - - /core-util-is@1.0.2: - resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} - - /core-util-is@1.0.3: - resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - dev: true - - /cose-base@1.0.3: - resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} - dependencies: - layout-base: 1.0.2 - dev: false - - /cose-base@2.2.0: - resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} - dependencies: - layout-base: 2.0.1 - dev: false - - /cosmiconfig@7.1.0: - resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} - engines: {node: '>=10'} - dependencies: - '@types/parse-json': 4.0.2 - import-fresh: 3.3.0 - parse-json: 5.2.0 - path-type: 4.0.0 - yaml: 1.10.2 - dev: false - - /cosmiconfig@8.3.6(typescript@4.9.5): - resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} - engines: {node: '>=14'} - peerDependencies: - typescript: '>=4.9.5' - peerDependenciesMeta: - typescript: - optional: true - dependencies: - import-fresh: 3.3.0 - js-yaml: 4.1.0 - parse-json: 5.2.0 - path-type: 4.0.0 - typescript: 4.9.5 - dev: true - - /create-jest@29.7.0(@types/node@20.11.30): - resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - dependencies: - '@jest/types': 29.6.3 - chalk: 4.1.2 - exit: 0.1.2 - graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.11.30) - jest-util: 29.7.0 - prompts: 2.4.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - - /create-require@1.1.1: - resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - dev: true - - /cross-env@7.0.3: - resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} - engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} - hasBin: true - dependencies: - cross-spawn: 7.0.3 - dev: true - - /cross-spawn@6.0.5: - resolution: {integrity: sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==} - engines: {node: '>=4.8'} - dependencies: - nice-try: 1.0.5 - path-key: 2.0.1 - semver: 5.7.2 - shebang-command: 1.2.0 - which: 1.3.1 - dev: true - - /cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - /css-box-model@1.2.1: - resolution: {integrity: sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==} - dependencies: - tiny-invariant: 1.3.3 - dev: false - - /css-select@4.3.0: - resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} - dependencies: - boolbase: 1.0.0 - css-what: 6.1.0 - domhandler: 4.3.1 - domutils: 2.8.0 - nth-check: 2.1.1 - dev: true - - /css-select@5.1.0: - resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} - dependencies: - boolbase: 1.0.0 - css-what: 6.1.0 - domhandler: 5.0.3 - domutils: 3.1.0 - nth-check: 2.1.1 - dev: true - - /css-tree@2.2.1: - resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} - dependencies: - mdn-data: 2.0.28 - source-map-js: 1.2.1 - dev: true - - /css-tree@2.3.1: - resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} - dependencies: - mdn-data: 2.0.30 - source-map-js: 1.2.1 - dev: true - - /css-what@6.1.0: - resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} - engines: {node: '>= 6'} - dev: true - - /cssesc@3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} - hasBin: true - - /csso@5.0.5: - resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} - dependencies: - css-tree: 2.2.1 - dev: true - - /cssom@0.3.8: - resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==} - dev: true - - /cssom@0.5.0: - resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==} - dev: true - - /cssstyle@2.3.0: - resolution: {integrity: sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==} - engines: {node: '>=8'} - dependencies: - cssom: 0.3.8 - dev: true - - /csstype@3.1.3: - resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - - /cwise-compiler@1.1.3: - resolution: {integrity: sha512-WXlK/m+Di8DMMcCjcWr4i+XzcQra9eCdXIJrgh4TUgh0pIS/yJduLxS9JgefsHJ/YVLdgPtXm9r62W92MvanEQ==} - dependencies: - uniq: 1.0.1 - dev: false - - /cypress-image-snapshot@4.0.1(cypress@13.7.2)(jest@29.5.0): - resolution: {integrity: sha512-PBpnhX/XItlx3/DAk5ozsXQHUi72exybBNH5Mpqj1DVmjq+S5Jd9WE5CRa4q5q0zuMZb2V2VpXHth6MjFpgj9Q==} - engines: {node: '>=8'} - peerDependencies: - cypress: ^4.5.0 - dependencies: - chalk: 2.4.2 - cypress: 13.7.2 - fs-extra: 7.0.1 - glob: 7.2.3 - jest-image-snapshot: 4.2.0(jest@29.5.0) - pkg-dir: 3.0.0 - term-img: 4.1.0 - transitivePeerDependencies: - - jest - dev: true - - /cypress-real-events@1.13.0(cypress@13.7.2): - resolution: {integrity: sha512-LoejtK+dyZ1jaT8wGT5oASTPfsNV8/ClRp99ruN60oPj8cBJYod80iJDyNwfPAu4GCxTXOhhAv9FO65Hpwt6Hg==} - peerDependencies: - cypress: ^4.x || ^5.x || ^6.x || ^7.x || ^8.x || ^9.x || ^10.x || ^11.x || ^12.x || ^13.x - dependencies: - cypress: 13.7.2 - dev: true - - /cypress@13.7.2: - resolution: {integrity: sha512-FF5hFI5wlRIHY8urLZjJjj/YvfCBrRpglbZCLr/cYcL9MdDe0+5usa8kTIrDHthlEc9lwihbkb5dmwqBDNS2yw==} - engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0} - hasBin: true - requiresBuild: true - dependencies: - '@cypress/request': 3.0.1 - '@cypress/xvfb': 1.2.4(supports-color@8.1.1) - '@types/sinonjs__fake-timers': 8.1.1 - '@types/sizzle': 2.3.8 - arch: 2.2.0 - blob-util: 2.0.2 - bluebird: 3.7.2 - buffer: 5.7.1 - cachedir: 2.4.0 - chalk: 4.1.2 - check-more-types: 2.24.0 - cli-cursor: 3.1.0 - cli-table3: 0.6.4 - commander: 6.2.1 - common-tags: 1.8.2 - dayjs: 1.11.9 - debug: 4.3.4(supports-color@8.1.1) - enquirer: 2.4.1 - eventemitter2: 6.4.7 - execa: 4.1.0 - executable: 4.1.1 - extract-zip: 2.0.1(supports-color@8.1.1) - figures: 3.2.0 - fs-extra: 9.1.0 - getos: 3.2.1 - is-ci: 3.0.1 - is-installed-globally: 0.4.0 - lazy-ass: 1.6.0 - listr2: 3.14.0(enquirer@2.4.1) - lodash: 4.17.21 - log-symbols: 4.1.0 - minimist: 1.2.8 - ospath: 1.2.2 - pretty-bytes: 5.6.0 - process: 0.11.10 - proxy-from-env: 1.0.0 - request-progress: 3.0.0 - semver: 7.6.0 - supports-color: 8.1.1 - tmp: 0.2.3 - untildify: 4.0.0 - yauzl: 2.10.0 - dev: true - - /cytoscape-cose-bilkent@4.1.0(cytoscape@3.30.4): - resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==} - peerDependencies: - cytoscape: ^3.2.0 - dependencies: - cose-base: 1.0.3 - cytoscape: 3.30.4 - dev: false - - /cytoscape-fcose@2.2.0(cytoscape@3.30.4): - resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==} - peerDependencies: - cytoscape: ^3.2.0 - dependencies: - cose-base: 2.2.0 - cytoscape: 3.30.4 - dev: false - - /cytoscape@3.30.4: - resolution: {integrity: sha512-OxtlZwQl1WbwMmLiyPSEBuzeTIQnwZhJYYWFzZ2PhEHVFwpeaqNIkUzSiso00D98qk60l8Gwon2RP304d3BJ1A==} - engines: {node: '>=0.10'} - dev: false - - /d3-array@2.12.1: - resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} - dependencies: - internmap: 1.0.1 - dev: false - - /d3-array@3.2.4: - resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} - engines: {node: '>=12'} - dependencies: - internmap: 2.0.3 - dev: false - - /d3-axis@3.0.0: - resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} - engines: {node: '>=12'} - dev: false - - /d3-brush@3.0.0: - resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} - engines: {node: '>=12'} - dependencies: - d3-dispatch: 3.0.1 - d3-drag: 3.0.0 - d3-interpolate: 3.0.1 - d3-selection: 3.0.0 - d3-transition: 3.0.1(d3-selection@3.0.0) - dev: false - - /d3-chord@3.0.1: - resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} - engines: {node: '>=12'} - dependencies: - d3-path: 3.1.0 - dev: false - - /d3-color@3.1.0: - resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} - engines: {node: '>=12'} - dev: false - - /d3-contour@4.0.2: - resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} - engines: {node: '>=12'} - dependencies: - d3-array: 3.2.4 - dev: false - - /d3-delaunay@6.0.4: - resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} - engines: {node: '>=12'} - dependencies: - delaunator: 5.0.1 - dev: false - - /d3-dispatch@3.0.1: - resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} - engines: {node: '>=12'} - dev: false - - /d3-drag@3.0.0: - resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} - engines: {node: '>=12'} - dependencies: - d3-dispatch: 3.0.1 - d3-selection: 3.0.0 - dev: false - - /d3-dsv@3.0.1: - resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} - engines: {node: '>=12'} - hasBin: true - dependencies: - commander: 7.2.0 - iconv-lite: 0.6.3 - rw: 1.3.3 - dev: false - - /d3-ease@3.0.1: - resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} - engines: {node: '>=12'} - dev: false - - /d3-fetch@3.0.1: - resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} - engines: {node: '>=12'} - dependencies: - d3-dsv: 3.0.1 - dev: false - - /d3-force@3.0.0: - resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} - engines: {node: '>=12'} - dependencies: - d3-dispatch: 3.0.1 - d3-quadtree: 3.0.1 - d3-timer: 3.0.1 - dev: false - - /d3-format@3.1.0: - resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} - engines: {node: '>=12'} - dev: false - - /d3-geo@3.1.1: - resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} - engines: {node: '>=12'} - dependencies: - d3-array: 3.2.4 - dev: false - - /d3-hierarchy@3.1.2: - resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} - engines: {node: '>=12'} - dev: false - - /d3-interpolate@3.0.1: - resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} - engines: {node: '>=12'} - dependencies: - d3-color: 3.1.0 - dev: false - - /d3-path@1.0.9: - resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} - dev: false - - /d3-path@3.1.0: - resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} - engines: {node: '>=12'} - dev: false - - /d3-polygon@3.0.1: - resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} - engines: {node: '>=12'} - dev: false - - /d3-quadtree@3.0.1: - resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} - engines: {node: '>=12'} - dev: false - - /d3-random@3.0.1: - resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} - engines: {node: '>=12'} - dev: false - - /d3-sankey@0.12.3: - resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==} - dependencies: - d3-array: 2.12.1 - d3-shape: 1.3.7 - dev: false - - /d3-scale-chromatic@3.1.0: - resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} - engines: {node: '>=12'} - dependencies: - d3-color: 3.1.0 - d3-interpolate: 3.0.1 - dev: false - - /d3-scale@4.0.2: - resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} - engines: {node: '>=12'} - dependencies: - d3-array: 3.2.4 - d3-format: 3.1.0 - d3-interpolate: 3.0.1 - d3-time: 3.1.0 - d3-time-format: 4.1.0 - dev: false - - /d3-selection@3.0.0: - resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} - engines: {node: '>=12'} - dev: false - - /d3-shape@1.3.7: - resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} - dependencies: - d3-path: 1.0.9 - dev: false - - /d3-shape@3.2.0: - resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} - engines: {node: '>=12'} - dependencies: - d3-path: 3.1.0 - dev: false - - /d3-time-format@4.1.0: - resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} - engines: {node: '>=12'} - dependencies: - d3-time: 3.1.0 - dev: false - - /d3-time@3.1.0: - resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} - engines: {node: '>=12'} - dependencies: - d3-array: 3.2.4 - dev: false - - /d3-timer@3.0.1: - resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} - engines: {node: '>=12'} - dev: false - - /d3-transition@3.0.1(d3-selection@3.0.0): - resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} - engines: {node: '>=12'} - peerDependencies: - d3-selection: 2 - 3 - dependencies: - d3-color: 3.1.0 - d3-dispatch: 3.0.1 - d3-ease: 3.0.1 - d3-interpolate: 3.0.1 - d3-selection: 3.0.0 - d3-timer: 3.0.1 - dev: false - - /d3-zoom@3.0.0: - resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} - engines: {node: '>=12'} - dependencies: - d3-dispatch: 3.0.1 - d3-drag: 3.0.0 - d3-interpolate: 3.0.1 - d3-selection: 3.0.0 - d3-transition: 3.0.1(d3-selection@3.0.0) - dev: false - - /d3@7.9.0: - resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==} - engines: {node: '>=12'} - dependencies: - d3-array: 3.2.4 - d3-axis: 3.0.0 - d3-brush: 3.0.0 - d3-chord: 3.0.1 - d3-color: 3.1.0 - d3-contour: 4.0.2 - d3-delaunay: 6.0.4 - d3-dispatch: 3.0.1 - d3-drag: 3.0.0 - d3-dsv: 3.0.1 - d3-ease: 3.0.1 - d3-fetch: 3.0.1 - d3-force: 3.0.0 - d3-format: 3.1.0 - d3-geo: 3.1.1 - d3-hierarchy: 3.1.2 - d3-interpolate: 3.0.1 - d3-path: 3.1.0 - d3-polygon: 3.0.1 - d3-quadtree: 3.0.1 - d3-random: 3.0.1 - d3-scale: 4.0.2 - d3-scale-chromatic: 3.1.0 - d3-selection: 3.0.0 - d3-shape: 3.2.0 - d3-time: 3.1.0 - d3-time-format: 4.1.0 - d3-timer: 3.0.1 - d3-transition: 3.0.1(d3-selection@3.0.0) - d3-zoom: 3.0.0 - dev: false - - /dagre-d3-es@7.0.11: - resolution: {integrity: sha512-tvlJLyQf834SylNKax8Wkzco/1ias1OPw8DcUMDE7oUIoSEW25riQVuiu/0OWEFqT0cxHT3Pa9/D82Jr47IONw==} - dependencies: - d3: 7.9.0 - lodash-es: 4.17.21 - dev: false - - /dashdash@1.14.1: - resolution: {integrity: sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==} - engines: {node: '>=0.10'} - dependencies: - assert-plus: 1.0.0 - - /data-uri-to-buffer@0.0.3: - resolution: {integrity: sha512-Cp+jOa8QJef5nXS5hU7M1DWzXPEIoVR3kbV0dQuVGwROZg8bGf1DcCnkmajBTnvghTtSNMUdRrPjgaT6ZQucbw==} - dev: false - - /data-urls@3.0.2: - resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==} - engines: {node: '>=12'} - dependencies: - abab: 2.0.6 - whatwg-mimetype: 3.0.0 - whatwg-url: 11.0.0 - dev: true - - /data-view-buffer@1.0.1: - resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - is-data-view: 1.0.1 - dev: true - - /data-view-byte-length@1.0.1: - resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - is-data-view: 1.0.1 - dev: true - - /data-view-byte-offset@1.0.0: - resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - is-data-view: 1.0.1 - dev: true - - /date-arithmetic@4.1.0: - resolution: {integrity: sha512-QWxYLR5P/6GStZcdem+V1xoto6DMadYWpMXU82ES3/RfR3Wdwr3D0+be7mgOJ+Ov0G9D5Dmb9T17sNLQYj9XOg==} - dev: false - - /date-fns@2.30.0: - resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} - engines: {node: '>=0.11'} - dependencies: - '@babel/runtime': 7.26.0 - - /dateformat@4.6.3: - resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} - dev: true - - /dayjs@1.11.10: - resolution: {integrity: sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==} - - /dayjs@1.11.9: - resolution: {integrity: sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA==} - - /debug@3.2.7(supports-color@8.1.1): - resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.3 - supports-color: 8.1.1 - dev: true - - /debug@4.3.4(supports-color@8.1.1): - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.2 - supports-color: 8.1.1 - - /debug@4.3.7: - resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.3 - - /decamelize@1.2.0: - resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} - engines: {node: '>=0.10.0'} - dev: true - - /decimal.js@10.4.3: - resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} - - /decode-named-character-reference@1.0.2: - resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} - dependencies: - character-entities: 2.0.2 - dev: false - - /dedent@1.5.1: - resolution: {integrity: sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==} - peerDependencies: - babel-plugin-macros: ^3.1.0 - peerDependenciesMeta: - babel-plugin-macros: - optional: true - - /deep-equal@1.1.2: - resolution: {integrity: sha512-5tdhKF6DbU7iIzrIOa1AOUt39ZRm13cmL1cGEh//aqR8x9+tNfbywRf0n5FD/18OKMdo7DNEtrX2t22ZAkI+eg==} - engines: {node: '>= 0.4'} - dependencies: - is-arguments: 1.1.1 - is-date-object: 1.0.5 - is-regex: 1.1.4 - object-is: 1.1.6 - object-keys: 1.1.1 - regexp.prototype.flags: 1.5.2 - dev: false - - /deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - dev: true - - /deepmerge@4.3.1: - resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} - engines: {node: '>=0.10.0'} - - /default-require-extensions@3.0.1: - resolution: {integrity: sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==} - engines: {node: '>=8'} - dependencies: - strip-bom: 4.0.0 - dev: true - - /define-data-property@1.1.4: - resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} - engines: {node: '>= 0.4'} - dependencies: - es-define-property: 1.0.0 - es-errors: 1.3.0 - gopd: 1.0.1 - - /define-lazy-prop@2.0.0: - resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} - engines: {node: '>=8'} - dev: true - - /define-properties@1.2.1: - resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} - engines: {node: '>= 0.4'} - dependencies: - define-data-property: 1.1.4 - has-property-descriptors: 1.0.2 - object-keys: 1.1.1 - - /defined@1.0.1: - resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==} - dev: true - - /delaunator@5.0.1: - resolution: {integrity: sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==} - dependencies: - robust-predicates: 3.0.2 - dev: false - - /delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - - /dequal@2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} - engines: {node: '>=6'} - - /detect-libc@1.0.3: - resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} - engines: {node: '>=0.10'} - hasBin: true - dev: false - optional: true - - /detect-newline@3.1.0: - resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} - engines: {node: '>=8'} - - /detect-node-es@1.1.0: - resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} - dev: false - - /detective@5.2.1: - resolution: {integrity: sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw==} - engines: {node: '>=0.8.0'} - hasBin: true - dependencies: - acorn-node: 1.8.2 - defined: 1.0.1 - minimist: 1.2.8 - dev: true - - /devlop@1.1.0: - resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} - dependencies: - dequal: 2.0.3 - dev: false - - /dexie-react-hooks@1.1.7(@types/react@18.2.66)(dexie@4.0.7)(react@18.2.0): - resolution: {integrity: sha512-Lwv5W0Hk+uOW3kGnsU9GZoR1er1B7WQ5DSdonoNG+focTNeJbHW6vi6nBoX534VKI3/uwHebYzSw1fwY6a7mTw==} - peerDependencies: - '@types/react': '>=16' - dexie: ^3.2 || ^4.0.1-alpha - react: '>=16' - dependencies: - '@types/react': 18.2.66 - dexie: 4.0.7 - react: 18.2.0 - dev: false - - /dexie@4.0.7: - resolution: {integrity: sha512-M+Lo6rk4pekIfrc2T0o2tvVJwL6EAAM/B78DNfb8aaxFVoI1f8/rz5KTxuAnApkwqTSuxx7T5t0RKH7qprapGg==} - dev: false - - /didyoumean@1.2.2: - resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} - - /diff-sequences@29.6.3: - resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - /diff@4.0.2: - resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} - engines: {node: '>=0.3.1'} - dev: true - - /dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} - dependencies: - path-type: 4.0.0 - dev: true - - /direction@1.0.4: - resolution: {integrity: sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ==} - hasBin: true - dev: false - - /dlv@1.1.3: - resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} - - /doctrine@2.1.0: - resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} - engines: {node: '>=0.10.0'} - dependencies: - esutils: 2.0.3 - dev: true - - /doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} - dependencies: - esutils: 2.0.3 - dev: true - - /dom-accessibility-api@0.5.16: - resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} - dev: true - - /dom-css@2.1.0: - resolution: {integrity: sha512-w9kU7FAbaSh3QKijL6n59ofAhkkmMJ31GclJIz/vyQdjogfyxcB6Zf8CZyibOERI5o0Hxz30VmJS7+7r5fEj2Q==} - dependencies: - add-px-to-style: 1.0.0 - prefix-style: 2.0.1 - to-camel-case: 1.0.0 - dev: false - - /dom-helpers@5.2.1: - resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} - dependencies: - '@babel/runtime': 7.24.6 - csstype: 3.1.3 - dev: false - - /dom-serializer@1.4.1: - resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} - dependencies: - domelementtype: 2.3.0 - domhandler: 4.3.1 - entities: 2.2.0 - dev: true - - /dom-serializer@2.0.0: - resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} - dependencies: - domelementtype: 2.3.0 - domhandler: 5.0.3 - entities: 4.5.0 - dev: true - - /domelementtype@2.3.0: - resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} - dev: true - - /domexception@4.0.0: - resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==} - engines: {node: '>=12'} - deprecated: Use your platform's native DOMException instead - dependencies: - webidl-conversions: 7.0.0 - dev: true - - /domhandler@4.3.1: - resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} - engines: {node: '>= 4'} - dependencies: - domelementtype: 2.3.0 - dev: true - - /domhandler@5.0.3: - resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} - engines: {node: '>= 4'} - dependencies: - domelementtype: 2.3.0 - dev: true - - /dompurify@3.1.7: - resolution: {integrity: sha512-VaTstWtsneJY8xzy7DekmYWEOZcmzIe3Qb3zPd4STve1OBTa+e+WmS1ITQec1fZYXI3HCsOZZiSMpG6oxoWMWQ==} - dev: false - - /dompurify@3.2.2: - resolution: {integrity: sha512-YMM+erhdZ2nkZ4fTNRTSI94mb7VG7uVF5vj5Zde7tImgnhZE3R6YW/IACGIHb2ux+QkEXMhe591N+5jWOmL4Zw==} - optionalDependencies: - '@types/trusted-types': 2.0.7 - dev: false - - /domutils@2.8.0: - resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} - dependencies: - dom-serializer: 1.4.1 - domelementtype: 2.3.0 - domhandler: 4.3.1 - dev: true - - /domutils@3.1.0: - resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} - dependencies: - dom-serializer: 2.0.0 - domelementtype: 2.3.0 - domhandler: 5.0.3 - dev: true - - /dot-case@3.0.4: - resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} - dependencies: - no-case: 3.0.4 - tslib: 2.6.2 - dev: true - - /dotenv-expand@8.0.3: - resolution: {integrity: sha512-SErOMvge0ZUyWd5B0NXMQlDkN+8r+HhVUsxgOO7IoPDOdDRD2JjExpN6y3KnFR66jsJMwSn1pqIivhU5rcJiNg==} - engines: {node: '>=12'} - dev: true - - /dotenv@16.4.5: - resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} - engines: {node: '>=12'} - dev: true - - /duplexify@3.7.1: - resolution: {integrity: sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==} - dependencies: - end-of-stream: 1.4.4 - inherits: 2.0.4 - readable-stream: 2.3.8 - stream-shift: 1.0.3 - dev: true - - /dynamic-dedupe@0.3.0: - resolution: {integrity: sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==} - dependencies: - xtend: 4.0.2 - dev: true - - /eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - - /ecc-jsbn@0.1.2: - resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} - dependencies: - jsbn: 0.1.1 - safer-buffer: 2.1.2 - - /ejs@3.1.10: - resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} - engines: {node: '>=0.10.0'} - hasBin: true - dependencies: - jake: 10.9.2 - dev: true - - /electron-to-chromium@1.4.722: - resolution: {integrity: sha512-5nLE0TWFFpZ80Crhtp4pIp8LXCztjYX41yUcV6b+bKR2PqzjskTMOOlBi1VjBHlvHwS+4gar7kNKOrsbsewEZQ==} - - /emittery@0.13.1: - resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} - engines: {node: '>=12'} - - /emoji-mart@5.6.0: - resolution: {integrity: sha512-eJp3QRe79pjwa+duv+n7+5YsNhRcMl812EcFVwrnRvYKoNPoQb5qxU8DG6Bgwji0akHdp6D4Ln6tYLG58MFSow==} - dev: false - - /emoji-regex@10.3.0: - resolution: {integrity: sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==} - dev: false - - /emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - /emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - - /end-of-stream@1.4.4: - resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} - dependencies: - once: 1.4.0 - dev: true - - /enhanced-resolve@5.16.1: - resolution: {integrity: sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==} - engines: {node: '>=10.13.0'} - dependencies: - graceful-fs: 4.2.11 - tapable: 2.2.1 - dev: true - - /enquirer@2.4.1: - resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} - engines: {node: '>=8.6'} - dependencies: - ansi-colors: 4.1.3 - strip-ansi: 6.0.1 - dev: true - - /entities@2.2.0: - resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} - dev: true - - /entities@4.5.0: - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} - engines: {node: '>=0.12'} - - /error-ex@1.3.2: - resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - dependencies: - is-arrayish: 0.2.1 - - /es-abstract@1.23.3: - resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} - engines: {node: '>= 0.4'} - dependencies: - array-buffer-byte-length: 1.0.1 - arraybuffer.prototype.slice: 1.0.3 - available-typed-arrays: 1.0.7 - call-bind: 1.0.7 - data-view-buffer: 1.0.1 - data-view-byte-length: 1.0.1 - data-view-byte-offset: 1.0.0 - es-define-property: 1.0.0 - es-errors: 1.3.0 - es-object-atoms: 1.0.0 - es-set-tostringtag: 2.0.3 - es-to-primitive: 1.2.1 - function.prototype.name: 1.1.6 - get-intrinsic: 1.2.4 - get-symbol-description: 1.0.2 - globalthis: 1.0.3 - gopd: 1.0.1 - has-property-descriptors: 1.0.2 - has-proto: 1.0.3 - has-symbols: 1.0.3 - hasown: 2.0.2 - internal-slot: 1.0.7 - is-array-buffer: 3.0.4 - is-callable: 1.2.7 - is-data-view: 1.0.1 - is-negative-zero: 2.0.3 - is-regex: 1.1.4 - is-shared-array-buffer: 1.0.3 - is-string: 1.0.7 - is-typed-array: 1.1.13 - is-weakref: 1.0.2 - object-inspect: 1.13.1 - object-keys: 1.1.1 - object.assign: 4.1.5 - regexp.prototype.flags: 1.5.2 - safe-array-concat: 1.1.2 - safe-regex-test: 1.0.3 - string.prototype.trim: 1.2.9 - string.prototype.trimend: 1.0.8 - string.prototype.trimstart: 1.0.8 - typed-array-buffer: 1.0.2 - typed-array-byte-length: 1.0.1 - typed-array-byte-offset: 1.0.2 - typed-array-length: 1.0.6 - unbox-primitive: 1.0.2 - which-typed-array: 1.1.15 - dev: true - - /es-define-property@1.0.0: - resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} - engines: {node: '>= 0.4'} - dependencies: - get-intrinsic: 1.2.4 - - /es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - - /es-module-lexer@0.4.1: - resolution: {integrity: sha512-ooYciCUtfw6/d2w56UVeqHPcoCFAiJdz5XOkYpv/Txl1HMUozpXjz/2RIQgqwKdXNDPSF1W7mJCFse3G+HDyAA==} - dev: true - - /es-module-lexer@1.5.3: - resolution: {integrity: sha512-i1gCgmR9dCl6Vil6UKPI/trA69s08g/syhiDK9TG0Nf1RJjjFI+AzoWW7sPufzkgYAn861skuCwJa0pIIHYxvg==} - dev: true - - /es-object-atoms@1.0.0: - resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} - engines: {node: '>= 0.4'} - dependencies: - es-errors: 1.3.0 - dev: true - - /es-set-tostringtag@2.0.3: - resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} - engines: {node: '>= 0.4'} - dependencies: - get-intrinsic: 1.2.4 - has-tostringtag: 1.0.2 - hasown: 2.0.2 - dev: true - - /es-shim-unscopables@1.0.2: - resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} - dependencies: - hasown: 2.0.2 - dev: true - - /es-to-primitive@1.2.1: - resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} - engines: {node: '>= 0.4'} - dependencies: - is-callable: 1.2.7 - is-date-object: 1.0.5 - is-symbol: 1.0.4 - dev: true - - /es6-error@4.1.1: - resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} - dev: true - - /esbuild@0.20.2: - resolution: {integrity: sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==} - engines: {node: '>=12'} - hasBin: true - requiresBuild: true - optionalDependencies: - '@esbuild/aix-ppc64': 0.20.2 - '@esbuild/android-arm': 0.20.2 - '@esbuild/android-arm64': 0.20.2 - '@esbuild/android-x64': 0.20.2 - '@esbuild/darwin-arm64': 0.20.2 - '@esbuild/darwin-x64': 0.20.2 - '@esbuild/freebsd-arm64': 0.20.2 - '@esbuild/freebsd-x64': 0.20.2 - '@esbuild/linux-arm': 0.20.2 - '@esbuild/linux-arm64': 0.20.2 - '@esbuild/linux-ia32': 0.20.2 - '@esbuild/linux-loong64': 0.20.2 - '@esbuild/linux-mips64el': 0.20.2 - '@esbuild/linux-ppc64': 0.20.2 - '@esbuild/linux-riscv64': 0.20.2 - '@esbuild/linux-s390x': 0.20.2 - '@esbuild/linux-x64': 0.20.2 - '@esbuild/netbsd-x64': 0.20.2 - '@esbuild/openbsd-x64': 0.20.2 - '@esbuild/sunos-x64': 0.20.2 - '@esbuild/win32-arm64': 0.20.2 - '@esbuild/win32-ia32': 0.20.2 - '@esbuild/win32-x64': 0.20.2 - - /escalade@3.1.2: - resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} - engines: {node: '>=6'} - - /escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - - /escape-string-regexp@2.0.0: - resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} - engines: {node: '>=8'} - - /escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - - /escape-string-regexp@5.0.0: - resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} - engines: {node: '>=12'} - dev: false - - /escodegen@2.1.0: - resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} - engines: {node: '>=6.0'} - hasBin: true - dependencies: - esprima: 4.0.1 - estraverse: 5.3.0 - esutils: 2.0.3 - optionalDependencies: - source-map: 0.6.1 - dev: true - - /eslint-plugin-react-hooks@4.6.0(eslint@8.57.0): - resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==} - engines: {node: '>=10'} - peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 - dependencies: - eslint: 8.57.0 - dev: true - - /eslint-plugin-react-refresh@0.4.6(eslint@8.57.0): - resolution: {integrity: sha512-NjGXdm7zgcKRkKMua34qVO9doI7VOxZ6ancSvBELJSSoX97jyndXcSoa8XBh69JoB31dNz3EEzlMcizZl7LaMA==} - peerDependencies: - eslint: '>=7' - dependencies: - eslint: 8.57.0 - dev: true - - /eslint-plugin-react@7.32.2(eslint@8.57.0): - resolution: {integrity: sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==} - engines: {node: '>=4'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 - dependencies: - array-includes: 3.1.8 - array.prototype.flatmap: 1.3.2 - array.prototype.tosorted: 1.1.3 - doctrine: 2.1.0 - eslint: 8.57.0 - estraverse: 5.3.0 - jsx-ast-utils: 3.3.5 - minimatch: 3.1.2 - object.entries: 1.1.8 - object.fromentries: 2.0.8 - object.hasown: 1.1.4 - object.values: 1.2.0 - prop-types: 15.8.1 - resolve: 2.0.0-next.5 - semver: 6.3.1 - string.prototype.matchall: 4.0.11 - dev: true - - /eslint-scope@5.1.1: - resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} - engines: {node: '>=8.0.0'} - dependencies: - esrecurse: 4.3.0 - estraverse: 4.3.0 - dev: true - - /eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - dev: true - - /eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true - - /eslint-visitor-keys@4.0.0: - resolution: {integrity: sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - dev: true - - /eslint@8.57.0: - resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - hasBin: true - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) - '@eslint-community/regexpp': 4.10.0 - '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.57.0 - '@humanwhocodes/config-array': 0.11.14 - '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.2.0 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.3 - debug: 4.3.4(supports-color@8.1.1) - doctrine: 3.0.0 - escape-string-regexp: 4.0.0 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 - esquery: 1.5.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 - find-up: 5.0.0 - glob-parent: 6.0.2 - globals: 13.24.0 - graphemer: 1.4.0 - ignore: 5.3.1 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - is-path-inside: 3.0.3 - js-yaml: 4.1.0 - json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.3 - strip-ansi: 6.0.1 - text-table: 0.2.0 - transitivePeerDependencies: - - supports-color - dev: true - - /espree@10.0.1: - resolution: {integrity: sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - dependencies: - acorn: 8.11.3 - acorn-jsx: 5.3.2(acorn@8.11.3) - eslint-visitor-keys: 4.0.0 - dev: true - - /espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - acorn: 8.11.3 - acorn-jsx: 5.3.2(acorn@8.11.3) - eslint-visitor-keys: 3.4.3 - dev: true - - /esprima@4.0.1: - resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} - engines: {node: '>=4'} - hasBin: true - - /esquery@1.5.0: - resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} - engines: {node: '>=0.10'} - dependencies: - estraverse: 5.3.0 - dev: true - - /esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} - dependencies: - estraverse: 5.3.0 - dev: true - - /estraverse@4.3.0: - resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} - engines: {node: '>=4.0'} - dev: true - - /estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - dev: true - - /estree-walker@2.0.2: - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - dev: true - - /esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - dev: true - - /event-target-shim@5.0.1: - resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} - engines: {node: '>=6'} - dev: true - - /eventemitter2@6.4.7: - resolution: {integrity: sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==} - dev: true - - /eventemitter3@2.0.3: - resolution: {integrity: sha512-jLN68Dx5kyFHaePoXWPsCGW5qdyZQtLYHkxkg02/Mz6g0kYpDx4FyP6XfArhQdlOC4b8Mv+EMxPo/8La7Tzghg==} - dev: false - - /events@3.3.0: - resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} - engines: {node: '>=0.8.x'} - - /execa@1.0.0: - resolution: {integrity: sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==} - engines: {node: '>=6'} - dependencies: - cross-spawn: 6.0.5 - get-stream: 4.1.0 - is-stream: 1.1.0 - npm-run-path: 2.0.2 - p-finally: 1.0.0 - signal-exit: 3.0.7 - strip-eof: 1.0.0 - dev: true - - /execa@4.1.0: - resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} - engines: {node: '>=10'} - dependencies: - cross-spawn: 7.0.3 - get-stream: 5.2.0 - human-signals: 1.1.1 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - dev: true - - /execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} - dependencies: - cross-spawn: 7.0.3 - get-stream: 6.0.1 - human-signals: 2.1.0 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - - /executable@4.1.1: - resolution: {integrity: sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==} - engines: {node: '>=4'} - dependencies: - pify: 2.3.0 - dev: true - - /exit@0.1.2: - resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} - engines: {node: '>= 0.8.0'} - - /expect@29.7.0: - resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/expect-utils': 29.7.0 - jest-get-type: 29.6.3 - jest-matcher-utils: 29.7.0 - jest-message-util: 29.7.0 - jest-util: 29.7.0 - - /extend@3.0.2: - resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - - /extract-zip@2.0.1(supports-color@8.1.1): - resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} - engines: {node: '>= 10.17.0'} - hasBin: true - dependencies: - debug: 4.3.4(supports-color@8.1.1) - get-stream: 5.2.0 - yauzl: 2.10.0 - optionalDependencies: - '@types/yauzl': 2.10.3 - transitivePeerDependencies: - - supports-color - dev: true - - /extsprintf@1.3.0: - resolution: {integrity: sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==} - engines: {'0': node >=0.6.0} - - /fast-copy@3.0.2: - resolution: {integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==} - dev: true - - /fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - /fast-diff@1.1.2: - resolution: {integrity: sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==} - dev: false - - /fast-diff@1.2.0: - resolution: {integrity: sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==} - dev: true - - /fast-diff@1.3.0: - resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} - dev: false - - /fast-fifo@1.3.2: - resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} - dev: true - - /fast-glob@3.3.2: - resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} - engines: {node: '>=8.6.0'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.5 - - /fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - - /fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - dev: true - - /fast-redact@3.5.0: - resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} - engines: {node: '>=6'} - dev: true - - /fast-safe-stringify@2.1.1: - resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - dev: true - - /fastq@1.17.1: - resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} - dependencies: - reusify: 1.0.4 - - /fb-watchman@2.0.2: - resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} - dependencies: - bser: 2.1.1 - - /fd-slicer@1.1.0: - resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} - dependencies: - pend: 1.2.0 - dev: true - - /figures@3.2.0: - resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} - engines: {node: '>=8'} - dependencies: - escape-string-regexp: 1.0.5 - dev: true - - /file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} - dependencies: - flat-cache: 3.2.0 - dev: true - - /filelist@1.0.4: - resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} - dependencies: - minimatch: 5.1.6 - dev: true - - /fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - dependencies: - to-regex-range: 5.0.1 - - /find-cache-dir@2.1.0: - resolution: {integrity: sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==} - engines: {node: '>=6'} - dependencies: - commondir: 1.0.1 - make-dir: 2.1.0 - pkg-dir: 3.0.0 - dev: true - - /find-cache-dir@3.3.2: - resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==} - engines: {node: '>=8'} - dependencies: - commondir: 1.0.1 - make-dir: 3.1.0 - pkg-dir: 4.2.0 - dev: true - - /find-cache-dir@4.0.0: - resolution: {integrity: sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==} - engines: {node: '>=14.16'} - dependencies: - common-path-prefix: 3.0.0 - pkg-dir: 7.0.0 - dev: true - - /find-root@1.1.0: - resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} - dev: false - - /find-up@3.0.0: - resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} - engines: {node: '>=6'} - dependencies: - locate-path: 3.0.0 - dev: true - - /find-up@4.1.0: - resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} - engines: {node: '>=8'} - dependencies: - locate-path: 5.0.0 - path-exists: 4.0.0 - - /find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - dev: true - - /find-up@6.3.0: - resolution: {integrity: sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - locate-path: 7.2.0 - path-exists: 5.0.0 - dev: true - - /flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} - dependencies: - flatted: 3.3.1 - keyv: 4.5.4 - rimraf: 3.0.2 - dev: true - - /flatted@3.3.1: - resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} - dev: true - - /follow-redirects@1.15.6: - resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - - /for-each@0.3.3: - resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} - dependencies: - is-callable: 1.2.7 - dev: true - - /foreground-child@2.0.0: - resolution: {integrity: sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==} - engines: {node: '>=8.0.0'} - dependencies: - cross-spawn: 7.0.3 - signal-exit: 3.0.7 - dev: true - - /foreground-child@3.1.1: - resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} - engines: {node: '>=14'} - dependencies: - cross-spawn: 7.0.3 - signal-exit: 4.1.0 - - /forever-agent@0.6.1: - resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} - - /form-data@2.3.3: - resolution: {integrity: sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==} - engines: {node: '>= 0.12'} - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 - - /form-data@4.0.0: - resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} - engines: {node: '>= 6'} - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 - - /fraction.js@4.3.7: - resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} - dev: true - - /fromentries@1.3.2: - resolution: {integrity: sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==} - dev: true - - /fs-extra@10.1.0: - resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} - engines: {node: '>=12'} - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.1.0 - universalify: 2.0.1 - dev: true - - /fs-extra@7.0.1: - resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} - engines: {node: '>=6 <7 || >=8'} - dependencies: - graceful-fs: 4.2.11 - jsonfile: 4.0.0 - universalify: 0.1.2 - dev: true - - /fs-extra@9.1.0: - resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} - engines: {node: '>=10'} - dependencies: - at-least-node: 1.0.0 - graceful-fs: 4.2.11 - jsonfile: 6.1.0 - universalify: 2.0.1 - dev: true - - /fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - - /fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - requiresBuild: true - optional: true - - /function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - /function.prototype.name@1.1.6: - resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - functions-have-names: 1.2.3 - dev: true - - /functions-have-names@1.2.3: - resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - - /gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - - /get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - - /get-intrinsic@1.2.4: - resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} - engines: {node: '>= 0.4'} - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - has-proto: 1.0.3 - has-symbols: 1.0.3 - hasown: 2.0.2 - - /get-node-dimensions@1.2.1: - resolution: {integrity: sha512-2MSPMu7S1iOTL+BOa6K1S62hB2zUAYNF/lV0gSVlOaacd087lc6nR1H1r0e3B1CerTo+RceOmi1iJW+vp21xcQ==} - dev: false - - /get-nonce@1.0.1: - resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} - engines: {node: '>=6'} - dev: false - - /get-package-type@0.1.0: - resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} - engines: {node: '>=8.0.0'} - - /get-pixels@3.3.3: - resolution: {integrity: sha512-5kyGBn90i9tSMUVHTqkgCHsoWoR+/lGbl4yC83Gefyr0HLIhgSWEx/2F/3YgsZ7UpYNuM6pDhDK7zebrUJ5nXg==} - dependencies: - data-uri-to-buffer: 0.0.3 - jpeg-js: 0.4.4 - mime-types: 2.1.35 - ndarray: 1.0.19 - ndarray-pack: 1.2.1 - node-bitmap: 0.0.1 - omggif: 1.0.10 - parse-data-uri: 0.2.0 - pngjs: 3.4.0 - request: 2.88.2 - through: 2.3.8 - dev: false - - /get-stdin@5.0.1: - resolution: {integrity: sha512-jZV7n6jGE3Gt7fgSTJoz91Ak5MuTLwMwkoYdjxuJ/AmjIsE1UC03y/IWkZCQGEvVNS9qoRNwy5BCqxImv0FVeA==} - engines: {node: '>=0.12.0'} - dev: true - - /get-stream@4.1.0: - resolution: {integrity: sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==} - engines: {node: '>=6'} - dependencies: - pump: 3.0.0 - dev: true - - /get-stream@5.2.0: - resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} - engines: {node: '>=8'} - dependencies: - pump: 3.0.0 - dev: true - - /get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} - - /get-symbol-description@1.0.2: - resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - get-intrinsic: 1.2.4 - dev: true - - /getos@3.2.1: - resolution: {integrity: sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==} - dependencies: - async: 3.2.5 - dev: true - - /getpass@0.1.7: - resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} - dependencies: - assert-plus: 1.0.0 - - /glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - dependencies: - is-glob: 4.0.3 - - /glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} - dependencies: - is-glob: 4.0.3 - - /glob-to-regexp@0.4.1: - resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} - dev: true - - /glob@10.3.12: - resolution: {integrity: sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==} - engines: {node: '>=16 || 14 >=14.17'} - hasBin: true - dependencies: - foreground-child: 3.1.1 - jackspeak: 2.3.6 - minimatch: 9.0.4 - minipass: 7.0.4 - path-scurry: 1.10.2 - - /glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - - /global-dirs@3.0.1: - resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} - engines: {node: '>=10'} - dependencies: - ini: 2.0.0 - dev: true - - /globalize@0.1.1: - resolution: {integrity: sha512-5e01v8eLGfuQSOvx2MsDMOWS0GFtCx1wPzQSmcHw4hkxFzrQDBO3Xwg/m8Hr/7qXMrHeOIE29qWVzyv06u1TZA==} - dev: false - - /globals@11.12.0: - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} - engines: {node: '>=4'} - - /globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} - dependencies: - type-fest: 0.20.2 - dev: true - - /globalthis@1.0.3: - resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} - engines: {node: '>= 0.4'} - dependencies: - define-properties: 1.2.1 - dev: true - - /globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.3.2 - ignore: 5.3.1 - merge2: 1.4.1 - slash: 3.0.0 - dev: true - - /glur@1.1.2: - resolution: {integrity: sha512-l+8esYHTKOx2G/Aao4lEQ0bnHWg4fWtJbVoZZT9Knxi01pB8C80BR85nONLFwkkQoFRCmXY+BUcGZN3yZ2QsRA==} - dev: true - - /goober@2.1.14(csstype@3.1.3): - resolution: {integrity: sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==} - peerDependencies: - csstype: ^3.0.10 - dependencies: - csstype: 3.1.3 - dev: false - - /google-protobuf@3.21.2: - resolution: {integrity: sha512-3MSOYFO5U9mPGikIYCzK0SaThypfGgS6bHqrUGXG3DPHCrb+txNqeEcns1W0lkGfk0rCyNXm7xB9rMxnCiZOoA==} - dev: false - - /gopd@1.0.1: - resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} - dependencies: - get-intrinsic: 1.2.4 - - /graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - /graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - dev: true - - /gunzip-maybe@1.4.2: - resolution: {integrity: sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==} - hasBin: true - dependencies: - browserify-zlib: 0.1.4 - is-deflate: 1.0.0 - is-gzip: 1.0.0 - peek-stream: 1.1.3 - pumpify: 1.5.1 - through2: 2.0.5 - dev: true - - /hachure-fill@0.5.2: - resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} - dev: false - - /har-schema@2.0.0: - resolution: {integrity: sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==} - engines: {node: '>=4'} - dev: false - - /har-validator@5.1.5: - resolution: {integrity: sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==} - engines: {node: '>=6'} - deprecated: this library is no longer supported - dependencies: - ajv: 6.12.6 - har-schema: 2.0.0 - dev: false - - /has-ansi@2.0.0: - resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==} - engines: {node: '>=0.10.0'} - dependencies: - ansi-regex: 2.1.1 - dev: true - - /has-bigints@1.0.2: - resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} - dev: true - - /has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} - - /has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - /has-property-descriptors@1.0.2: - resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} - dependencies: - es-define-property: 1.0.0 - - /has-proto@1.0.3: - resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} - engines: {node: '>= 0.4'} - - /has-symbols@1.0.3: - resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} - engines: {node: '>= 0.4'} - - /has-tostringtag@1.0.2: - resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} - engines: {node: '>= 0.4'} - dependencies: - has-symbols: 1.0.3 - - /hasha@5.2.2: - resolution: {integrity: sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==} - engines: {node: '>=8'} - dependencies: - is-stream: 2.0.1 - type-fest: 0.8.1 - dev: true - - /hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} - dependencies: - function-bind: 1.1.2 - - /hast-util-embedded@3.0.0: - resolution: {integrity: sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==} - dependencies: - '@types/hast': 3.0.4 - hast-util-is-element: 3.0.0 - dev: false - - /hast-util-from-html@2.0.3: - resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} - dependencies: - '@types/hast': 3.0.4 - devlop: 1.1.0 - hast-util-from-parse5: 8.0.1 - parse5: 7.1.2 - vfile: 6.0.3 - vfile-message: 4.0.2 - dev: false - - /hast-util-from-parse5@8.0.1: - resolution: {integrity: sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==} - dependencies: - '@types/hast': 3.0.4 - '@types/unist': 3.0.3 - devlop: 1.1.0 - hastscript: 8.0.0 - property-information: 6.5.0 - vfile: 6.0.3 - vfile-location: 5.0.3 - web-namespaces: 2.0.1 - dev: false - - /hast-util-has-property@3.0.0: - resolution: {integrity: sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==} - dependencies: - '@types/hast': 3.0.4 - dev: false - - /hast-util-is-body-ok-link@3.0.1: - resolution: {integrity: sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==} - dependencies: - '@types/hast': 3.0.4 - dev: false - - /hast-util-is-element@3.0.0: - resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} - dependencies: - '@types/hast': 3.0.4 - dev: false - - /hast-util-minify-whitespace@1.0.1: - resolution: {integrity: sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==} - dependencies: - '@types/hast': 3.0.4 - hast-util-embedded: 3.0.0 - hast-util-is-element: 3.0.0 - hast-util-whitespace: 3.0.0 - unist-util-is: 6.0.0 - dev: false - - /hast-util-parse-selector@4.0.0: - resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} - dependencies: - '@types/hast': 3.0.4 - dev: false - - /hast-util-phrasing@3.0.1: - resolution: {integrity: sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==} - dependencies: - '@types/hast': 3.0.4 - hast-util-embedded: 3.0.0 - hast-util-has-property: 3.0.0 - hast-util-is-body-ok-link: 3.0.1 - hast-util-is-element: 3.0.0 - dev: false - - /hast-util-to-html@9.0.3: - resolution: {integrity: sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg==} - dependencies: - '@types/hast': 3.0.4 - '@types/unist': 3.0.3 - ccount: 2.0.1 - comma-separated-tokens: 2.0.3 - hast-util-whitespace: 3.0.0 - html-void-elements: 3.0.0 - mdast-util-to-hast: 13.2.0 - property-information: 6.5.0 - space-separated-tokens: 2.0.2 - stringify-entities: 4.0.4 - zwitch: 2.0.4 - dev: false - - /hast-util-to-mdast@10.1.0: - resolution: {integrity: sha512-DsL/SvCK9V7+vfc6SLQ+vKIyBDXTk2KLSbfBYkH4zeF/uR1yBajHRhkzuaUSGOB1WJSTieJBdHwxlC+HLKvZZw==} - dependencies: - '@types/hast': 3.0.4 - '@types/mdast': 4.0.4 - '@ungap/structured-clone': 1.2.0 - hast-util-phrasing: 3.0.1 - hast-util-to-html: 9.0.3 - hast-util-to-text: 4.0.2 - hast-util-whitespace: 3.0.0 - mdast-util-phrasing: 4.1.0 - mdast-util-to-hast: 13.2.0 - mdast-util-to-string: 4.0.0 - rehype-minify-whitespace: 6.0.2 - trim-trailing-lines: 2.1.0 - unist-util-position: 5.0.0 - unist-util-visit: 5.0.0 - dev: false - - /hast-util-to-text@4.0.2: - resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} - dependencies: - '@types/hast': 3.0.4 - '@types/unist': 3.0.3 - hast-util-is-element: 3.0.0 - unist-util-find-after: 5.0.0 - dev: false - - /hast-util-whitespace@3.0.0: - resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} - dependencies: - '@types/hast': 3.0.4 - dev: false - - /hastscript@8.0.0: - resolution: {integrity: sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==} - dependencies: - '@types/hast': 3.0.4 - comma-separated-tokens: 2.0.3 - hast-util-parse-selector: 4.0.0 - property-information: 6.5.0 - space-separated-tokens: 2.0.2 - dev: false - - /he@1.2.0: - resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} - hasBin: true - dev: true - - /header-case@2.0.4: - resolution: {integrity: sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==} - dependencies: - capital-case: 1.0.4 - tslib: 2.6.2 - dev: true - - /help-me@5.0.0: - resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} - dev: true - - /highlight.js@11.10.0: - resolution: {integrity: sha512-SYVnVFswQER+zu1laSya563s+F8VDGt7o35d4utbamowvUNLLMovFqwCLSocpZTz3MgaSRA1IbqRWZv97dtErQ==} - engines: {node: '>=12.0.0'} - dev: false - - /hoist-non-react-statics@3.3.2: - resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} - dependencies: - react-is: 16.13.1 - dev: false - - /html-encoding-sniffer@3.0.0: - resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==} - engines: {node: '>=12'} - dependencies: - whatwg-encoding: 2.0.0 - dev: true - - /html-escaper@2.0.2: - resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - - /html-minifier-terser@6.1.0: - resolution: {integrity: sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==} - engines: {node: '>=12'} - hasBin: true - dependencies: - camel-case: 4.1.2 - clean-css: 5.3.3 - commander: 8.3.0 - he: 1.2.0 - param-case: 3.0.4 - relateurl: 0.2.7 - terser: 5.31.0 - dev: true - - /html-parse-stringify@3.0.1: - resolution: {integrity: sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==} - dependencies: - void-elements: 3.1.0 - dev: false - - /html-void-elements@3.0.0: - resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} - dev: false - - /htmlparser2@8.0.2: - resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} - dependencies: - domelementtype: 2.3.0 - domhandler: 5.0.3 - domutils: 3.1.0 - entities: 4.5.0 - dev: true - - /http-proxy-agent@5.0.0: - resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} - engines: {node: '>= 6'} - dependencies: - '@tootallnate/once': 2.0.0 - agent-base: 6.0.2 - debug: 4.3.7 - transitivePeerDependencies: - - supports-color - dev: true - - /http-signature@1.2.0: - resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==} - engines: {node: '>=0.8', npm: '>=1.3.7'} - dependencies: - assert-plus: 1.0.0 - jsprim: 1.4.2 - sshpk: 1.18.0 - dev: false - - /http-signature@1.3.6: - resolution: {integrity: sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==} - engines: {node: '>=0.10'} - dependencies: - assert-plus: 1.0.0 - jsprim: 2.0.2 - sshpk: 1.18.0 - dev: true - - /https-proxy-agent@5.0.1: - resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} - engines: {node: '>= 6'} - dependencies: - agent-base: 6.0.2 - debug: 4.3.7 - transitivePeerDependencies: - - supports-color - dev: true - - /human-signals@1.1.1: - resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} - engines: {node: '>=8.12.0'} - dev: true - - /human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} - - /i18next-browser-languagedetector@7.2.1: - resolution: {integrity: sha512-h/pM34bcH6tbz8WgGXcmWauNpQupCGr25XPp9cZwZInR9XHSjIFDYp1SIok7zSPsTOMxdvuLyu86V+g2Kycnfw==} - dependencies: - '@babel/runtime': 7.24.1 - dev: false - - /i18next-resources-to-backend@1.2.1: - resolution: {integrity: sha512-okHbVA+HZ7n1/76MsfhPqDou0fptl2dAlhRDu2ideXloRRduzHsqDOznJBef+R3DFZnbvWoBW+KxJ7fnFjd6Yw==} - dependencies: - '@babel/runtime': 7.24.1 - dev: false - - /i18next@22.5.1: - resolution: {integrity: sha512-8TGPgM3pAD+VRsMtUMNknRz3kzqwp/gPALrWMsDnmC1mKqJwpWyooQRLMcbTwq8z8YwSmuj+ZYvc+xCuEpkssA==} - dependencies: - '@babel/runtime': 7.24.1 - dev: false - - /iconv-lite@0.6.3: - resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} - engines: {node: '>=0.10.0'} - dependencies: - safer-buffer: 2.1.2 - - /ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - dev: true - - /ignore@5.3.1: - resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} - engines: {node: '>= 4'} - dev: true - - /immer@10.1.1: - resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==} - dev: false - - /immutable@4.3.6: - resolution: {integrity: sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==} - - /immutable@5.0.3: - resolution: {integrity: sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==} - dev: false - - /import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - - /import-local@3.1.0: - resolution: {integrity: sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==} - engines: {node: '>=8'} - hasBin: true - dependencies: - pkg-dir: 4.2.0 - resolve-cwd: 3.0.0 - - /imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - - /indent-string@4.0.0: - resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} - engines: {node: '>=8'} - dev: true - - /inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - - /inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - /ini@2.0.0: - resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} - engines: {node: '>=10'} - dev: true - - /internal-slot@1.0.7: - resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} - engines: {node: '>= 0.4'} - dependencies: - es-errors: 1.3.0 - hasown: 2.0.2 - side-channel: 1.0.6 - dev: true - - /internmap@1.0.1: - resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} - dev: false - - /internmap@2.0.3: - resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} - engines: {node: '>=12'} - dev: false - - /invariant@2.2.4: - resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} - dependencies: - loose-envify: 1.4.0 - dev: false - - /iota-array@1.0.0: - resolution: {integrity: sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==} - dev: false - - /is-arguments@1.1.1: - resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - has-tostringtag: 1.0.2 - dev: false - - /is-array-buffer@3.0.4: - resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 - dev: true - - /is-arrayish@0.2.1: - resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - - /is-bigint@1.0.4: - resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} - dependencies: - has-bigints: 1.0.2 - dev: true - - /is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - dependencies: - binary-extensions: 2.3.0 - - /is-boolean-object@1.1.2: - resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - has-tostringtag: 1.0.2 - dev: true - - /is-buffer@1.1.6: - resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} - dev: false - - /is-buffer@2.0.5: - resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} - engines: {node: '>=4'} - dev: true - - /is-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} - dev: true - - /is-ci@3.0.1: - resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} - hasBin: true - dependencies: - ci-info: 3.9.0 - dev: true - - /is-core-module@2.13.1: - resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} - dependencies: - hasown: 2.0.2 - - /is-data-view@1.0.1: - resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} - engines: {node: '>= 0.4'} - dependencies: - is-typed-array: 1.1.13 - dev: true - - /is-date-object@1.0.5: - resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} - engines: {node: '>= 0.4'} - dependencies: - has-tostringtag: 1.0.2 - - /is-deflate@1.0.0: - resolution: {integrity: sha512-YDoFpuZWu1VRXlsnlYMzKyVRITXj7Ej/V9gXQ2/pAe7X1J7M/RNOqaIYi6qUn+B7nGyB9pDXrv02dsB58d2ZAQ==} - dev: true - - /is-docker@2.2.1: - resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} - engines: {node: '>=8'} - hasBin: true - dev: true - - /is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - /is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - /is-generator-fn@2.1.0: - resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} - engines: {node: '>=6'} - - /is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - dependencies: - is-extglob: 2.1.1 - - /is-gzip@1.0.0: - resolution: {integrity: sha512-rcfALRIb1YewtnksfRIHGcIY93QnK8BIQ/2c9yDYcG/Y6+vRoJuTWBmmSEbyLLYtXm7q35pHOHbZFQBaLrhlWQ==} - engines: {node: '>=0.10.0'} - dev: true - - /is-hotkey@0.2.0: - resolution: {integrity: sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw==} - dev: false - - /is-installed-globally@0.4.0: - resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==} - engines: {node: '>=10'} - dependencies: - global-dirs: 3.0.1 - is-path-inside: 3.0.3 - dev: true - - /is-negative-zero@2.0.3: - resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} - engines: {node: '>= 0.4'} - dev: true - - /is-number-object@1.0.7: - resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} - engines: {node: '>= 0.4'} - dependencies: - has-tostringtag: 1.0.2 - dev: true - - /is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - - /is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - dev: true - - /is-plain-obj@4.1.0: - resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} - engines: {node: '>=12'} - dev: false - - /is-plain-object@2.0.4: - resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} - engines: {node: '>=0.10.0'} - dependencies: - isobject: 3.0.1 - dev: true - - /is-plain-object@5.0.0: - resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} - engines: {node: '>=0.10.0'} - dev: false - - /is-potential-custom-element-name@1.0.1: - resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} - dev: true - - /is-regex@1.1.4: - resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - has-tostringtag: 1.0.2 - - /is-shared-array-buffer@1.0.3: - resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - dev: true - - /is-stream@1.1.0: - resolution: {integrity: sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==} - engines: {node: '>=0.10.0'} - dev: true - - /is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} - - /is-string@1.0.7: - resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} - engines: {node: '>= 0.4'} - dependencies: - has-tostringtag: 1.0.2 - dev: true - - /is-symbol@1.0.4: - resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} - engines: {node: '>= 0.4'} - dependencies: - has-symbols: 1.0.3 - dev: true - - /is-typed-array@1.1.13: - resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} - engines: {node: '>= 0.4'} - dependencies: - which-typed-array: 1.1.15 - dev: true - - /is-typedarray@1.0.0: - resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} - - /is-unicode-supported@0.1.0: - resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} - engines: {node: '>=10'} - dev: true - - /is-weakref@1.0.2: - resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} - dependencies: - call-bind: 1.0.7 - dev: true - - /is-windows@1.0.2: - resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} - engines: {node: '>=0.10.0'} - dev: true - - /is-wsl@2.2.0: - resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} - engines: {node: '>=8'} - dependencies: - is-docker: 2.2.1 - dev: true - - /isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - dev: true - - /isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - dev: true - - /isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - /isobject@3.0.1: - resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} - engines: {node: '>=0.10.0'} - dev: true - - /isomorphic.js@0.2.5: - resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==} - dev: false - - /isstream@0.1.2: - resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} - - /istanbul-lib-coverage@3.2.2: - resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} - engines: {node: '>=8'} - - /istanbul-lib-hook@3.0.0: - resolution: {integrity: sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==} - engines: {node: '>=8'} - dependencies: - append-transform: 2.0.0 - dev: true - - /istanbul-lib-instrument@4.0.3: - resolution: {integrity: sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==} - engines: {node: '>=8'} - dependencies: - '@babel/core': 7.24.3 - '@istanbuljs/schema': 0.1.3 - istanbul-lib-coverage: 3.2.2 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - dev: true - - /istanbul-lib-instrument@5.2.1: - resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} - engines: {node: '>=8'} - dependencies: - '@babel/core': 7.24.3 - '@babel/parser': 7.24.1 - '@istanbuljs/schema': 0.1.3 - istanbul-lib-coverage: 3.2.2 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - /istanbul-lib-instrument@6.0.2: - resolution: {integrity: sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw==} - engines: {node: '>=10'} - dependencies: - '@babel/core': 7.24.3 - '@babel/parser': 7.24.1 - '@istanbuljs/schema': 0.1.3 - istanbul-lib-coverage: 3.2.2 - semver: 7.6.0 - transitivePeerDependencies: - - supports-color - - /istanbul-lib-processinfo@2.0.3: - resolution: {integrity: sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==} - engines: {node: '>=8'} - dependencies: - archy: 1.0.0 - cross-spawn: 7.0.3 - istanbul-lib-coverage: 3.2.2 - p-map: 3.0.0 - rimraf: 3.0.2 - uuid: 8.3.2 - dev: true - - /istanbul-lib-report@3.0.1: - resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} - engines: {node: '>=10'} - dependencies: - istanbul-lib-coverage: 3.2.2 - make-dir: 4.0.0 - supports-color: 7.2.0 - - /istanbul-lib-source-maps@4.0.1: - resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} - engines: {node: '>=10'} - dependencies: - debug: 4.3.4(supports-color@8.1.1) - istanbul-lib-coverage: 3.2.2 - source-map: 0.6.1 - transitivePeerDependencies: - - supports-color - - /istanbul-reports@3.1.7: - resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} - engines: {node: '>=8'} - dependencies: - html-escaper: 2.0.2 - istanbul-lib-report: 3.0.1 - - /iterm2-version@4.2.0: - resolution: {integrity: sha512-IoiNVk4SMPu6uTcK+1nA5QaHNok2BMDLjSl5UomrOixe5g4GkylhPwuiGdw00ysSCrXAKNMfFTu+u/Lk5f6OLQ==} - engines: {node: '>=8'} - dependencies: - app-path: 3.3.0 - plist: 3.1.0 - dev: true - - /jackspeak@2.3.6: - resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} - engines: {node: '>=14'} - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - - /jake@10.9.2: - resolution: {integrity: sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==} - engines: {node: '>=10'} - hasBin: true - dependencies: - async: 3.2.5 - chalk: 4.1.2 - filelist: 1.0.4 - minimatch: 3.1.2 - dev: true - - /jest-changed-files@29.7.0: - resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - execa: 5.1.1 - jest-util: 29.7.0 - p-limit: 3.1.0 - - /jest-circus@29.7.0: - resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/environment': 29.7.0 - '@jest/expect': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 20.11.30 - chalk: 4.1.2 - co: 4.6.0 - dedent: 1.5.1 - is-generator-fn: 2.1.0 - jest-each: 29.7.0 - jest-matcher-utils: 29.7.0 - jest-message-util: 29.7.0 - jest-runtime: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - p-limit: 3.1.0 - pretty-format: 29.7.0 - pure-rand: 6.1.0 - slash: 3.0.0 - stack-utils: 2.0.6 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - /jest-cli@29.7.0(@types/node@20.11.30): - resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - dependencies: - '@jest/core': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/types': 29.6.3 - chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.11.30) - exit: 0.1.2 - import-local: 3.1.0 - jest-config: 29.7.0(@types/node@20.11.30) - jest-util: 29.7.0 - jest-validate: 29.7.0 - yargs: 17.7.2 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - - /jest-config@29.7.0(@types/node@20.11.30): - resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - '@types/node': '*' - ts-node: '>=9.0.0' - peerDependenciesMeta: - '@types/node': - optional: true - ts-node: - optional: true - dependencies: - '@babel/core': 7.24.3 - '@jest/test-sequencer': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 20.11.30 - babel-jest: 29.7.0(@babel/core@7.24.3) - chalk: 4.1.2 - ci-info: 3.9.0 - deepmerge: 4.3.1 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-circus: 29.7.0 - jest-environment-node: 29.7.0 - jest-get-type: 29.6.3 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-runner: 29.7.0 - jest-util: 29.7.0 - jest-validate: 29.7.0 - micromatch: 4.0.8 - parse-json: 5.2.0 - pretty-format: 29.7.0 - slash: 3.0.0 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - babel-plugin-macros - - supports-color - - /jest-diff@29.7.0: - resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - chalk: 4.1.2 - diff-sequences: 29.6.3 - jest-get-type: 29.6.3 - pretty-format: 29.7.0 - - /jest-docblock@29.7.0: - resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - detect-newline: 3.1.0 - - /jest-each@29.7.0: - resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.6.3 - chalk: 4.1.2 - jest-get-type: 29.6.3 - jest-util: 29.7.0 - pretty-format: 29.7.0 - - /jest-environment-jsdom@29.6.2: - resolution: {integrity: sha512-7oa/+266AAEgkzae8i1awNEfTfjwawWKLpiw2XesZmaoVVj9u9t8JOYx18cG29rbPNtkUlZ8V4b5Jb36y/VxoQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - peerDependencies: - canvas: ^2.5.0 - peerDependenciesMeta: - canvas: - optional: true - dependencies: - '@jest/environment': 29.7.0 - '@jest/fake-timers': 29.7.0 - '@jest/types': 29.6.3 - '@types/jsdom': 20.0.1 - '@types/node': 20.11.30 - jest-mock: 29.7.0 - jest-util: 29.7.0 - jsdom: 20.0.3 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - dev: true - - /jest-environment-node@29.7.0: - resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/environment': 29.7.0 - '@jest/fake-timers': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 20.11.30 - jest-mock: 29.7.0 - jest-util: 29.7.0 - - /jest-get-type@29.6.3: - resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - /jest-haste-map@29.7.0: - resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.6.3 - '@types/graceful-fs': 4.1.9 - '@types/node': 20.11.30 - anymatch: 3.1.3 - fb-watchman: 2.0.2 - graceful-fs: 4.2.11 - jest-regex-util: 29.6.3 - jest-util: 29.7.0 - jest-worker: 29.7.0 - micromatch: 4.0.8 - walker: 1.0.8 - optionalDependencies: - fsevents: 2.3.3 - - /jest-image-snapshot@4.2.0(jest@29.5.0): - resolution: {integrity: sha512-6aAqv2wtfOgxiJeBayBCqHo1zX+A12SUNNzo7rIxiXh6W6xYVu8QyHWkada8HeRi+QUTHddp0O0Xa6kmQr+xbQ==} - engines: {node: '>= 10.14.2'} - peerDependencies: - jest: '>=20 <=26' - dependencies: - chalk: 1.1.3 - get-stdin: 5.0.1 - glur: 1.1.2 - jest: 29.5.0(@types/node@20.11.30) - lodash: 4.17.21 - mkdirp: 0.5.6 - pixelmatch: 5.3.0 - pngjs: 3.4.0 - rimraf: 2.7.1 - ssim.js: 3.5.0 - dev: true - - /jest-leak-detector@29.7.0: - resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - jest-get-type: 29.6.3 - pretty-format: 29.7.0 - - /jest-matcher-utils@29.7.0: - resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - chalk: 4.1.2 - jest-diff: 29.7.0 - jest-get-type: 29.6.3 - pretty-format: 29.7.0 - - /jest-message-util@29.7.0: - resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@babel/code-frame': 7.24.2 - '@jest/types': 29.6.3 - '@types/stack-utils': 2.0.3 - chalk: 4.1.2 - graceful-fs: 4.2.11 - micromatch: 4.0.8 - pretty-format: 29.7.0 - slash: 3.0.0 - stack-utils: 2.0.6 - - /jest-mock@29.7.0: - resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.6.3 - '@types/node': 20.11.30 - jest-util: 29.7.0 - - /jest-node-exports-resolver@1.1.6: - resolution: {integrity: sha512-NU412Qcb6WSRetCyEGMCC7IWHzO12LhSKaF1s9cyfM+EOYs4YN2gcNUT8hgu22X0oPFYNwLSPevgstBgLbD9ig==} - dev: true - - /jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): - resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} - engines: {node: '>=6'} - peerDependencies: - jest-resolve: '*' - peerDependenciesMeta: - jest-resolve: - optional: true - dependencies: - jest-resolve: 29.7.0 - - /jest-regex-util@29.6.3: - resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - /jest-resolve-dependencies@29.7.0: - resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - jest-regex-util: 29.6.3 - jest-snapshot: 29.7.0 - transitivePeerDependencies: - - supports-color - - /jest-resolve@29.7.0: - resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - chalk: 4.1.2 - graceful-fs: 4.2.11 - jest-haste-map: 29.7.0 - jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) - jest-util: 29.7.0 - jest-validate: 29.7.0 - resolve: 1.22.8 - resolve.exports: 2.0.2 - slash: 3.0.0 - - /jest-runner@29.7.0: - resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/console': 29.7.0 - '@jest/environment': 29.7.0 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 20.11.30 - chalk: 4.1.2 - emittery: 0.13.1 - graceful-fs: 4.2.11 - jest-docblock: 29.7.0 - jest-environment-node: 29.7.0 - jest-haste-map: 29.7.0 - jest-leak-detector: 29.7.0 - jest-message-util: 29.7.0 - jest-resolve: 29.7.0 - jest-runtime: 29.7.0 - jest-util: 29.7.0 - jest-watcher: 29.7.0 - jest-worker: 29.7.0 - p-limit: 3.1.0 - source-map-support: 0.5.13 - transitivePeerDependencies: - - supports-color - - /jest-runtime@29.7.0: - resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/environment': 29.7.0 - '@jest/fake-timers': 29.7.0 - '@jest/globals': 29.7.0 - '@jest/source-map': 29.6.3 - '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 20.11.30 - chalk: 4.1.2 - cjs-module-lexer: 1.2.3 - collect-v8-coverage: 1.0.2 - glob: 7.2.3 - graceful-fs: 4.2.11 - jest-haste-map: 29.7.0 - jest-message-util: 29.7.0 - jest-mock: 29.7.0 - jest-regex-util: 29.6.3 - jest-resolve: 29.7.0 - jest-snapshot: 29.7.0 - jest-util: 29.7.0 - slash: 3.0.0 - strip-bom: 4.0.0 - transitivePeerDependencies: - - supports-color - - /jest-snapshot@29.7.0: - resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@babel/core': 7.24.3 - '@babel/generator': 7.24.1 - '@babel/plugin-syntax-jsx': 7.24.1(@babel/core@7.24.3) - '@babel/plugin-syntax-typescript': 7.24.1(@babel/core@7.24.3) - '@babel/types': 7.24.0 - '@jest/expect-utils': 29.7.0 - '@jest/transform': 29.7.0 - '@jest/types': 29.6.3 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.24.3) - chalk: 4.1.2 - expect: 29.7.0 - graceful-fs: 4.2.11 - jest-diff: 29.7.0 - jest-get-type: 29.6.3 - jest-matcher-utils: 29.7.0 - jest-message-util: 29.7.0 - jest-util: 29.7.0 - natural-compare: 1.4.0 - pretty-format: 29.7.0 - semver: 7.6.0 - transitivePeerDependencies: - - supports-color - - /jest-util@29.7.0: - resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.6.3 - '@types/node': 20.11.30 - chalk: 4.1.2 - ci-info: 3.9.0 - graceful-fs: 4.2.11 - picomatch: 2.3.1 - - /jest-validate@29.7.0: - resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/types': 29.6.3 - camelcase: 6.3.0 - chalk: 4.1.2 - jest-get-type: 29.6.3 - leven: 3.1.0 - pretty-format: 29.7.0 - - /jest-watcher@29.7.0: - resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/test-result': 29.7.0 - '@jest/types': 29.6.3 - '@types/node': 20.11.30 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - emittery: 0.13.1 - jest-util: 29.7.0 - string-length: 4.0.2 - - /jest-worker@27.5.1: - resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} - engines: {node: '>= 10.13.0'} - dependencies: - '@types/node': 20.11.30 - merge-stream: 2.0.0 - supports-color: 8.1.1 - dev: true - - /jest-worker@29.7.0: - resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@types/node': 20.11.30 - jest-util: 29.7.0 - merge-stream: 2.0.0 - supports-color: 8.1.1 - - /jest@29.5.0(@types/node@20.11.30): - resolution: {integrity: sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - peerDependencies: - node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 - peerDependenciesMeta: - node-notifier: - optional: true - dependencies: - '@jest/core': 29.7.0 - '@jest/types': 29.6.3 - import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@20.11.30) - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - supports-color - - ts-node - - /jiti@1.21.7: - resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} - hasBin: true - dev: false - - /joycon@3.1.1: - resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} - engines: {node: '>=10'} - dev: true - - /jpeg-js@0.4.4: - resolution: {integrity: sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==} - dev: false - - /js-base64@3.7.7: - resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} - dev: false - - /js-md5@0.8.3: - resolution: {integrity: sha512-qR0HB5uP6wCuRMrWPTrkMaev7MJZwJuuw4fnwAzRgP4J4/F8RwtodOKpGp4XpqsLBFzzgqIO42efFAyz2Et6KQ==} - dev: false - - /js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - /js-yaml@3.14.1: - resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} - hasBin: true - dependencies: - argparse: 1.0.10 - esprima: 4.0.1 - - /js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true - dependencies: - argparse: 2.0.1 - dev: true - - /jsbn@0.1.1: - resolution: {integrity: sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==} - - /jsdom@20.0.3: - resolution: {integrity: sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==} - engines: {node: '>=14'} - peerDependencies: - canvas: ^2.5.0 - peerDependenciesMeta: - canvas: - optional: true - dependencies: - abab: 2.0.6 - acorn: 8.11.3 - acorn-globals: 7.0.1 - cssom: 0.5.0 - cssstyle: 2.3.0 - data-urls: 3.0.2 - decimal.js: 10.4.3 - domexception: 4.0.0 - escodegen: 2.1.0 - form-data: 4.0.0 - html-encoding-sniffer: 3.0.0 - http-proxy-agent: 5.0.0 - https-proxy-agent: 5.0.1 - is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.7 - parse5: 7.1.2 - saxes: 6.0.0 - symbol-tree: 3.2.4 - tough-cookie: 4.1.3 - w3c-xmlserializer: 4.0.0 - webidl-conversions: 7.0.0 - whatwg-encoding: 2.0.0 - whatwg-mimetype: 3.0.0 - whatwg-url: 11.0.0 - ws: 8.16.0 - xml-name-validator: 4.0.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - dev: true - - /jsesc@0.5.0: - resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} - hasBin: true - dev: true - - /jsesc@2.5.2: - resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} - engines: {node: '>=4'} - hasBin: true - - /json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - dev: true - - /json-parse-even-better-errors@2.3.1: - resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - - /json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - - /json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - dev: true - - /json-schema@0.4.0: - resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} - - /json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - dev: true - - /json-stringify-safe@5.0.1: - resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - - /json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - - /jsonc-parser@3.2.1: - resolution: {integrity: sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==} - dev: true - - /jsonfile@4.0.0: - resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} - optionalDependencies: - graceful-fs: 4.2.11 - dev: true - - /jsonfile@6.1.0: - resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.11 - dev: true - - /jsprim@1.4.2: - resolution: {integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==} - engines: {node: '>=0.6.0'} - dependencies: - assert-plus: 1.0.0 - extsprintf: 1.3.0 - json-schema: 0.4.0 - verror: 1.10.0 - dev: false - - /jsprim@2.0.2: - resolution: {integrity: sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==} - engines: {'0': node >=0.6.0} - dependencies: - assert-plus: 1.0.0 - extsprintf: 1.3.0 - json-schema: 0.4.0 - verror: 1.10.0 - dev: true - - /jsx-ast-utils@3.3.5: - resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} - engines: {node: '>=4.0'} - dependencies: - array-includes: 3.1.8 - array.prototype.flat: 1.3.2 - object.assign: 4.1.5 - object.values: 1.2.0 - dev: true - - /katex@0.16.10: - resolution: {integrity: sha512-ZiqaC04tp2O5utMsl2TEZTXxa6WSC4yo0fv5ML++D3QZv/vx2Mct0mTlRx3O+uUkjfuAgOkzsCmq5MiUEsDDdA==} - hasBin: true - dependencies: - commander: 8.3.0 - dev: false - - /keycode@2.2.1: - resolution: {integrity: sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg==} - dev: false - - /keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - dependencies: - json-buffer: 3.0.1 - dev: true - - /khroma@2.1.0: - resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} - dev: false - - /kind-of@6.0.3: - resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} - engines: {node: '>=0.10.0'} - dev: true - - /kleur@3.0.3: - resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} - engines: {node: '>=6'} - - /kolorist@1.8.0: - resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} - - /langium@3.0.0: - resolution: {integrity: sha512-+Ez9EoiByeoTu/2BXmEaZ06iPNXM6thWJp02KfBO/raSMyCJ4jw7AkWWa+zBCTm0+Tw1Fj9FOxdqSskyN5nAwg==} - engines: {node: '>=16.0.0'} - dependencies: - chevrotain: 11.0.3 - chevrotain-allstar: 0.3.1(chevrotain@11.0.3) - vscode-languageserver: 9.0.1 - vscode-languageserver-textdocument: 1.0.12 - vscode-uri: 3.0.8 - dev: false - - /layout-base@1.0.2: - resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} - dev: false - - /layout-base@2.0.1: - resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==} - dev: false - - /lazy-ass@1.6.0: - resolution: {integrity: sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==} - engines: {node: '> 0.8'} - dev: true - - /leven@3.1.0: - resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} - engines: {node: '>=6'} - - /levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - dev: true - - /lib0@0.2.94: - resolution: {integrity: sha512-hZ3p54jL4Wpu7IOg26uC7dnEWiMyNlUrb9KoG7+xYs45WkQwpVvKFndVq2+pqLYKe1u8Fp3+zAfZHVvTK34PvQ==} - engines: {node: '>=16'} - hasBin: true - dependencies: - isomorphic.js: 0.2.5 - dev: false - - /lightgallery@2.7.2: - resolution: {integrity: sha512-Ewdcg9UPDqV0HGZeD7wNE4uYejwH2u0fMo5VAr6GHzlPYlhItJvjhLTR0cL0V1HjhMsH39PAom9iv69ewitLWw==} - engines: {node: '>=6.0.0'} - dev: false - - /lilconfig@2.1.0: - resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} - engines: {node: '>=10'} - dev: true - - /lilconfig@3.1.3: - resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} - engines: {node: '>=14'} - dev: false - - /lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - - /listr2@3.14.0(enquirer@2.4.1): - resolution: {integrity: sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==} - engines: {node: '>=10.0.0'} - peerDependencies: - enquirer: '>= 2.3.0 < 3' - peerDependenciesMeta: - enquirer: - optional: true - dependencies: - cli-truncate: 2.1.0 - colorette: 2.0.20 - enquirer: 2.4.1 - log-update: 4.0.0 - p-map: 4.0.0 - rfdc: 1.3.1 - rxjs: 7.8.0 - through: 2.3.8 - wrap-ansi: 7.0.0 - dev: true - - /loader-runner@4.3.0: - resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} - engines: {node: '>=6.11.5'} - dev: true - - /local-pkg@0.5.1: - resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==} - engines: {node: '>=14'} - dependencies: - mlly: 1.7.3 - pkg-types: 1.2.1 - dev: false - - /locate-path@3.0.0: - resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} - engines: {node: '>=6'} - dependencies: - p-locate: 3.0.0 - path-exists: 3.0.0 - dev: true - - /locate-path@5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} - engines: {node: '>=8'} - dependencies: - p-locate: 4.1.0 - - /locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - dependencies: - p-locate: 5.0.0 - dev: true - - /locate-path@7.2.0: - resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - p-locate: 6.0.0 - dev: true - - /lodash-es@4.17.21: - resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} - dev: false - - /lodash.clonedeep@4.5.0: - resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} - - /lodash.debounce@4.0.8: - resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} - dev: true - - /lodash.flattendeep@4.4.0: - resolution: {integrity: sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==} - dev: true - - /lodash.isequal@4.5.0: - resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} - - /lodash.memoize@4.1.2: - resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} - dev: true - - /lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - dev: true - - /lodash.once@4.1.1: - resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} - dev: true - - /lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - - /log-symbols@4.1.0: - resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} - engines: {node: '>=10'} - dependencies: - chalk: 4.1.2 - is-unicode-supported: 0.1.0 - dev: true - - /log-update@4.0.0: - resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==} - engines: {node: '>=10'} - dependencies: - ansi-escapes: 4.3.2 - cli-cursor: 3.1.0 - slice-ansi: 4.0.0 - wrap-ansi: 6.2.0 - dev: true - - /loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true - dependencies: - js-tokens: 4.0.0 - - /lower-case@2.0.2: - resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} - dependencies: - tslib: 2.6.2 - dev: true - - /lru-cache@10.2.0: - resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==} - engines: {node: 14 || >=16.14} - - /lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - dependencies: - yallist: 3.1.1 - - /lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} - dependencies: - yallist: 4.0.0 - - /lucide-react@0.468.0(react@18.2.0): - resolution: {integrity: sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA==} - peerDependencies: - react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc - dependencies: - react: 18.2.0 - dev: false - - /luxon@3.4.4: - resolution: {integrity: sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==} - engines: {node: '>=12'} - dev: false - - /lz-string@1.5.0: - resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} - hasBin: true - dev: true - - /magic-string@0.25.9: - resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} - dependencies: - sourcemap-codec: 1.4.8 - dev: true - - /magic-string@0.30.8: - resolution: {integrity: sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==} - engines: {node: '>=12'} - dependencies: - '@jridgewell/sourcemap-codec': 1.4.15 - dev: true - - /make-dir@2.1.0: - resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==} - engines: {node: '>=6'} - dependencies: - pify: 4.0.1 - semver: 5.7.2 - dev: true - - /make-dir@3.1.0: - resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} - engines: {node: '>=8'} - dependencies: - semver: 6.3.1 - dev: true - - /make-dir@4.0.0: - resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} - engines: {node: '>=10'} - dependencies: - semver: 7.6.0 - - /make-error@1.3.6: - resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - dev: true - - /makeerror@1.0.12: - resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} - dependencies: - tmpl: 1.0.5 - - /marked@13.0.3: - resolution: {integrity: sha512-rqRix3/TWzE9rIoFGIn8JmsVfhiuC8VIQ8IdX5TfzmeBucdY05/0UlzKaw0eVtpcN/OdVFpBk7CjKGo9iHJ/zA==} - engines: {node: '>= 18'} - hasBin: true - dev: false - - /material-colors@1.2.6: - resolution: {integrity: sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==} - dev: false - - /mdast-util-from-markdown@2.0.2: - resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} - dependencies: - '@types/mdast': 4.0.4 - '@types/unist': 3.0.3 - decode-named-character-reference: 1.0.2 - devlop: 1.1.0 - mdast-util-to-string: 4.0.0 - micromark: 4.0.1 - micromark-util-decode-numeric-character-reference: 2.0.2 - micromark-util-decode-string: 2.0.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 - unist-util-stringify-position: 4.0.0 - transitivePeerDependencies: - - supports-color - dev: false - - /mdast-util-phrasing@4.1.0: - resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} - dependencies: - '@types/mdast': 4.0.4 - unist-util-is: 6.0.0 - dev: false - - /mdast-util-to-hast@13.2.0: - resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} - dependencies: - '@types/hast': 3.0.4 - '@types/mdast': 4.0.4 - '@ungap/structured-clone': 1.2.0 - devlop: 1.1.0 - micromark-util-sanitize-uri: 2.0.0 - trim-lines: 3.0.1 - unist-util-position: 5.0.0 - unist-util-visit: 5.0.0 - vfile: 6.0.3 - dev: false - - /mdast-util-to-string@4.0.0: - resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} - dependencies: - '@types/mdast': 4.0.4 - dev: false - - /mdn-data@2.0.28: - resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} - dev: true - - /mdn-data@2.0.30: - resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} - dev: true - - /memoize-one@5.2.1: - resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} - dev: false - - /memoize-one@6.0.0: - resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} - dev: false - - /merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - - /merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - - /mermaid@11.4.1: - resolution: {integrity: sha512-Mb01JT/x6CKDWaxigwfZYuYmDZ6xtrNwNlidKZwkSrDaY9n90tdrJTV5Umk+wP1fZscGptmKFXHsXMDEVZ+Q6A==} - dependencies: - '@braintree/sanitize-url': 7.1.0 - '@iconify/utils': 2.1.33 - '@mermaid-js/parser': 0.3.0 - '@types/d3': 7.4.3 - cytoscape: 3.30.4 - cytoscape-cose-bilkent: 4.1.0(cytoscape@3.30.4) - cytoscape-fcose: 2.2.0(cytoscape@3.30.4) - d3: 7.9.0 - d3-sankey: 0.12.3 - dagre-d3-es: 7.0.11 - dayjs: 1.11.10 - dompurify: 3.2.2 - katex: 0.16.10 - khroma: 2.1.0 - lodash-es: 4.17.21 - marked: 13.0.3 - roughjs: 4.6.6 - stylis: 4.3.4 - ts-dedent: 2.2.0 - uuid: 9.0.1 - transitivePeerDependencies: - - supports-color - dev: false - - /micromark-core-commonmark@2.0.2: - resolution: {integrity: sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==} - dependencies: - decode-named-character-reference: 1.0.2 - devlop: 1.1.0 - micromark-factory-destination: 2.0.1 - micromark-factory-label: 2.0.1 - micromark-factory-space: 2.0.1 - micromark-factory-title: 2.0.1 - micromark-factory-whitespace: 2.0.1 - micromark-util-character: 2.1.0 - micromark-util-chunked: 2.0.1 - micromark-util-classify-character: 2.0.1 - micromark-util-html-tag-name: 2.0.1 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-subtokenize: 2.0.3 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 - dev: false - - /micromark-factory-destination@2.0.1: - resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} - dependencies: - micromark-util-character: 2.1.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 - dev: false - - /micromark-factory-label@2.0.1: - resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} - dependencies: - devlop: 1.1.0 - micromark-util-character: 2.1.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 - dev: false - - /micromark-factory-space@2.0.1: - resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} - dependencies: - micromark-util-character: 2.1.0 - micromark-util-types: 2.0.0 - dev: false - - /micromark-factory-title@2.0.1: - resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} - dependencies: - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 - dev: false - - /micromark-factory-whitespace@2.0.1: - resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} - dependencies: - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 - dev: false - - /micromark-util-character@2.1.0: - resolution: {integrity: sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==} - dependencies: - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 - dev: false - - /micromark-util-chunked@2.0.1: - resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} - dependencies: - micromark-util-symbol: 2.0.0 - dev: false - - /micromark-util-classify-character@2.0.1: - resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} - dependencies: - micromark-util-character: 2.1.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 - dev: false - - /micromark-util-combine-extensions@2.0.1: - resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} - dependencies: - micromark-util-chunked: 2.0.1 - micromark-util-types: 2.0.0 - dev: false - - /micromark-util-decode-numeric-character-reference@2.0.2: - resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} - dependencies: - micromark-util-symbol: 2.0.0 - dev: false - - /micromark-util-decode-string@2.0.1: - resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} - dependencies: - decode-named-character-reference: 1.0.2 - micromark-util-character: 2.1.0 - micromark-util-decode-numeric-character-reference: 2.0.2 - micromark-util-symbol: 2.0.0 - dev: false - - /micromark-util-encode@2.0.0: - resolution: {integrity: sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==} - dev: false - - /micromark-util-html-tag-name@2.0.1: - resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} - dev: false - - /micromark-util-normalize-identifier@2.0.1: - resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} - dependencies: - micromark-util-symbol: 2.0.0 - dev: false - - /micromark-util-resolve-all@2.0.1: - resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} - dependencies: - micromark-util-types: 2.0.0 - dev: false - - /micromark-util-sanitize-uri@2.0.0: - resolution: {integrity: sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==} - dependencies: - micromark-util-character: 2.1.0 - micromark-util-encode: 2.0.0 - micromark-util-symbol: 2.0.0 - dev: false - - /micromark-util-subtokenize@2.0.3: - resolution: {integrity: sha512-VXJJuNxYWSoYL6AJ6OQECCFGhIU2GGHMw8tahogePBrjkG8aCCas3ibkp7RnVOSTClg2is05/R7maAhF1XyQMg==} - dependencies: - devlop: 1.1.0 - micromark-util-chunked: 2.0.1 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 - dev: false - - /micromark-util-symbol@2.0.0: - resolution: {integrity: sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==} - dev: false - - /micromark-util-types@2.0.0: - resolution: {integrity: sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==} - dev: false - - /micromark@4.0.1: - resolution: {integrity: sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==} - dependencies: - '@types/debug': 4.1.12 - debug: 4.3.7 - decode-named-character-reference: 1.0.2 - devlop: 1.1.0 - micromark-core-commonmark: 2.0.2 - micromark-factory-space: 2.0.1 - micromark-util-character: 2.1.0 - micromark-util-chunked: 2.0.1 - micromark-util-combine-extensions: 2.0.1 - micromark-util-decode-numeric-character-reference: 2.0.2 - micromark-util-encode: 2.0.0 - micromark-util-normalize-identifier: 2.0.1 - micromark-util-resolve-all: 2.0.1 - micromark-util-sanitize-uri: 2.0.0 - micromark-util-subtokenize: 2.0.3 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 - transitivePeerDependencies: - - supports-color - dev: false - - /micromatch@4.0.5: - resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} - engines: {node: '>=8.6'} - dependencies: - braces: 3.0.2 - picomatch: 2.3.1 - - /micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} - dependencies: - braces: 3.0.3 - picomatch: 2.3.1 - - /mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - - /mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - dependencies: - mime-db: 1.52.0 - - /mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - - /minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - dependencies: - brace-expansion: 1.1.11 - - /minimatch@5.1.6: - resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} - engines: {node: '>=10'} - dependencies: - brace-expansion: 2.0.1 - dev: true - - /minimatch@9.0.3: - resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} - engines: {node: '>=16 || 14 >=14.17'} - dependencies: - brace-expansion: 2.0.1 - dev: true - - /minimatch@9.0.4: - resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} - engines: {node: '>=16 || 14 >=14.17'} - dependencies: - brace-expansion: 2.0.1 - - /minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - dev: true - - /minipass@7.0.4: - resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} - engines: {node: '>=16 || 14 >=14.17'} - - /mkdirp@0.5.6: - resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} - hasBin: true - dependencies: - minimist: 1.2.8 - dev: true - - /mkdirp@1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} - hasBin: true - dev: true - - /mlly@1.7.3: - resolution: {integrity: sha512-xUsx5n/mN0uQf4V548PKQ+YShA4/IW0KI1dZhrNrPCLG+xizETbHTkOa1f8/xut9JRPp8kQuMnz0oqwkTiLo/A==} - dependencies: - acorn: 8.14.0 - pathe: 1.1.2 - pkg-types: 1.2.1 - ufo: 1.5.4 - dev: false - - /moment-timezone@0.5.45: - resolution: {integrity: sha512-HIWmqA86KcmCAhnMAN0wuDOARV/525R2+lOLotuGFzn4HO+FH+/645z2wx0Dt3iDv6/p61SIvKnDstISainhLQ==} - dependencies: - moment: 2.30.1 - dev: false - - /moment@2.30.1: - resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} - dev: false - - /mrmime@2.0.0: - resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==} - engines: {node: '>=10'} - dev: true - - /ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - - /ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - /mz@2.7.0: - resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} - dependencies: - any-promise: 1.3.0 - object-assign: 4.1.1 - thenify-all: 1.6.0 - dev: false - - /nanoid@3.3.7: - resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - /nanoid@4.0.2: - resolution: {integrity: sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==} - engines: {node: ^14 || ^16 || >=18} - hasBin: true - dev: false - - /natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - - /ndarray-pack@1.2.1: - resolution: {integrity: sha512-51cECUJMT0rUZNQa09EoKsnFeDL4x2dHRT0VR5U2H5ZgEcm95ZDWcMA5JShroXjHOejmAD/fg8+H+OvUnVXz2g==} - dependencies: - cwise-compiler: 1.1.3 - ndarray: 1.0.19 - dev: false - - /ndarray@1.0.19: - resolution: {integrity: sha512-B4JHA4vdyZU30ELBw3g7/p9bZupyew5a7tX1Y/gGeF2hafrPaQZhgrGQfsvgfYbgdFZjYwuEcnaobeM/WMW+HQ==} - dependencies: - iota-array: 1.0.0 - is-buffer: 1.1.6 - dev: false - - /neo-async@2.6.2: - resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} - dev: true - - /nice-try@1.0.5: - resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} - dev: true - - /no-case@3.0.4: - resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} - dependencies: - lower-case: 2.0.2 - tslib: 2.6.2 - dev: true - - /node-addon-api@7.1.1: - resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} - dev: false - optional: true - - /node-bitmap@0.0.1: - resolution: {integrity: sha512-Jx5lPaaLdIaOsj2mVLWMWulXF6GQVdyLvNSxmiYCvZ8Ma2hfKX0POoR2kgKOqz+oFsRreq0yYZjQ2wjE9VNzCA==} - engines: {node: '>=v0.6.5'} - dev: false - - /node-html-parser@5.4.2: - resolution: {integrity: sha512-RaBPP3+51hPne/OolXxcz89iYvQvKOydaqoePpOgXcrOKZhjVIzmpKZz+Hd/RBO2/zN2q6CNJhQzucVz+u3Jyw==} - dependencies: - css-select: 4.3.0 - he: 1.2.0 - dev: true - - /node-int64@0.4.0: - resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} - - /node-preload@0.2.1: - resolution: {integrity: sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==} - engines: {node: '>=8'} - dependencies: - process-on-spawn: 1.0.0 - dev: true - - /node-releases@2.0.14: - resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} - - /normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - /normalize-range@0.1.2: - resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} - engines: {node: '>=0.10.0'} - dev: true - - /notistack@3.0.1(csstype@3.1.3)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-ntVZXXgSQH5WYfyU+3HfcXuKaapzAJ8fBLQ/G618rn3yvSzEbnOB8ZSOwhX+dAORy/lw+GC2N061JA0+gYWTVA==} - engines: {node: '>=12.0.0', npm: '>=6.0.0'} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - dependencies: - clsx: 1.2.1 - goober: 2.1.14(csstype@3.1.3) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - transitivePeerDependencies: - - csstype - dev: false - - /npm-run-path@2.0.2: - resolution: {integrity: sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==} - engines: {node: '>=4'} - dependencies: - path-key: 2.0.1 - dev: true - - /npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} - dependencies: - path-key: 3.1.1 - - /nth-check@2.1.1: - resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - dependencies: - boolbase: 1.0.0 - dev: true - - /numeral@2.0.6: - resolution: {integrity: sha512-qaKRmtYPZ5qdw4jWJD6bxEf1FJEqllJrwxCLIm0sQU/A7v2/czigzOb+C2uSiFsa9lBUzeH7M1oK+Q+OLxL3kA==} - dev: false - - /nwsapi@2.2.7: - resolution: {integrity: sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==} - dev: true - - /nyc@15.1.0: - resolution: {integrity: sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==} - engines: {node: '>=8.9'} - hasBin: true - dependencies: - '@istanbuljs/load-nyc-config': 1.1.0 - '@istanbuljs/schema': 0.1.3 - caching-transform: 4.0.0 - convert-source-map: 1.9.0 - decamelize: 1.2.0 - find-cache-dir: 3.3.2 - find-up: 4.1.0 - foreground-child: 2.0.0 - get-package-type: 0.1.0 - glob: 7.2.3 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-hook: 3.0.0 - istanbul-lib-instrument: 4.0.3 - istanbul-lib-processinfo: 2.0.3 - istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 4.0.1 - istanbul-reports: 3.1.7 - make-dir: 3.1.0 - node-preload: 0.2.1 - p-map: 3.0.0 - process-on-spawn: 1.0.0 - resolve-from: 5.0.0 - rimraf: 3.0.2 - signal-exit: 3.0.7 - spawn-wrap: 2.0.0 - test-exclude: 6.0.0 - yargs: 15.4.1 - transitivePeerDependencies: - - supports-color - dev: true - - /oauth-sign@0.9.0: - resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==} - dev: false - - /object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - /object-hash@3.0.0: - resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} - engines: {node: '>= 6'} - - /object-inspect@1.13.1: - resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} - dev: true - - /object-is@1.1.6: - resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - dev: false - - /object-keys@1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} - engines: {node: '>= 0.4'} - - /object.assign@4.1.5: - resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - has-symbols: 1.0.3 - object-keys: 1.1.1 - dev: true - - /object.entries@1.1.8: - resolution: {integrity: sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-object-atoms: 1.0.0 - dev: true - - /object.fromentries@2.0.8: - resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-object-atoms: 1.0.0 - dev: true - - /object.hasown@1.1.4: - resolution: {integrity: sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg==} - engines: {node: '>= 0.4'} - dependencies: - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-object-atoms: 1.0.0 - dev: true - - /object.values@1.2.0: - resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-object-atoms: 1.0.0 - dev: true - - /omggif@1.0.10: - resolution: {integrity: sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==} - dev: false - - /on-exit-leak-free@2.1.2: - resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==} - engines: {node: '>=14.0.0'} - dev: true - - /once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - dependencies: - wrappy: 1.0.2 - - /onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - dependencies: - mimic-fn: 2.1.0 - - /open@8.4.2: - resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} - engines: {node: '>=12'} - dependencies: - define-lazy-prop: 2.0.0 - is-docker: 2.2.1 - is-wsl: 2.2.0 - dev: true - - /optionator@0.9.3: - resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} - engines: {node: '>= 0.8.0'} - dependencies: - '@aashutoshrathi/word-wrap': 1.2.6 - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 - dev: true - - /ospath@1.2.2: - resolution: {integrity: sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==} - dev: true - - /p-finally@1.0.0: - resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} - engines: {node: '>=4'} - dev: true - - /p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} - dependencies: - p-try: 2.2.0 - - /p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - dependencies: - yocto-queue: 0.1.0 - - /p-limit@4.0.0: - resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - yocto-queue: 1.0.0 - dev: true - - /p-locate@3.0.0: - resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} - engines: {node: '>=6'} - dependencies: - p-limit: 2.3.0 - dev: true - - /p-locate@4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} - engines: {node: '>=8'} - dependencies: - p-limit: 2.3.0 - - /p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - dependencies: - p-limit: 3.1.0 - dev: true - - /p-locate@6.0.0: - resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dependencies: - p-limit: 4.0.0 - dev: true - - /p-map@3.0.0: - resolution: {integrity: sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==} - engines: {node: '>=8'} - dependencies: - aggregate-error: 3.1.0 - dev: true - - /p-map@4.0.0: - resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} - engines: {node: '>=10'} - dependencies: - aggregate-error: 3.1.0 - dev: true - - /p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} - - /package-hash@4.0.0: - resolution: {integrity: sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==} - engines: {node: '>=8'} - dependencies: - graceful-fs: 4.2.11 - hasha: 5.2.2 - lodash.flattendeep: 4.4.0 - release-zalgo: 1.0.0 - dev: true - - /package-manager-detector@0.2.6: - resolution: {integrity: sha512-9vPH3qooBlYRJdmdYP00nvjZOulm40r5dhtal8st18ctf+6S1k7pi5yIHLvI4w5D70x0Y+xdVD9qITH0QO/A8A==} - dev: false - - /pako@0.2.9: - resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} - dev: true - - /param-case@3.0.4: - resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} - dependencies: - dot-case: 3.0.4 - tslib: 2.6.2 - dev: true - - /parchment@1.1.4: - resolution: {integrity: sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==} - - /parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - dependencies: - callsites: 3.1.0 - - /parse-data-uri@0.2.0: - resolution: {integrity: sha512-uOtts8NqDcaCt1rIsO3VFDRsAfgE4c6osG4d9z3l4dCBlxYFzni6Di/oNU270SDrjkfZuUvLZx1rxMyqh46Y9w==} - dependencies: - data-uri-to-buffer: 0.0.3 - dev: false - - /parse-json@5.2.0: - resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} - engines: {node: '>=8'} - dependencies: - '@babel/code-frame': 7.24.2 - error-ex: 1.3.2 - json-parse-even-better-errors: 2.3.1 - lines-and-columns: 1.2.4 - - /parse5-htmlparser2-tree-adapter@7.0.0: - resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==} - dependencies: - domhandler: 5.0.3 - parse5: 7.1.2 - dev: true - - /parse5@7.1.2: - resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} - dependencies: - entities: 4.5.0 - - /pascal-case@3.1.2: - resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} - dependencies: - no-case: 3.0.4 - tslib: 2.6.2 - dev: true - - /path-case@3.0.4: - resolution: {integrity: sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==} - dependencies: - dot-case: 3.0.4 - tslib: 2.6.2 - dev: true - - /path-data-parser@0.1.0: - resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==} - dev: false - - /path-exists@3.0.0: - resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} - engines: {node: '>=4'} - dev: true - - /path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - - /path-exists@5.0.0: - resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - dev: true - - /path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - - /path-key@2.0.1: - resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==} - engines: {node: '>=4'} - dev: true - - /path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - /path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - - /path-scurry@1.10.2: - resolution: {integrity: sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==} - engines: {node: '>=16 || 14 >=14.17'} - dependencies: - lru-cache: 10.2.0 - minipass: 7.0.4 - - /path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - - /pathe@0.2.0: - resolution: {integrity: sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==} - dev: true - - /pathe@1.1.2: - resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - dev: false - - /peek-stream@1.1.3: - resolution: {integrity: sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==} - dependencies: - buffer-from: 1.1.2 - duplexify: 3.7.1 - through2: 2.0.5 - dev: true - - /pend@1.2.0: - resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} - dev: true - - /performance-now@2.1.0: - resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} - - /picocolors@1.0.0: - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - dev: true - - /picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - /picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - - /pify@2.3.0: - resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} - engines: {node: '>=0.10.0'} - - /pify@4.0.1: - resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} - engines: {node: '>=6'} - dev: true - - /pino-abstract-transport@1.2.0: - resolution: {integrity: sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==} - dependencies: - readable-stream: 4.5.2 - split2: 4.2.0 - dev: true - - /pino-pretty@11.2.1: - resolution: {integrity: sha512-O05NuD9tkRasFRWVaF/uHLOvoRDFD7tb5VMertr78rbsYFjYp48Vg3477EshVAF5eZaEw+OpDl/tu+B0R5o+7g==} - hasBin: true - dependencies: - colorette: 2.0.20 - dateformat: 4.6.3 - fast-copy: 3.0.2 - fast-safe-stringify: 2.1.1 - help-me: 5.0.0 - joycon: 3.1.1 - minimist: 1.2.8 - on-exit-leak-free: 2.1.2 - pino-abstract-transport: 1.2.0 - pump: 3.0.0 - readable-stream: 4.5.2 - secure-json-parse: 2.7.0 - sonic-boom: 4.0.1 - strip-json-comments: 3.1.1 - dev: true - - /pino-std-serializers@7.0.0: - resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} - dev: true - - /pino@9.2.0: - resolution: {integrity: sha512-g3/hpwfujK5a4oVbaefoJxezLzsDgLcNJeITvC6yrfwYeT9la+edCK42j5QpEQSQCZgTKapXvnQIdgZwvRaZug==} - hasBin: true - dependencies: - atomic-sleep: 1.0.0 - fast-redact: 3.5.0 - on-exit-leak-free: 2.1.2 - pino-abstract-transport: 1.2.0 - pino-std-serializers: 7.0.0 - process-warning: 3.0.0 - quick-format-unescaped: 4.0.4 - real-require: 0.2.0 - safe-stable-stringify: 2.4.3 - sonic-boom: 4.0.1 - thread-stream: 3.1.0 - dev: true - - /pirates@4.0.6: - resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} - engines: {node: '>= 6'} - - /pixelmatch@5.3.0: - resolution: {integrity: sha512-o8mkY4E/+LNUf6LzX96ht6k6CEDi65k9G2rjMtBe9Oo+VPKSvl+0GKHuH/AlG+GA5LPG/i5hrekkxUc3s2HU+Q==} - hasBin: true - dependencies: - pngjs: 6.0.0 - dev: true - - /pkg-dir@3.0.0: - resolution: {integrity: sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==} - engines: {node: '>=6'} - dependencies: - find-up: 3.0.0 - dev: true - - /pkg-dir@4.2.0: - resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} - engines: {node: '>=8'} - dependencies: - find-up: 4.1.0 - - /pkg-dir@7.0.0: - resolution: {integrity: sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==} - engines: {node: '>=14.16'} - dependencies: - find-up: 6.3.0 - dev: true - - /pkg-types@1.2.1: - resolution: {integrity: sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==} - dependencies: - confbox: 0.1.8 - mlly: 1.7.3 - pathe: 1.1.2 - dev: false - - /plist@3.1.0: - resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==} - engines: {node: '>=10.4.0'} - dependencies: - '@xmldom/xmldom': 0.8.10 - base64-js: 1.5.1 - xmlbuilder: 15.1.1 - dev: true - - /pngjs@3.4.0: - resolution: {integrity: sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==} - engines: {node: '>=4.0.0'} - - /pngjs@6.0.0: - resolution: {integrity: sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==} - engines: {node: '>=12.13.0'} - dev: true - - /points-on-curve@0.2.0: - resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} - dev: false - - /points-on-path@0.2.1: - resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==} - dependencies: - path-data-parser: 0.1.0 - points-on-curve: 0.2.0 - dev: false - - /possible-typed-array-names@1.0.0: - resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} - engines: {node: '>= 0.4'} - dev: true - - /postcss-import@14.1.0(postcss@8.4.21): - resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==} - engines: {node: '>=10.0.0'} - peerDependencies: - postcss: ^8.0.0 - dependencies: - postcss: 8.4.21 - postcss-value-parser: 4.2.0 - read-cache: 1.0.0 - resolve: 1.22.8 - dev: true - - /postcss-import@15.1.0(postcss@8.4.49): - resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} - engines: {node: '>=14.0.0'} - peerDependencies: - postcss: ^8.0.0 - dependencies: - postcss: 8.4.49 - postcss-value-parser: 4.2.0 - read-cache: 1.0.0 - resolve: 1.22.8 - dev: false - - /postcss-js@4.0.1(postcss@8.4.21): - resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} - engines: {node: ^12 || ^14 || >= 16} - peerDependencies: - postcss: ^8.4.21 - dependencies: - camelcase-css: 2.0.1 - postcss: 8.4.21 - dev: true - - /postcss-js@4.0.1(postcss@8.4.49): - resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} - engines: {node: ^12 || ^14 || >= 16} - peerDependencies: - postcss: ^8.4.21 - dependencies: - camelcase-css: 2.0.1 - postcss: 8.4.49 - dev: false - - /postcss-load-config@3.1.4(postcss@8.4.21): - resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} - engines: {node: '>= 10'} - peerDependencies: - postcss: '>=8.0.9' - ts-node: '>=9.0.0' - peerDependenciesMeta: - postcss: - optional: true - ts-node: - optional: true - dependencies: - lilconfig: 2.1.0 - postcss: 8.4.21 - yaml: 1.10.2 - dev: true - - /postcss-load-config@4.0.2(postcss@8.4.49): - resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} - engines: {node: '>= 14'} - peerDependencies: - postcss: '>=8.0.9' - ts-node: '>=9.0.0' - peerDependenciesMeta: - postcss: - optional: true - ts-node: - optional: true - dependencies: - lilconfig: 3.1.3 - postcss: 8.4.49 - yaml: 2.6.1 - dev: false - - /postcss-nested@6.0.0(postcss@8.4.21): - resolution: {integrity: sha512-0DkamqrPcmkBDsLn+vQDIrtkSbNkv5AD/M322ySo9kqFkCIYklym2xEmWkwo+Y3/qZo34tzEPNUw4y7yMCdv5w==} - engines: {node: '>=12.0'} - peerDependencies: - postcss: ^8.2.14 - dependencies: - postcss: 8.4.21 - postcss-selector-parser: 6.0.16 - dev: true - - /postcss-nested@6.2.0(postcss@8.4.49): - resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} - engines: {node: '>=12.0'} - peerDependencies: - postcss: ^8.2.14 - dependencies: - postcss: 8.4.49 - postcss-selector-parser: 6.1.2 - dev: false - - /postcss-selector-parser@6.0.16: - resolution: {integrity: sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==} - engines: {node: '>=4'} - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - dev: true - - /postcss-selector-parser@6.1.2: - resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} - engines: {node: '>=4'} - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - dev: false - - /postcss-value-parser@4.2.0: - resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - - /postcss@8.4.21: - resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==} - engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: 3.3.7 - picocolors: 1.0.0 - source-map-js: 1.2.0 - dev: true - - /postcss@8.4.38: - resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} - engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: 3.3.7 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - /postcss@8.4.49: - resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} - engines: {node: ^10 || ^12 || >=14} - dependencies: - nanoid: 3.3.7 - picocolors: 1.1.1 - source-map-js: 1.2.1 - dev: false - - /prefix-style@2.0.1: - resolution: {integrity: sha512-gdr1MBNVT0drzTq95CbSNdsrBDoHGlb2aDJP/FoY+1e+jSDPOb1Cv554gH2MGiSr2WTcXi/zu+NaFzfcHQkfBQ==} - dev: false - - /prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - dev: true - - /prettier-plugin-tailwindcss@0.2.2(prettier@2.8.4): - resolution: {integrity: sha512-5RjUbWRe305pUpc48MosoIp6uxZvZxrM6GyOgsbGLTce+ehePKNm7ziW2dLG2air9aXbGuXlHVSQQw4Lbosq3w==} - engines: {node: '>=12.17.0'} - peerDependencies: - '@prettier/plugin-php': '*' - '@prettier/plugin-pug': '*' - '@shopify/prettier-plugin-liquid': '*' - '@shufo/prettier-plugin-blade': '*' - '@trivago/prettier-plugin-sort-imports': '*' - prettier: '>=2.2.0' - prettier-plugin-astro: '*' - prettier-plugin-css-order: '*' - prettier-plugin-import-sort: '*' - prettier-plugin-jsdoc: '*' - prettier-plugin-organize-attributes: '*' - prettier-plugin-organize-imports: '*' - prettier-plugin-style-order: '*' - prettier-plugin-svelte: '*' - prettier-plugin-twig-melody: '*' - peerDependenciesMeta: - '@prettier/plugin-php': - optional: true - '@prettier/plugin-pug': - optional: true - '@shopify/prettier-plugin-liquid': - optional: true - '@shufo/prettier-plugin-blade': - optional: true - '@trivago/prettier-plugin-sort-imports': - optional: true - prettier-plugin-astro: - optional: true - prettier-plugin-css-order: - optional: true - prettier-plugin-import-sort: - optional: true - prettier-plugin-jsdoc: - optional: true - prettier-plugin-organize-attributes: - optional: true - prettier-plugin-organize-imports: - optional: true - prettier-plugin-style-order: - optional: true - prettier-plugin-svelte: - optional: true - prettier-plugin-twig-melody: - optional: true - dependencies: - prettier: 2.8.4 - dev: true - - /prettier@2.8.4: - resolution: {integrity: sha512-vIS4Rlc2FNh0BySk3Wkd6xmwxB0FpOndW5fisM5H8hsZSxU2VWVB5CWIkIjWvrHjIhxk2g3bfMKM87zNTrZddw==} - engines: {node: '>=10.13.0'} - hasBin: true - dev: true - - /pretty-bytes@5.6.0: - resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} - engines: {node: '>=6'} - dev: true - - /pretty-format@27.5.1: - resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} - engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - dependencies: - ansi-regex: 5.0.1 - ansi-styles: 5.2.0 - react-is: 17.0.2 - dev: true - - /pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - dependencies: - '@jest/schemas': 29.6.3 - ansi-styles: 5.2.0 - react-is: 18.2.0 - - /prismjs@1.29.0: - resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==} - engines: {node: '>=6'} - dev: false - - /process-nextick-args@2.0.1: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - dev: true - - /process-on-spawn@1.0.0: - resolution: {integrity: sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==} - engines: {node: '>=8'} - dependencies: - fromentries: 1.3.2 - dev: true - - /process-warning@3.0.0: - resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==} - dev: true - - /process@0.11.10: - resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} - engines: {node: '>= 0.6.0'} - dev: true - - /prompts@2.4.2: - resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} - engines: {node: '>= 6'} - dependencies: - kleur: 3.0.3 - sisteransi: 1.0.5 - - /prop-types@15.8.1: - resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - react-is: 16.13.1 - - /property-information@6.5.0: - resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} - dev: false - - /protoc-gen-ts@0.8.7: - resolution: {integrity: sha512-jr4VJey2J9LVYCV7EVyVe53g1VMw28cCmYJhBe5e3YX5wiyiDwgxWxeDf9oTqAe4P1bN/YGAkW2jhlH8LohwiQ==} - hasBin: true - dev: false - - /proxy-from-env@1.0.0: - resolution: {integrity: sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==} - dev: true - - /proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - - /psl@1.9.0: - resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} - - /pump@2.0.1: - resolution: {integrity: sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==} - dependencies: - end-of-stream: 1.4.4 - once: 1.4.0 - dev: true - - /pump@3.0.0: - resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} - dependencies: - end-of-stream: 1.4.4 - once: 1.4.0 - dev: true - - /pumpify@1.5.1: - resolution: {integrity: sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==} - dependencies: - duplexify: 3.7.1 - inherits: 2.0.4 - pump: 2.0.1 - dev: true - - /punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - - /pure-rand@6.1.0: - resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} - - /qs@6.10.4: - resolution: {integrity: sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==} - engines: {node: '>=0.6'} - dependencies: - side-channel: 1.0.6 - dev: true - - /qs@6.5.3: - resolution: {integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==} - engines: {node: '>=0.6'} - dev: false - - /querystringify@2.2.0: - resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} - dev: true - - /queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - - /queue-tick@1.0.1: - resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==} - dev: true - - /quick-format-unescaped@4.0.4: - resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} - dev: true - - /quick-lru@5.1.1: - resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} - engines: {node: '>=10'} - dev: true - - /quill-delta@3.6.3: - resolution: {integrity: sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==} - engines: {node: '>=0.10'} - dependencies: - deep-equal: 1.1.2 - extend: 3.0.2 - fast-diff: 1.1.2 - dev: false - - /quill-delta@4.2.2: - resolution: {integrity: sha512-qjbn82b/yJzOjstBgkhtBjN2TNK+ZHP/BgUQO+j6bRhWQQdmj2lH6hXG7+nwwLF41Xgn//7/83lxs9n2BkTtTg==} - dependencies: - fast-diff: 1.2.0 - lodash.clonedeep: 4.5.0 - lodash.isequal: 4.5.0 - dev: true - - /quill-delta@5.1.0: - resolution: {integrity: sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==} - engines: {node: '>= 12.0.0'} - dependencies: - fast-diff: 1.3.0 - lodash.clonedeep: 4.5.0 - lodash.isequal: 4.5.0 - dev: false - - /quill@1.3.7: - resolution: {integrity: sha512-hG/DVzh/TiknWtE6QmWAF/pxoZKYxfe3J/d/+ShUWkDvvkZQVTPeVmUJVu1uE6DDooC4fWTiCLh84ul89oNz5g==} - dependencies: - clone: 2.1.2 - deep-equal: 1.1.2 - eventemitter3: 2.0.3 - extend: 3.0.2 - parchment: 1.1.4 - quill-delta: 3.6.3 - dev: false - - /raf-schd@4.0.3: - resolution: {integrity: sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==} - dev: false - - /raf@3.4.1: - resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==} - dependencies: - performance-now: 2.1.0 - dev: false - - /randombytes@2.1.0: - resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} - dependencies: - safe-buffer: 5.2.1 - dev: true - - /react-beautiful-dnd@13.1.1(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ==} - peerDependencies: - react: ^16.8.5 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.5 || ^17.0.0 || ^18.0.0 - dependencies: - '@babel/runtime': 7.24.1 - css-box-model: 1.2.1 - memoize-one: 5.2.1 - raf-schd: 4.0.3 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-redux: 7.2.9(react-dom@18.2.0)(react@18.2.0) - redux: 4.2.1 - use-memo-one: 1.1.3(react@18.2.0) - transitivePeerDependencies: - - react-native - dev: false - - /react-big-calendar@1.12.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-cPVcwH5V1YiC6QKaV4afvpuZ2DtP8+TocnZY98nGodqq8bfjVDiP3Ch+TewBZzj9mg7JbewHdufDZXZBqQl1lw==} - peerDependencies: - react: ^16.14.0 || ^17 || ^18 - react-dom: ^16.14.0 || ^17 || ^18 - dependencies: - '@babel/runtime': 7.24.1 - clsx: 1.2.1 - date-arithmetic: 4.1.0 - dayjs: 1.11.9 - dom-helpers: 5.2.1 - globalize: 0.1.1 - invariant: 2.2.4 - lodash: 4.17.21 - lodash-es: 4.17.21 - luxon: 3.4.4 - memoize-one: 6.0.0 - moment: 2.30.1 - moment-timezone: 0.5.45 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-overlays: 5.2.1(react-dom@18.2.0)(react@18.2.0) - uncontrollable: 7.2.1(react@18.2.0) - dev: false - - /react-color@2.19.3(react@18.2.0): - resolution: {integrity: sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA==} - peerDependencies: - react: '*' - dependencies: - '@icons/material': 0.2.4(react@18.2.0) - lodash: 4.17.21 - lodash-es: 4.17.21 - material-colors: 1.2.6 - prop-types: 15.8.1 - react: 18.2.0 - reactcss: 1.2.3(react@18.2.0) - tinycolor2: 1.6.0 - dev: false - - /react-custom-scrollbars-2@4.5.0(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-/z0nWAeXfMDr4+OXReTpYd1Atq9kkn4oI3qxq3iMXGQx1EEfwETSqB8HTAvg1X7dEqcCachbny1DRNGlqX5bDQ==} - peerDependencies: - react: ^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 - react-dom: ^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 - dependencies: - dom-css: 2.1.0 - prop-types: 15.8.1 - raf: 3.4.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /react-custom-scrollbars@4.2.1(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-VtJTUvZ7kPh/auZWIbBRceGPkE30XBYe+HktFxuMWBR2eVQQ+Ur6yFJMoaYcNpyGq22uYJ9Wx4UAEcC0K+LNPQ==} - peerDependencies: - react: ^0.14.0 || ^15.0.0 || ^16.0.0 - react-dom: ^0.14.0 || ^15.0.0 || ^16.0.0 - dependencies: - dom-css: 2.1.0 - prop-types: 15.8.1 - raf: 3.4.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /react-datepicker@4.25.0(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-zB7CSi44SJ0sqo8hUQ3BF1saE/knn7u25qEMTO1CQGofY1VAKahO8k9drZtp0cfW1DMfoYLR3uSY1/uMvbEzbg==} - peerDependencies: - react: ^16.9.0 || ^17 || ^18 - react-dom: ^16.9.0 || ^17 || ^18 - dependencies: - '@popperjs/core': 2.11.8 - classnames: 2.5.1 - date-fns: 2.30.0 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-onclickoutside: 6.13.1(react-dom@18.2.0)(react@18.2.0) - react-popper: 2.3.0(@popperjs/core@2.11.8)(react-dom@18.2.0)(react@18.2.0) - dev: false - - /react-dom@18.2.0(react@18.2.0): - resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} - peerDependencies: - react: ^18.2.0 - dependencies: - loose-envify: 1.4.0 - react: 18.2.0 - scheduler: 0.23.0 - - /react-error-boundary@4.0.13(react@18.2.0): - resolution: {integrity: sha512-b6PwbdSv8XeOSYvjt8LpgpKrZ0yGdtZokYwkwV2wlcZbxgopHX/hgPl5VgpnoVOWd868n1hktM8Qm4b+02MiLQ==} - peerDependencies: - react: '>=16.13.1' - dependencies: - '@babel/runtime': 7.24.1 - react: 18.2.0 - dev: false - - /react-event-listener@0.6.6(react@18.2.0): - resolution: {integrity: sha512-+hCNqfy7o9wvO6UgjqFmBzARJS7qrNoda0VqzvOuioEpoEXKutiKuv92dSz6kP7rYLmyHPyYNLesi5t/aH1gfw==} - peerDependencies: - react: ^16.3.0 - dependencies: - '@babel/runtime': 7.26.0 - prop-types: 15.8.1 - react: 18.2.0 - warning: 4.0.3 - dev: false - - /react-fast-compare@3.2.2: - resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} - - /react-helmet@6.1.0(react@18.2.0): - resolution: {integrity: sha512-4uMzEY9nlDlgxr61NL3XbKRy1hEkXmKNXhjbAIOVw5vcFrsdYbH2FEwcNyWvWinl103nXgzYNlns9ca+8kFiWw==} - peerDependencies: - react: '>=16.3.0' - dependencies: - object-assign: 4.1.1 - prop-types: 15.8.1 - react: 18.2.0 - react-fast-compare: 3.2.2 - react-side-effect: 2.1.2(react@18.2.0) - dev: false - - /react-hook-form@7.52.2(react@18.2.0): - resolution: {integrity: sha512-pqfPEbERnxxiNMPd0bzmt1tuaPcVccywFDpyk2uV5xCIBphHV5T8SVnX9/o3kplPE1zzKt77+YIoq+EMwJp56A==} - engines: {node: '>=18.0.0'} - peerDependencies: - react: ^16.8.0 || ^17 || ^18 || ^19 - dependencies: - react: 18.2.0 - dev: false - - /react-hot-toast@2.4.1(csstype@3.1.3)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==} - engines: {node: '>=10'} - peerDependencies: - react: '>=16' - react-dom: '>=16' - dependencies: - goober: 2.1.14(csstype@3.1.3) - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - transitivePeerDependencies: - - csstype - dev: false - - /react-i18next@14.1.2(i18next@22.5.1)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-FSIcJy6oauJbGEXfhUgVeLzvWBhIBIS+/9c6Lj4niwKZyGaGb4V4vUbATXSlsHJDXXB+ociNxqFNiFuV1gmoqg==} - peerDependencies: - i18next: '>= 23.2.3' - react: '>= 16.8.0' - react-dom: '*' - react-native: '*' - peerDependenciesMeta: - react-dom: - optional: true - react-native: - optional: true - dependencies: - '@babel/runtime': 7.24.1 - html-parse-stringify: 3.0.1 - i18next: 22.5.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /react-is@16.13.1: - resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - - /react-is@17.0.2: - resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - - /react-is@18.2.0: - resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} - - /react-is@19.0.0: - resolution: {integrity: sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==} - dev: false - - /react-katex@3.0.1(prop-types@15.8.1)(react@18.2.0): - resolution: {integrity: sha512-wIUW1fU5dHlkKvq4POfDkHruQsYp3fM8xNb/jnc8dnQ+nNCnaj0sx5pw7E6UyuEdLRyFKK0HZjmXBo+AtXXy0A==} - peerDependencies: - prop-types: ^15.8.1 - react: '>=15.3.2 <=18' - dependencies: - katex: 0.16.10 - prop-types: 15.8.1 - react: 18.2.0 - dev: false - - /react-lifecycles-compat@3.0.4: - resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==} - dev: false - - /react-measure@2.5.2(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-M+rpbTLWJ3FD6FXvYV6YEGvQ5tMayQ3fGrZhRPHrE9bVlBYfDCLuDcgNttYfk8IqfOI03jz6cbpqMRTUclQnaA==} - peerDependencies: - react: '>0.13.0' - react-dom: '>0.13.0' - dependencies: - '@babel/runtime': 7.24.1 - get-node-dimensions: 1.2.1 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - resize-observer-polyfill: 1.5.1 - dev: false - - /react-onclickoutside@6.13.1(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-LdrrxK/Yh9zbBQdFbMTXPp3dTSN9B+9YJQucdDu3JNKRrbdU+H+/TVONJoWtOwy4II8Sqf1y/DTI6w/vGPYW0w==} - peerDependencies: - react: ^15.5.x || ^16.x || ^17.x || ^18.x - react-dom: ^15.5.x || ^16.x || ^17.x || ^18.x - dependencies: - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /react-overlays@5.2.1(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-GLLSOLWr21CqtJn8geSwQfoJufdt3mfdsnIiQswouuQ2MMPns+ihZklxvsTDKD3cR2tF8ELbi5xUsvqVhR6WvA==} - peerDependencies: - react: '>=16.3.0' - react-dom: '>=16.3.0' - dependencies: - '@babel/runtime': 7.24.6 - '@popperjs/core': 2.11.8 - '@restart/hooks': 0.4.16(react@18.2.0) - '@types/warning': 3.0.3 - dom-helpers: 5.2.1 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - uncontrollable: 7.2.1(react@18.2.0) - warning: 4.0.3 - dev: false - - /react-popper@2.3.0(@popperjs/core@2.11.8)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==} - peerDependencies: - '@popperjs/core': ^2.0.0 - react: ^16.8.0 || ^17 || ^18 - react-dom: ^16.8.0 || ^17 || ^18 - dependencies: - '@popperjs/core': 2.11.8 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-fast-compare: 3.2.2 - warning: 4.0.3 - - /react-redux@7.2.9(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ==} - peerDependencies: - react: ^16.8.3 || ^17 || ^18 - react-dom: '*' - react-native: '*' - peerDependenciesMeta: - react-dom: - optional: true - react-native: - optional: true - dependencies: - '@babel/runtime': 7.24.6 - '@types/react-redux': 7.1.33 - hoist-non-react-statics: 3.3.2 - loose-envify: 1.4.0 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-is: 17.0.2 - dev: false - - /react-redux@8.1.3(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1): - resolution: {integrity: sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw==} - peerDependencies: - '@types/react': ^16.8 || ^17.0 || ^18.0 - '@types/react-dom': ^16.8 || ^17.0 || ^18.0 - react: ^16.8 || ^17.0 || ^18.0 - react-dom: ^16.8 || ^17.0 || ^18.0 - react-native: '>=0.59' - redux: ^4 || ^5.0.0-beta.0 - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - react-dom: - optional: true - react-native: - optional: true - redux: - optional: true - dependencies: - '@babel/runtime': 7.24.1 - '@types/hoist-non-react-statics': 3.3.5 - '@types/react': 18.2.66 - '@types/react-dom': 18.2.22 - '@types/use-sync-external-store': 0.0.3 - hoist-non-react-statics: 3.3.2 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-is: 18.2.0 - redux: 4.2.1 - use-sync-external-store: 1.2.2(react@18.2.0) - dev: false - - /react-refresh@0.14.0: - resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==} - engines: {node: '>=0.10.0'} - dev: true - - /react-remove-scroll-bar@2.3.8(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@types/react': 18.2.66 - react: 18.2.0 - react-style-singleton: 2.2.3(@types/react@18.2.66)(react@18.2.0) - tslib: 2.6.2 - dev: false - - /react-remove-scroll@2.6.2(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-KmONPx5fnlXYJQqC62Q+lwIeAk64ws/cUw6omIumRzMRPqgnYqhSSti99nbj0Ry13bv7dF+BKn7NB+OqkdZGTw==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@types/react': 18.2.66 - react: 18.2.0 - react-remove-scroll-bar: 2.3.8(@types/react@18.2.66)(react@18.2.0) - react-style-singleton: 2.2.3(@types/react@18.2.66)(react@18.2.0) - tslib: 2.6.2 - use-callback-ref: 1.3.3(@types/react@18.2.66)(react@18.2.0) - use-sidecar: 1.1.3(@types/react@18.2.66)(react@18.2.0) - dev: false - - /react-router-dom@6.23.1(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-utP+K+aSTtEdbWpC+4gxhdlPFwuEfDKq8ZrPFU65bbRJY+l706qjR7yaidBpo3MSeA/fzwbXWbKBI6ftOnP3OQ==} - engines: {node: '>=14.0.0'} - peerDependencies: - react: '>=16.8' - react-dom: '>=16.8' - dependencies: - '@remix-run/router': 1.16.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-router: 6.23.1(react@18.2.0) - dev: false - - /react-router@6.23.1(react@18.2.0): - resolution: {integrity: sha512-fzcOaRF69uvqbbM7OhvQyBTFDVrrGlsFdS3AL+1KfIBtGETibHzi3FkoTRyiDJnWNc2VxrfvR+657ROHjaNjqQ==} - engines: {node: '>=14.0.0'} - peerDependencies: - react: '>=16.8' - dependencies: - '@remix-run/router': 1.16.1 - react: 18.2.0 - dev: false - - /react-side-effect@2.1.2(react@18.2.0): - resolution: {integrity: sha512-PVjOcvVOyIILrYoyGEpDN3vmYNLdy1CajSFNt4TDsVQC5KpTijDvWVoR+/7Rz2xT978D8/ZtFceXxzsPwZEDvw==} - peerDependencies: - react: ^16.3.0 || ^17.0.0 || ^18.0.0 - dependencies: - react: 18.2.0 - dev: false - - /react-style-singleton@2.2.3(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@types/react': 18.2.66 - get-nonce: 1.0.1 - react: 18.2.0 - tslib: 2.6.2 - dev: false - - /react-swipeable-views-core@0.14.0: - resolution: {integrity: sha512-0W/e9uPweNEOSPjmYtuKSC/SvKKg1sfo+WtPdnxeLF3t2L82h7jjszuOHz9C23fzkvLfdgkaOmcbAxE9w2GEjA==} - engines: {node: '>=6.0.0'} - dependencies: - '@babel/runtime': 7.0.0 - warning: 4.0.3 - dev: false - - /react-swipeable-views-utils@0.14.0(react@18.2.0): - resolution: {integrity: sha512-W+fXBOsDqgFK1/g7MzRMVcDurp3LqO3ksC8UgInh2P/tKgb5DusuuB1geKHFc6o1wKl+4oyER4Zh3Lxmr8xbXA==} - engines: {node: '>=6.0.0'} - dependencies: - '@babel/runtime': 7.0.0 - keycode: 2.2.1 - prop-types: 15.8.1 - react-event-listener: 0.6.6(react@18.2.0) - react-swipeable-views-core: 0.14.0 - shallow-equal: 1.2.1 - transitivePeerDependencies: - - react - dev: false - - /react-swipeable-views@0.14.0(react@18.2.0): - resolution: {integrity: sha512-wrTT6bi2nC3JbmyNAsPXffUXLn0DVT9SbbcFr36gKpbaCgEp7rX/OFxsu5hPc/NBsUhHyoSRGvwqJNNrWTwCww==} - engines: {node: '>=6.0.0'} - peerDependencies: - react: ^15.3.0 || ^16.0.0 || ^17.0.0 - dependencies: - '@babel/runtime': 7.0.0 - prop-types: 15.8.1 - react: 18.2.0 - react-swipeable-views-core: 0.14.0 - react-swipeable-views-utils: 0.14.0(react@18.2.0) - warning: 4.0.3 - dev: false - - /react-transition-group@4.4.5(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} - peerDependencies: - react: '>=16.6.0' - react-dom: '>=16.6.0' - dependencies: - '@babel/runtime': 7.24.1 - dom-helpers: 5.2.1 - loose-envify: 1.4.0 - prop-types: 15.8.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /react-virtualized-auto-sizer@1.0.24(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-3kCn7N9NEb3FlvJrSHWGQ4iVl+ydQObq2fHMn12i5wbtm74zHOPhz/i64OL3c1S1vi9i2GXtZqNqUJTQ+BnNfg==} - peerDependencies: - react: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0 - react-dom: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0 - dependencies: - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /react-vtree@2.0.4(@types/react-window@1.8.8)(react-dom@18.2.0)(react-window@1.8.10)(react@18.2.0): - resolution: {integrity: sha512-UOld0VqyAZrryF06K753X4bcEVN6/wW831exvVlMZeZAVHk9KXnlHs4rpqDAeoiBgUwJqoW/rtn0hwsokRRxPA==} - peerDependencies: - '@types/react-window': ^1.8.2 - react: ^16.13.1 - react-dom: ^16.13.1 - react-window: ^1.8.5 - dependencies: - '@babel/runtime': 7.24.1 - '@types/react-window': 1.8.8 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - react-window: 1.8.10(react-dom@18.2.0)(react@18.2.0) - dev: false - - /react-window@1.8.10(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-Y0Cx+dnU6NLa5/EvoHukUD0BklJ8qITCtVEPY1C/nL8wwoZ0b5aEw8Ff1dOVHw7fCzMt55XfJDd8S8W8LCaUCg==} - engines: {node: '>8.0.0'} - peerDependencies: - react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 - react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 - dependencies: - '@babel/runtime': 7.24.1 - memoize-one: 5.2.1 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /react-zoom-pan-pinch@3.6.1(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-SdPqdk7QDSV7u/WulkFOi+cnza8rEZ0XX4ZpeH7vx3UZEg7DoyuAy3MCmm+BWv/idPQL2Oe73VoC0EhfCN+sZQ==} - engines: {node: '>=8', npm: '>=5'} - peerDependencies: - react: '*' - react-dom: '*' - dependencies: - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /react18-input-otp@1.1.4(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-35xvmTeuPWIxd0Z0Opx4z3OoMaTmKN4ubirQCx1YMZiNoe+2h1hsOSUco4aKPlGXWZCtXrfOFieAh46vqiK9mA==} - peerDependencies: - react: 16.2.0 - 18 - react-dom: 16.2.0 - 18 - dependencies: - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - dev: false - - /react@18.2.0: - resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} - engines: {node: '>=0.10.0'} - dependencies: - loose-envify: 1.4.0 - - /reactcss@1.2.3(react@18.2.0): - resolution: {integrity: sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==} - peerDependencies: - react: '*' - dependencies: - lodash: 4.17.21 - react: 18.2.0 - dev: false - - /read-cache@1.0.0: - resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} - dependencies: - pify: 2.3.0 - - /readable-stream@2.3.8: - resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} - dependencies: - core-util-is: 1.0.3 - inherits: 2.0.4 - isarray: 1.0.0 - process-nextick-args: 2.0.1 - safe-buffer: 5.1.2 - string_decoder: 1.1.1 - util-deprecate: 1.0.2 - dev: true - - /readable-stream@4.5.2: - resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - abort-controller: 3.0.0 - buffer: 6.0.3 - events: 3.3.0 - process: 0.11.10 - string_decoder: 1.3.0 - dev: true - - /readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - dependencies: - picomatch: 2.3.1 - - /readdirp@4.0.2: - resolution: {integrity: sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==} - engines: {node: '>= 14.16.0'} - dev: false - - /real-require@0.2.0: - resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} - engines: {node: '>= 12.13.0'} - dev: true - - /redux-thunk@3.1.0(redux@5.0.1): - resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} - peerDependencies: - redux: ^5.0.0 - dependencies: - redux: 5.0.1 - dev: false - - /redux@4.2.1: - resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==} - dependencies: - '@babel/runtime': 7.24.1 - dev: false - - /redux@5.0.1: - resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} - dev: false - - /regenerate-unicode-properties@10.1.1: - resolution: {integrity: sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==} - engines: {node: '>=4'} - dependencies: - regenerate: 1.4.2 - dev: true - - /regenerate@1.4.2: - resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} - dev: true - - /regenerator-runtime@0.12.1: - resolution: {integrity: sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==} - dev: false - - /regenerator-runtime@0.14.1: - resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} - - /regenerator-transform@0.15.2: - resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==} - dependencies: - '@babel/runtime': 7.26.0 - dev: true - - /regexp.prototype.flags@1.5.2: - resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-errors: 1.3.0 - set-function-name: 2.0.2 - - /regexpu-core@5.3.2: - resolution: {integrity: sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==} - engines: {node: '>=4'} - dependencies: - '@babel/regjsgen': 0.8.0 - regenerate: 1.4.2 - regenerate-unicode-properties: 10.1.1 - regjsparser: 0.9.1 - unicode-match-property-ecmascript: 2.0.0 - unicode-match-property-value-ecmascript: 2.1.0 - dev: true - - /regjsparser@0.9.1: - resolution: {integrity: sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==} - hasBin: true - dependencies: - jsesc: 0.5.0 - dev: true - - /rehype-minify-whitespace@6.0.2: - resolution: {integrity: sha512-Zk0pyQ06A3Lyxhe9vGtOtzz3Z0+qZ5+7icZ/PL/2x1SHPbKao5oB/g/rlc6BCTajqBb33JcOe71Ye1oFsuYbnw==} - dependencies: - '@types/hast': 3.0.4 - hast-util-minify-whitespace: 1.0.1 - dev: false - - /rehype-parse@9.0.1: - resolution: {integrity: sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==} - dependencies: - '@types/hast': 3.0.4 - hast-util-from-html: 2.0.3 - unified: 11.0.5 - dev: false - - /relateurl@0.2.7: - resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==} - engines: {node: '>= 0.10'} - dev: true - - /release-zalgo@1.0.0: - resolution: {integrity: sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==} - engines: {node: '>=4'} - dependencies: - es6-error: 4.1.1 - dev: true - - /request-progress@3.0.0: - resolution: {integrity: sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==} - dependencies: - throttleit: 1.0.1 - dev: true - - /request@2.88.2: - resolution: {integrity: sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==} - engines: {node: '>= 6'} - deprecated: request has been deprecated, see https://github.com/request/request/issues/3142 - dependencies: - aws-sign2: 0.7.0 - aws4: 1.12.0 - caseless: 0.12.0 - combined-stream: 1.0.8 - extend: 3.0.2 - forever-agent: 0.6.1 - form-data: 2.3.3 - har-validator: 5.1.5 - http-signature: 1.2.0 - is-typedarray: 1.0.0 - isstream: 0.1.2 - json-stringify-safe: 5.0.1 - mime-types: 2.1.35 - oauth-sign: 0.9.0 - performance-now: 2.1.0 - qs: 6.5.3 - safe-buffer: 5.1.2 - tough-cookie: 2.5.0 - tunnel-agent: 0.6.0 - uuid: 3.4.0 - dev: false - - /require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - - /require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} - dev: true - - /require-main-filename@2.0.0: - resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} - dev: true - - /requires-port@1.0.0: - resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} - dev: true - - /reselect@5.1.0: - resolution: {integrity: sha512-aw7jcGLDpSgNDyWBQLv2cedml85qd95/iszJjN988zX1t7AVRJi19d9kto5+W7oCfQ94gyo40dVbT6g2k4/kXg==} - dev: false - - /resize-observer-polyfill@1.5.1: - resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} - dev: false - - /resolve-cwd@3.0.0: - resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} - engines: {node: '>=8'} - dependencies: - resolve-from: 5.0.0 - - /resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - - /resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - - /resolve.exports@2.0.2: - resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} - engines: {node: '>=10'} - - /resolve@1.22.8: - resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} - hasBin: true - dependencies: - is-core-module: 2.13.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - - /resolve@2.0.0-next.5: - resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} - hasBin: true - dependencies: - is-core-module: 2.13.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - dev: true - - /restore-cursor@3.1.0: - resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} - engines: {node: '>=8'} - dependencies: - onetime: 5.1.2 - signal-exit: 3.0.7 - dev: true - - /retry@0.13.1: - resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} - engines: {node: '>= 4'} - dev: false - - /reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - - /rfdc@1.3.1: - resolution: {integrity: sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==} - dev: true - - /rimraf@2.7.1: - resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} - hasBin: true - dependencies: - glob: 7.2.3 - dev: true - - /rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - hasBin: true - dependencies: - glob: 7.2.3 - dev: true - - /robust-predicates@3.0.2: - resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} - dev: false - - /rollup-plugin-visualizer@5.12.0: - resolution: {integrity: sha512-8/NU9jXcHRs7Nnj07PF2o4gjxmm9lXIrZ8r175bT9dK8qoLlvKTwRMArRCMgpMGlq8CTLugRvEmyMeMXIU2pNQ==} - engines: {node: '>=14'} - hasBin: true - peerDependencies: - rollup: 2.x || 3.x || 4.x - peerDependenciesMeta: - rollup: - optional: true - dependencies: - open: 8.4.2 - picomatch: 2.3.1 - source-map: 0.7.4 - yargs: 17.7.2 - dev: true - - /rollup@4.13.2: - resolution: {integrity: sha512-MIlLgsdMprDBXC+4hsPgzWUasLO9CE4zOkj/u6j+Z6j5A4zRY+CtiXAdJyPtgCsc42g658Aeh1DlrdVEJhsL2g==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - dependencies: - '@types/estree': 1.0.5 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.13.2 - '@rollup/rollup-android-arm64': 4.13.2 - '@rollup/rollup-darwin-arm64': 4.13.2 - '@rollup/rollup-darwin-x64': 4.13.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.13.2 - '@rollup/rollup-linux-arm64-gnu': 4.13.2 - '@rollup/rollup-linux-arm64-musl': 4.13.2 - '@rollup/rollup-linux-powerpc64le-gnu': 4.13.2 - '@rollup/rollup-linux-riscv64-gnu': 4.13.2 - '@rollup/rollup-linux-s390x-gnu': 4.13.2 - '@rollup/rollup-linux-x64-gnu': 4.13.2 - '@rollup/rollup-linux-x64-musl': 4.13.2 - '@rollup/rollup-win32-arm64-msvc': 4.13.2 - '@rollup/rollup-win32-ia32-msvc': 4.13.2 - '@rollup/rollup-win32-x64-msvc': 4.13.2 - fsevents: 2.3.3 - - /roughjs@4.6.6: - resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} - dependencies: - hachure-fill: 0.5.2 - path-data-parser: 0.1.0 - points-on-curve: 0.2.0 - points-on-path: 0.2.1 - dev: false - - /run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - dependencies: - queue-microtask: 1.2.3 - - /rw@1.3.3: - resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} - dev: false - - /rxjs@7.8.0: - resolution: {integrity: sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==} - dependencies: - tslib: 2.6.2 - - /safe-array-concat@1.1.2: - resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} - engines: {node: '>=0.4'} - dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 - has-symbols: 1.0.3 - isarray: 2.0.5 - dev: true - - /safe-buffer@5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - - /safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - dev: true - - /safe-regex-test@1.0.3: - resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - is-regex: 1.1.4 - dev: true - - /safe-stable-stringify@2.4.3: - resolution: {integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==} - engines: {node: '>=10'} - dev: true - - /safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - - /sass@1.77.2: - resolution: {integrity: sha512-eb4GZt1C3avsX3heBNlrc7I09nyT00IUuo4eFhAbeXWU2fvA7oXI53SxODVAA+zgZCk9aunAZgO+losjR3fAwA==} - engines: {node: '>=14.0.0'} - hasBin: true - dependencies: - chokidar: 3.6.0 - immutable: 4.3.6 - source-map-js: 1.2.0 - - /sass@1.83.0: - resolution: {integrity: sha512-qsSxlayzoOjdvXMVLkzF84DJFc2HZEL/rFyGIKbbilYtAvlCxyuzUeff9LawTn4btVnLKg75Z8MMr1lxU1lfGw==} - engines: {node: '>=14.0.0'} - hasBin: true - dependencies: - chokidar: 4.0.2 - immutable: 5.0.3 - source-map-js: 1.2.1 - optionalDependencies: - '@parcel/watcher': 2.5.0 - dev: false - - /saxes@6.0.0: - resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} - engines: {node: '>=v12.22.7'} - dependencies: - xmlchars: 2.2.0 - dev: true - - /scheduler@0.23.0: - resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} - dependencies: - loose-envify: 1.4.0 - - /schema-utils@3.3.0: - resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} - engines: {node: '>= 10.13.0'} - dependencies: - '@types/json-schema': 7.0.15 - ajv: 6.12.6 - ajv-keywords: 3.5.2(ajv@6.12.6) - dev: true - - /schema-utils@4.2.0: - resolution: {integrity: sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==} - engines: {node: '>= 12.13.0'} - dependencies: - '@types/json-schema': 7.0.15 - ajv: 8.14.0 - ajv-formats: 2.1.1(ajv@8.14.0) - ajv-keywords: 5.1.0(ajv@8.14.0) - dev: true - - /scroll-into-view-if-needed@3.1.0: - resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==} - dependencies: - compute-scroll-into-view: 3.1.0 - dev: false - - /secure-json-parse@2.7.0: - resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} - dev: true - - /semver@5.7.2: - resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} - hasBin: true - dev: true - - /semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - - /semver@7.6.0: - resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} - engines: {node: '>=10'} - hasBin: true - dependencies: - lru-cache: 6.0.0 - - /sentence-case@3.0.4: - resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==} - dependencies: - no-case: 3.0.4 - tslib: 2.6.2 - upper-case-first: 2.0.2 - dev: true - - /serialize-javascript@6.0.2: - resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} - dependencies: - randombytes: 2.1.0 - dev: true - - /set-blocking@2.0.0: - resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - dev: true - - /set-function-length@1.2.2: - resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} - engines: {node: '>= 0.4'} - dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - function-bind: 1.1.2 - get-intrinsic: 1.2.4 - gopd: 1.0.1 - has-property-descriptors: 1.0.2 - - /set-function-name@2.0.2: - resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} - engines: {node: '>= 0.4'} - dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - functions-have-names: 1.2.3 - has-property-descriptors: 1.0.2 - - /shallow-clone@3.0.1: - resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==} - engines: {node: '>=8'} - dependencies: - kind-of: 6.0.3 - dev: true - - /shallow-equal@1.2.1: - resolution: {integrity: sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA==} - dev: false - - /shebang-command@1.2.0: - resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==} - engines: {node: '>=0.10.0'} - dependencies: - shebang-regex: 1.0.0 - dev: true - - /shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - dependencies: - shebang-regex: 3.0.0 - - /shebang-regex@1.0.0: - resolution: {integrity: sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==} - engines: {node: '>=0.10.0'} - dev: true - - /shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - /side-channel@1.0.6: - resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - get-intrinsic: 1.2.4 - object-inspect: 1.13.1 - dev: true - - /signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - - /signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - - /sirv@2.0.4: - resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} - engines: {node: '>= 10'} - dependencies: - '@polka/url': 1.0.0-next.25 - mrmime: 2.0.0 - totalist: 3.0.1 - dev: true - - /sisteransi@1.0.5: - resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - - /slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - - /slate-history@0.100.0(slate@0.101.5): - resolution: {integrity: sha512-x5rUuWLNtH97hs9PrFovGgt3Qc5zkTm/5mcUB+0NR/TK923eLax4HsL6xACLHMs245nI6aJElyM1y6hN0y5W/Q==} - peerDependencies: - slate: '>=0.65.3' - dependencies: - is-plain-object: 5.0.0 - slate: 0.101.5 - dev: false - - /slate-react@0.101.6(react-dom@18.2.0)(react@18.2.0)(slate@0.101.5): - resolution: {integrity: sha512-aMtp9FY127hKWTkCcTBonfKIwKJC2ESPqFdw2o/RuOk3RMQRwsWay8XTOHx8OBGOHanI2fsKaTAPF5zxOLA1Qg==} - peerDependencies: - react: '>=18.2.0' - react-dom: '>=18.2.0' - slate: '>=0.99.0' - dependencies: - '@juggle/resize-observer': 3.4.0 - '@types/is-hotkey': 0.1.10 - '@types/lodash': 4.17.0 - direction: 1.0.4 - is-hotkey: 0.2.0 - is-plain-object: 5.0.0 - lodash: 4.17.21 - react: 18.2.0 - react-dom: 18.2.0(react@18.2.0) - scroll-into-view-if-needed: 3.1.0 - slate: 0.101.5 - tiny-invariant: 1.3.1 - dev: false - - /slate@0.101.5: - resolution: {integrity: sha512-ZZt1ia8ayRqxtpILRMi2a4MfdvwdTu64CorxTVq9vNSd0GQ/t3YDkze6wKjdeUtENmBlq5wNIDInZbx38Hfu5Q==} - dependencies: - immer: 10.1.1 - is-plain-object: 5.0.0 - tiny-warning: 1.0.3 - dev: false - - /slice-ansi@3.0.0: - resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} - engines: {node: '>=8'} - dependencies: - ansi-styles: 4.3.0 - astral-regex: 2.0.0 - is-fullwidth-code-point: 3.0.0 - dev: true - - /slice-ansi@4.0.0: - resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} - engines: {node: '>=10'} - dependencies: - ansi-styles: 4.3.0 - astral-regex: 2.0.0 - is-fullwidth-code-point: 3.0.0 - dev: true - - /smooth-scroll-into-view-if-needed@2.0.2: - resolution: {integrity: sha512-z54WzUSlM+xHHvJu3lMIsh+1d1kA4vaakcAtQvqzeGJ5Ffau7EKjpRrMHh1/OBo5zyU2h30ZYEt77vWmPHqg7Q==} - dependencies: - scroll-into-view-if-needed: 3.1.0 - dev: false - - /snake-case@3.0.4: - resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} - dependencies: - dot-case: 3.0.4 - tslib: 2.6.2 - dev: true - - /sonic-boom@4.0.1: - resolution: {integrity: sha512-hTSD/6JMLyT4r9zeof6UtuBDpjJ9sO08/nmS5djaA9eozT9oOlNdpXSnzcgj4FTqpk3nkLrs61l4gip9r1HCrQ==} - dependencies: - atomic-sleep: 1.0.0 - dev: true - - /source-map-js@1.2.0: - resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} - engines: {node: '>=0.10.0'} - - /source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - /source-map-support@0.5.13: - resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 - - /source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 - dev: true - - /source-map@0.5.7: - resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} - engines: {node: '>=0.10.0'} - dev: false - - /source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - - /source-map@0.7.4: - resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} - engines: {node: '>= 8'} - dev: true - - /sourcemap-codec@1.4.8: - resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} - deprecated: Please use @jridgewell/sourcemap-codec instead - dev: true - - /space-separated-tokens@2.0.2: - resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} - dev: false - - /spawn-wrap@2.0.0: - resolution: {integrity: sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==} - engines: {node: '>=8'} - dependencies: - foreground-child: 2.0.0 - is-windows: 1.0.2 - make-dir: 3.1.0 - rimraf: 3.0.2 - signal-exit: 3.0.7 - which: 2.0.2 - dev: true - - /split2@4.2.0: - resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} - engines: {node: '>= 10.x'} - dev: true - - /sprintf-js@1.0.3: - resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} - - /sshpk@1.18.0: - resolution: {integrity: sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==} - engines: {node: '>=0.10.0'} - hasBin: true - dependencies: - asn1: 0.2.6 - assert-plus: 1.0.0 - bcrypt-pbkdf: 1.0.2 - dashdash: 1.14.1 - ecc-jsbn: 0.1.2 - getpass: 0.1.7 - jsbn: 0.1.1 - safer-buffer: 2.1.2 - tweetnacl: 0.14.5 - - /ssim.js@3.5.0: - resolution: {integrity: sha512-Aj6Jl2z6oDmgYFFbQqK7fght19bXdOxY7Tj03nF+03M9gCBAjeIiO8/PlEGMfKDwYpw4q6iBqVq2YuREorGg/g==} - dev: true - - /stack-utils@2.0.6: - resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} - engines: {node: '>=10'} - dependencies: - escape-string-regexp: 2.0.0 - - /stream-shift@1.0.3: - resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} - dev: true - - /streamx@2.16.1: - resolution: {integrity: sha512-m9QYj6WygWyWa3H1YY69amr4nVgy61xfjys7xO7kviL5rfIEc2naf+ewFiOA+aEJD7y0JO3h2GoiUv4TDwEGzQ==} - dependencies: - fast-fifo: 1.3.2 - queue-tick: 1.0.1 - optionalDependencies: - bare-events: 2.2.2 - dev: true - - /string-length@4.0.2: - resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} - engines: {node: '>=10'} - dependencies: - char-regex: 1.0.2 - strip-ansi: 6.0.1 - - /string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - /string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.0 - - /string.prototype.matchall@4.0.11: - resolution: {integrity: sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-errors: 1.3.0 - es-object-atoms: 1.0.0 - get-intrinsic: 1.2.4 - gopd: 1.0.1 - has-symbols: 1.0.3 - internal-slot: 1.0.7 - regexp.prototype.flags: 1.5.2 - set-function-name: 2.0.2 - side-channel: 1.0.6 - dev: true - - /string.prototype.trim@1.2.9: - resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-abstract: 1.23.3 - es-object-atoms: 1.0.0 - dev: true - - /string.prototype.trimend@1.0.8: - resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-object-atoms: 1.0.0 - dev: true - - /string.prototype.trimstart@1.0.8: - resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - define-properties: 1.2.1 - es-object-atoms: 1.0.0 - dev: true - - /string_decoder@1.1.1: - resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} - dependencies: - safe-buffer: 5.1.2 - dev: true - - /string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - dependencies: - safe-buffer: 5.2.1 - dev: true - - /stringify-entities@4.0.4: - resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} - dependencies: - character-entities-html4: 2.1.0 - character-entities-legacy: 3.0.0 - dev: false - - /strip-ansi@3.0.1: - resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==} - engines: {node: '>=0.10.0'} - dependencies: - ansi-regex: 2.1.1 - dev: true - - /strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - dependencies: - ansi-regex: 5.0.1 - - /strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} - engines: {node: '>=12'} - dependencies: - ansi-regex: 6.0.1 - - /strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} - dev: true - - /strip-bom@4.0.0: - resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} - engines: {node: '>=8'} - - /strip-eof@1.0.0: - resolution: {integrity: sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==} - engines: {node: '>=0.10.0'} - dev: true - - /strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - - /strip-json-comments@2.0.1: - resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} - engines: {node: '>=0.10.0'} - dev: true - - /strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - - /style-dictionary@3.9.2: - resolution: {integrity: sha512-M2pcQ6hyRtqHOh+NyT6T05R3pD/gwNpuhREBKvxC1En0vyywx+9Wy9nXWT1SZ9ePzv1vAo65ItnpA16tT9ZUCg==} - engines: {node: '>=12.0.0'} - hasBin: true - dependencies: - chalk: 4.1.2 - change-case: 4.1.2 - commander: 8.3.0 - fs-extra: 10.1.0 - glob: 10.3.12 - json5: 2.2.3 - jsonc-parser: 3.2.1 - lodash: 4.17.21 - tinycolor2: 1.6.0 - dev: true - - /stylis@4.2.0: - resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} - dev: false - - /stylis@4.3.4: - resolution: {integrity: sha512-osIBl6BGUmSfDkyH2mB7EFvCJntXDrLhKjHTRj/rK6xLH0yuPrHULDRQzKokSOD4VoorhtKpfcfW1GAntu8now==} - dev: false - - /sucrase@3.35.0: - resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} - engines: {node: '>=16 || 14 >=14.17'} - hasBin: true - dependencies: - '@jridgewell/gen-mapping': 0.3.5 - commander: 4.1.1 - glob: 10.3.12 - lines-and-columns: 1.2.4 - mz: 2.7.0 - pirates: 4.0.6 - ts-interface-checker: 0.1.13 - dev: false - - /supports-color@2.0.0: - resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==} - engines: {node: '>=0.8.0'} - dev: true - - /supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} - dependencies: - has-flag: 3.0.0 - - /supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - dependencies: - has-flag: 4.0.0 - - /supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} - dependencies: - has-flag: 4.0.0 - - /supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - - /svg-parser@2.0.4: - resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} - dev: true - - /svgo@3.2.0: - resolution: {integrity: sha512-4PP6CMW/V7l/GmKRKzsLR8xxjdHTV4IMvhTnpuHwwBazSIlw5W/5SmPjN8Dwyt7lKbSJrRDgp4t9ph0HgChFBQ==} - engines: {node: '>=14.0.0'} - hasBin: true - dependencies: - '@trysound/sax': 0.2.0 - commander: 7.2.0 - css-select: 5.1.0 - css-tree: 2.3.1 - css-what: 6.1.0 - csso: 5.0.5 - picocolors: 1.0.0 - dev: true - - /symbol-tree@3.2.4: - resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} - dev: true - - /tabbable@6.2.0: - resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} - dev: false - - /tailwind-merge@2.5.5: - resolution: {integrity: sha512-0LXunzzAZzo0tEPxV3I297ffKZPlKDrjj7NXphC8V5ak9yHC5zRmxnOe2m/Rd/7ivsOMJe3JZ2JVocoDdQTRBA==} - dev: false - - /tailwindcss-animate@1.0.7(tailwindcss@3.4.17): - resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} - peerDependencies: - tailwindcss: '>=3.0.0 || insiders' - dependencies: - tailwindcss: 3.4.17 - dev: false - - /tailwindcss@3.2.7(postcss@8.4.21): - resolution: {integrity: sha512-B6DLqJzc21x7wntlH/GsZwEXTBttVSl1FtCzC8WP4oBc/NKef7kaax5jeihkkCEWc831/5NDJ9gRNDK6NEioQQ==} - engines: {node: '>=12.13.0'} - hasBin: true - peerDependencies: - postcss: ^8.0.9 - dependencies: - arg: 5.0.2 - chokidar: 3.6.0 - color-name: 1.1.4 - detective: 5.2.1 - didyoumean: 1.2.2 - dlv: 1.1.3 - fast-glob: 3.3.2 - glob-parent: 6.0.2 - is-glob: 4.0.3 - lilconfig: 2.1.0 - micromatch: 4.0.5 - normalize-path: 3.0.0 - object-hash: 3.0.0 - picocolors: 1.0.0 - postcss: 8.4.21 - postcss-import: 14.1.0(postcss@8.4.21) - postcss-js: 4.0.1(postcss@8.4.21) - postcss-load-config: 3.1.4(postcss@8.4.21) - postcss-nested: 6.0.0(postcss@8.4.21) - postcss-selector-parser: 6.0.16 - postcss-value-parser: 4.2.0 - quick-lru: 5.1.1 - resolve: 1.22.8 - transitivePeerDependencies: - - ts-node - dev: true - - /tailwindcss@3.4.17: - resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==} - engines: {node: '>=14.0.0'} - hasBin: true - dependencies: - '@alloc/quick-lru': 5.2.0 - arg: 5.0.2 - chokidar: 3.6.0 - didyoumean: 1.2.2 - dlv: 1.1.3 - fast-glob: 3.3.2 - glob-parent: 6.0.2 - is-glob: 4.0.3 - jiti: 1.21.7 - lilconfig: 3.1.3 - micromatch: 4.0.8 - normalize-path: 3.0.0 - object-hash: 3.0.0 - picocolors: 1.1.1 - postcss: 8.4.49 - postcss-import: 15.1.0(postcss@8.4.49) - postcss-js: 4.0.1(postcss@8.4.49) - postcss-load-config: 4.0.2(postcss@8.4.49) - postcss-nested: 6.2.0(postcss@8.4.49) - postcss-selector-parser: 6.1.2 - resolve: 1.22.8 - sucrase: 3.35.0 - transitivePeerDependencies: - - ts-node - dev: false - - /tapable@2.2.1: - resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} - engines: {node: '>=6'} - dev: true - - /tar-stream@3.1.7: - resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} - dependencies: - b4a: 1.6.6 - fast-fifo: 1.3.2 - streamx: 2.16.1 - dev: true - - /term-img@4.1.0: - resolution: {integrity: sha512-DFpBhaF5j+2f7kheKFc1ajsAUUDGOaNPpKPtiIMxlbfud6mvfFZuWGnTRpaujUa5J7yl6cIw/h6nyr4mSsENPg==} - engines: {node: '>=8'} - dependencies: - ansi-escapes: 4.3.2 - iterm2-version: 4.2.0 - dev: true - - /terser-webpack-plugin@5.3.10(webpack@5.91.0): - resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} - engines: {node: '>= 10.13.0'} - peerDependencies: - '@swc/core': '*' - esbuild: '*' - uglify-js: '*' - webpack: ^5.1.0 - peerDependenciesMeta: - '@swc/core': - optional: true - esbuild: - optional: true - uglify-js: - optional: true - dependencies: - '@jridgewell/trace-mapping': 0.3.25 - jest-worker: 27.5.1 - schema-utils: 3.3.0 - serialize-javascript: 6.0.2 - terser: 5.31.0 - webpack: 5.91.0 - dev: true - - /terser@5.31.0: - resolution: {integrity: sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==} - engines: {node: '>=10'} - hasBin: true - dependencies: - '@jridgewell/source-map': 0.3.6 - acorn: 8.11.3 - commander: 2.20.3 - source-map-support: 0.5.21 - dev: true - - /test-exclude@6.0.0: - resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} - engines: {node: '>=8'} - dependencies: - '@istanbuljs/schema': 0.1.3 - glob: 7.2.3 - minimatch: 3.1.2 - - /text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - dev: true - - /thenify-all@1.6.0: - resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} - engines: {node: '>=0.8'} - dependencies: - thenify: 3.3.1 - dev: false - - /thenify@3.3.1: - resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - dependencies: - any-promise: 1.3.0 - dev: false - - /thread-stream@3.1.0: - resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} - dependencies: - real-require: 0.2.0 - dev: true - - /throttleit@1.0.1: - resolution: {integrity: sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==} - dev: true - - /through2@2.0.5: - resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} - dependencies: - readable-stream: 2.3.8 - xtend: 4.0.2 - dev: true - - /through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - - /tiny-invariant@1.3.1: - resolution: {integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==} - dev: false - - /tiny-invariant@1.3.3: - resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} - dev: false - - /tiny-warning@1.0.3: - resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} - dev: false - - /tinycolor2@1.6.0: - resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} - - /tinyexec@0.3.1: - resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==} - dev: false - - /tmp@0.2.3: - resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==} - engines: {node: '>=14.14'} - dev: true - - /tmpl@1.0.5: - resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} - - /to-camel-case@1.0.0: - resolution: {integrity: sha512-nD8pQi5H34kyu1QDMFjzEIYqk0xa9Alt6ZfrdEMuHCFOfTLhDG5pgTu/aAM9Wt9lXILwlXmWP43b8sav0GNE8Q==} - dependencies: - to-space-case: 1.0.0 - dev: false - - /to-fast-properties@2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} - engines: {node: '>=4'} - - /to-no-case@1.0.2: - resolution: {integrity: sha512-Z3g735FxuZY8rodxV4gH7LxClE4H0hTIyHNIHdk+vpQxjLm0cwnKXq/OFVZ76SOQmto7txVcwSCwkU5kqp+FKg==} - dev: false - - /to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - dependencies: - is-number: 7.0.0 - - /to-space-case@1.0.0: - resolution: {integrity: sha512-rLdvwXZ39VOn1IxGL3V6ZstoTbwLRckQmn/U8ZDLuWwIXNpuZDhQ3AiRUlhTbOXFVE9C+dR51wM0CBDhk31VcA==} - dependencies: - to-no-case: 1.0.2 - dev: false - - /totalist@3.0.1: - resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} - engines: {node: '>=6'} - dev: true - - /tough-cookie@2.5.0: - resolution: {integrity: sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==} - engines: {node: '>=0.8'} - dependencies: - psl: 1.9.0 - punycode: 2.3.1 - dev: false - - /tough-cookie@4.1.3: - resolution: {integrity: sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==} - engines: {node: '>=6'} - dependencies: - psl: 1.9.0 - punycode: 2.3.1 - universalify: 0.2.0 - url-parse: 1.5.10 - dev: true - - /tr46@3.0.0: - resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==} - engines: {node: '>=12'} - dependencies: - punycode: 2.3.1 - dev: true - - /tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true - dev: true - - /trim-lines@3.0.1: - resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} - dev: false - - /trim-trailing-lines@2.1.0: - resolution: {integrity: sha512-5UR5Biq4VlVOtzqkm2AZlgvSlDJtME46uV0br0gENbwN4l5+mMKT4b9gJKqWtuL2zAIqajGJGuvbCbcAJUZqBg==} - dev: false - - /trough@2.2.0: - resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} - dev: false - - /ts-api-utils@1.3.0(typescript@4.9.5): - resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} - engines: {node: '>=16'} - peerDependencies: - typescript: '>=4.2.0' - dependencies: - typescript: 4.9.5 - dev: true - - /ts-dedent@2.2.0: - resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} - engines: {node: '>=6.10'} - dev: false - - /ts-interface-checker@0.1.13: - resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} - dev: false - - /ts-jest@29.1.1(@babel/core@7.24.3)(babel-jest@29.6.2)(jest@29.5.0)(typescript@4.9.5): - resolution: {integrity: sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - hasBin: true - peerDependencies: - '@babel/core': '>=7.0.0-beta.0 <8' - '@jest/types': ^29.0.0 - babel-jest: ^29.0.0 - esbuild: '*' - jest: ^29.0.0 - typescript: '>=4.3 <6' - peerDependenciesMeta: - '@babel/core': - optional: true - '@jest/types': - optional: true - babel-jest: - optional: true - esbuild: - optional: true - dependencies: - '@babel/core': 7.24.3 - babel-jest: 29.6.2(@babel/core@7.24.3) - bs-logger: 0.2.6 - fast-json-stable-stringify: 2.1.0 - jest: 29.5.0(@types/node@20.11.30) - jest-util: 29.7.0 - json5: 2.2.3 - lodash.memoize: 4.1.2 - make-error: 1.3.6 - semver: 7.6.0 - typescript: 4.9.5 - yargs-parser: 21.1.1 - dev: true - - /ts-node-dev@2.0.0(@types/node@20.11.30)(typescript@4.9.5): - resolution: {integrity: sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==} - engines: {node: '>=0.8.0'} - hasBin: true - peerDependencies: - node-notifier: '*' - typescript: '*' - peerDependenciesMeta: - node-notifier: - optional: true - dependencies: - chokidar: 3.6.0 - dynamic-dedupe: 0.3.0 - minimist: 1.2.8 - mkdirp: 1.0.4 - resolve: 1.22.8 - rimraf: 2.7.1 - source-map-support: 0.5.21 - tree-kill: 1.2.2 - ts-node: 10.9.2(@types/node@20.11.30)(typescript@4.9.5) - tsconfig: 7.0.0 - typescript: 4.9.5 - transitivePeerDependencies: - - '@swc/core' - - '@swc/wasm' - - '@types/node' - dev: true - - /ts-node@10.9.2(@types/node@20.11.30)(typescript@4.9.5): - resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} - hasBin: true - peerDependencies: - '@swc/core': '>=1.2.50' - '@swc/wasm': '>=1.2.50' - '@types/node': '*' - typescript: '>=2.7' - peerDependenciesMeta: - '@swc/core': - optional: true - '@swc/wasm': - optional: true - dependencies: - '@cspotcode/source-map-support': 0.8.1 - '@tsconfig/node10': 1.0.11 - '@tsconfig/node12': 1.0.11 - '@tsconfig/node14': 1.0.3 - '@tsconfig/node16': 1.0.4 - '@types/node': 20.11.30 - acorn: 8.11.3 - acorn-walk: 8.3.2 - arg: 4.1.3 - create-require: 1.1.1 - diff: 4.0.2 - make-error: 1.3.6 - typescript: 4.9.5 - v8-compile-cache-lib: 3.0.1 - yn: 3.1.1 - dev: true - - /ts-results@3.3.0: - resolution: {integrity: sha512-FWqxGX2NHp5oCyaMd96o2y2uMQmSu8Dey6kvyuFdRJ2AzfmWo3kWa4UsPlCGlfQ/qu03m09ZZtppMoY8EMHuiA==} - dev: false - - /tsconfig-paths-jest@0.0.1: - resolution: {integrity: sha512-YKhUKqbteklNppC2NqL7dv1cWF8eEobgHVD5kjF1y9Q4ocqpBiaDlYslQ9eMhtbqIPRrA68RIEXqknEjlxdwxw==} - dev: true - - /tsconfig@7.0.0: - resolution: {integrity: sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==} - dependencies: - '@types/strip-bom': 3.0.0 - '@types/strip-json-comments': 0.0.30 - strip-bom: 3.0.0 - strip-json-comments: 2.0.1 - dev: true - - /tslib@2.6.2: - resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - - /tunnel-agent@0.6.0: - resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} - dependencies: - safe-buffer: 5.1.2 - - /tweetnacl@0.14.5: - resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} - - /type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - dependencies: - prelude-ls: 1.2.1 - dev: true - - /type-detect@4.0.8: - resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} - engines: {node: '>=4'} - - /type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - dev: true - - /type-fest@0.21.3: - resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} - engines: {node: '>=10'} - - /type-fest@0.8.1: - resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} - engines: {node: '>=8'} - dev: true - - /typed-array-buffer@1.0.2: - resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - es-errors: 1.3.0 - is-typed-array: 1.1.13 - dev: true - - /typed-array-byte-length@1.0.1: - resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - for-each: 0.3.3 - gopd: 1.0.1 - has-proto: 1.0.3 - is-typed-array: 1.1.13 - dev: true - - /typed-array-byte-offset@1.0.2: - resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==} - engines: {node: '>= 0.4'} - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.7 - for-each: 0.3.3 - gopd: 1.0.1 - has-proto: 1.0.3 - is-typed-array: 1.1.13 - dev: true - - /typed-array-length@1.0.6: - resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} - engines: {node: '>= 0.4'} - dependencies: - call-bind: 1.0.7 - for-each: 0.3.3 - gopd: 1.0.1 - has-proto: 1.0.3 - is-typed-array: 1.1.13 - possible-typed-array-names: 1.0.0 - dev: true - - /typedarray-to-buffer@3.1.5: - resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} - dependencies: - is-typedarray: 1.0.0 - dev: true - - /typescript@4.9.5: - resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} - engines: {node: '>=4.2.0'} - hasBin: true - dev: true - - /ufo@1.5.3: - resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==} - dev: true - - /ufo@1.5.4: - resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} - dev: false - - /unbox-primitive@1.0.2: - resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} - dependencies: - call-bind: 1.0.7 - has-bigints: 1.0.2 - has-symbols: 1.0.3 - which-boxed-primitive: 1.0.2 - dev: true - - /uncontrollable@7.2.1(react@18.2.0): - resolution: {integrity: sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==} - peerDependencies: - react: '>=15.0.0' - dependencies: - '@babel/runtime': 7.24.6 - '@types/react': 18.2.66 - invariant: 2.2.4 - react: 18.2.0 - react-lifecycles-compat: 3.0.4 - dev: false - - /undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - - /unicode-canonical-property-names-ecmascript@2.0.0: - resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} - engines: {node: '>=4'} - dev: true - - /unicode-match-property-ecmascript@2.0.0: - resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} - engines: {node: '>=4'} - dependencies: - unicode-canonical-property-names-ecmascript: 2.0.0 - unicode-property-aliases-ecmascript: 2.1.0 - dev: true - - /unicode-match-property-value-ecmascript@2.1.0: - resolution: {integrity: sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==} - engines: {node: '>=4'} - dev: true - - /unicode-property-aliases-ecmascript@2.1.0: - resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==} - engines: {node: '>=4'} - dev: true - - /unified@11.0.5: - resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} - dependencies: - '@types/unist': 3.0.3 - bail: 2.0.2 - devlop: 1.1.0 - extend: 3.0.2 - is-plain-obj: 4.1.0 - trough: 2.2.0 - vfile: 6.0.3 - dev: false - - /uniq@1.0.1: - resolution: {integrity: sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA==} - dev: false - - /unist-util-find-after@5.0.0: - resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} - dependencies: - '@types/unist': 3.0.3 - unist-util-is: 6.0.0 - dev: false - - /unist-util-is@6.0.0: - resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} - dependencies: - '@types/unist': 3.0.3 - dev: false - - /unist-util-position@5.0.0: - resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} - dependencies: - '@types/unist': 3.0.3 - dev: false - - /unist-util-stringify-position@4.0.0: - resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} - dependencies: - '@types/unist': 3.0.3 - dev: false - - /unist-util-visit-parents@6.0.1: - resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} - dependencies: - '@types/unist': 3.0.3 - unist-util-is: 6.0.0 - dev: false - - /unist-util-visit@5.0.0: - resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} - dependencies: - '@types/unist': 3.0.3 - unist-util-is: 6.0.0 - unist-util-visit-parents: 6.0.1 - dev: false - - /unist@0.0.1: - resolution: {integrity: sha512-bnzuF8b6d47WubA4a5yLqFbuZz/v/NS6eRwUIdOaDmsqzwTlyv8yS1g3M7ISdtBQrigPD3qKK87Cu7zhEfCF3A==} - deprecated: Use @types/unist instead - dev: false - - /universalify@0.1.2: - resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} - engines: {node: '>= 4.0.0'} - dev: true - - /universalify@0.2.0: - resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} - engines: {node: '>= 4.0.0'} - dev: true - - /universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} - dev: true - - /unsplash-js@7.0.19: - resolution: {integrity: sha512-j6qT2floy5Q2g2d939FJpwey1yw/GpQecFiSouyJtsHQPj3oqmqq3K4rI+GF8vU1zwGCT7ZwIGQd2dtCQLjYJw==} - engines: {node: '>=10'} - dev: false - - /untildify@4.0.0: - resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} - engines: {node: '>=8'} - dev: true - - /update-browserslist-db@1.0.13(browserslist@4.23.0): - resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - dependencies: - browserslist: 4.23.0 - escalade: 3.1.2 - picocolors: 1.1.1 - - /upper-case-first@2.0.2: - resolution: {integrity: sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==} - dependencies: - tslib: 2.6.2 - dev: true - - /upper-case@2.0.2: - resolution: {integrity: sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==} - dependencies: - tslib: 2.6.2 - dev: true - - /uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - dependencies: - punycode: 2.3.1 - - /url-parse@1.5.10: - resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} - dependencies: - querystringify: 2.2.0 - requires-port: 1.0.0 - dev: true - - /use-callback-ref@1.3.3(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@types/react': 18.2.66 - react: 18.2.0 - tslib: 2.6.2 - dev: false - - /use-memo-one@1.1.3(react@18.2.0): - resolution: {integrity: sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - dependencies: - react: 18.2.0 - dev: false - - /use-sidecar@1.1.3(@types/react@18.2.66)(react@18.2.0): - resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - dependencies: - '@types/react': 18.2.66 - detect-node-es: 1.1.0 - react: 18.2.0 - tslib: 2.6.2 - dev: false - - /use-sync-external-store@1.2.2(react@18.2.0): - resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - dependencies: - react: 18.2.0 - dev: false - - /utf8@3.0.0: - resolution: {integrity: sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==} - dev: false - - /util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - /uuid@3.4.0: - resolution: {integrity: sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==} - deprecated: Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details. - hasBin: true - dev: false - - /uuid@8.3.2: - resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} - hasBin: true - dev: true - - /uuid@9.0.0: - resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==} - hasBin: true - dev: true - - /uuid@9.0.1: - resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} - hasBin: true - dev: false - - /v8-compile-cache-lib@3.0.1: - resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} - dev: true - - /v8-to-istanbul@9.2.0: - resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==} - engines: {node: '>=10.12.0'} - dependencies: - '@jridgewell/trace-mapping': 0.3.25 - '@types/istanbul-lib-coverage': 2.0.6 - convert-source-map: 2.0.0 - - /validator@13.12.0: - resolution: {integrity: sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==} - engines: {node: '>= 0.10'} - dev: false - - /verror@1.10.0: - resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} - engines: {'0': node >=0.6.0} - dependencies: - assert-plus: 1.0.0 - core-util-is: 1.0.2 - extsprintf: 1.3.0 - - /vfile-location@5.0.3: - resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} - dependencies: - '@types/unist': 3.0.3 - vfile: 6.0.3 - dev: false - - /vfile-message@4.0.2: - resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} - dependencies: - '@types/unist': 3.0.3 - unist-util-stringify-position: 4.0.0 - dev: false - - /vfile@6.0.3: - resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} - dependencies: - '@types/unist': 3.0.3 - vfile-message: 4.0.2 - dev: false - - /vite-plugin-compression2@1.0.0: - resolution: {integrity: sha512-42XNp6FjxE0JIecxj1fdi770pLhYm3MJhBUAod9EszTgDg9C4LDOgBzWcj/0K52KfJrpRXwUsWV6kqTDuoCfLA==} - dependencies: - '@rollup/pluginutils': 5.1.0 - gunzip-maybe: 1.4.2 - tar-stream: 3.1.7 - transitivePeerDependencies: - - rollup - dev: true - - /vite-plugin-externals@0.6.2(vite@5.2.0): - resolution: {integrity: sha512-R5oVY8xDJjLXLTs2XDYzvYbc/RTZuIwOx2xcFbYf+/VXB6eJuatDgt8jzQ7kZ+IrgwQhe6tU8U2fTyy72C25CQ==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - vite: '>=2.0.0' - dependencies: - acorn: 8.11.3 - es-module-lexer: 0.4.1 - fs-extra: 10.1.0 - magic-string: 0.25.9 - vite: 5.2.0(@types/node@20.11.30)(sass@1.77.2) - dev: true - - /vite-plugin-html@3.2.2(vite@5.2.0): - resolution: {integrity: sha512-vb9C9kcdzcIo/Oc3CLZVS03dL5pDlOFuhGlZYDCJ840BhWl/0nGeZWf3Qy7NlOayscY4Cm/QRgULCQkEZige5Q==} - peerDependencies: - vite: '>=2.0.0' - dependencies: - '@rollup/pluginutils': 4.2.1 - colorette: 2.0.20 - connect-history-api-fallback: 1.6.0 - consola: 2.15.3 - dotenv: 16.4.5 - dotenv-expand: 8.0.3 - ejs: 3.1.10 - fast-glob: 3.3.2 - fs-extra: 10.1.0 - html-minifier-terser: 6.1.0 - node-html-parser: 5.4.2 - pathe: 0.2.0 - vite: 5.2.0(@types/node@20.11.30)(sass@1.77.2) - dev: true - - /vite-plugin-importer@0.2.5: - resolution: {integrity: sha512-6OtqJmVwnfw8+B4OIh7pIdXs+jLkN7g5PIqmZdpgrMYjIFMiZrcMB1zlyUQSTokKGC90KwXviO/lq1hcUBUG3Q==} - dependencies: - '@babel/core': 7.24.3 - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.3) - babel-plugin-import: 1.13.8 - transitivePeerDependencies: - - supports-color - dev: true - - /vite-plugin-istanbul@6.0.2(vite@5.2.0): - resolution: {integrity: sha512-0/sKwjEEIwbEyl43xX7onX3dIbMJAsigNsKyyVPalG1oRFo5jn3qkJbS2PUfp9wrr3piy1eT6qRoeeum2p4B2A==} - peerDependencies: - vite: '>=4 <=6' - dependencies: - '@istanbuljs/load-nyc-config': 1.1.0 - espree: 10.0.1 - istanbul-lib-instrument: 6.0.2 - picocolors: 1.0.0 - source-map: 0.7.4 - test-exclude: 6.0.0 - vite: 5.2.0(@types/node@20.11.30)(sass@1.77.2) - transitivePeerDependencies: - - supports-color - dev: true - - /vite-plugin-svgr@3.2.0(typescript@4.9.5)(vite@5.2.0): - resolution: {integrity: sha512-Uvq6niTvhqJU6ga78qLKBFJSDvxWhOnyfQSoKpDPMAGxJPo5S3+9hyjExE5YDj6Lpa4uaLkGc1cBgxXov+LjSw==} - peerDependencies: - vite: ^2.6.0 || 3 || 4 - dependencies: - '@rollup/pluginutils': 5.1.0 - '@svgr/core': 7.0.0(typescript@4.9.5) - '@svgr/plugin-jsx': 7.0.0 - vite: 5.2.0(@types/node@20.11.30)(sass@1.77.2) - transitivePeerDependencies: - - rollup - - supports-color - - typescript - dev: true - - /vite-plugin-terminal@1.2.0(vite@5.2.0): - resolution: {integrity: sha512-IIw1V+IySth8xlrGmH4U7YmfTp681vTzYpa7b8A3KNCJ2oW1BGPPwW8tSz6BQTvSgbRmrP/9NsBLsfXkN4e8sA==} - engines: {node: '>=14'} - peerDependencies: - vite: ^2.0.0||^3.0.0||^4.0.0||^5.0.0 - dependencies: - '@rollup/plugin-strip': 3.0.4 - debug: 4.3.4(supports-color@8.1.1) - kolorist: 1.8.0 - sirv: 2.0.4 - ufo: 1.5.3 - vite: 5.2.0(@types/node@20.11.30)(sass@1.77.2) - transitivePeerDependencies: - - rollup - - supports-color - dev: true - - /vite-plugin-total-bundle-size@1.0.7(vite@5.2.0): - resolution: {integrity: sha512-ritAi5hRcuNonHP1wquvzqkZHGpOqRpWiMoEQQDJ3DLYuuVAS3THKyIGv7QSGig5nT+xuMYTLUamBu3Legaipg==} - peerDependencies: - vite: '>=5.0.0' - dependencies: - chalk: 5.3.0 - vite: 5.2.0(@types/node@20.11.30)(sass@1.77.2) - dev: true - - /vite-plugin-wasm@3.3.0(vite@5.2.0): - resolution: {integrity: sha512-tVhz6w+W9MVsOCHzxo6SSMSswCeIw4HTrXEi6qL3IRzATl83jl09JVO1djBqPSwfjgnpVHNLYcaMbaDX5WB/pg==} - peerDependencies: - vite: ^2 || ^3 || ^4 || ^5 - dependencies: - vite: 5.2.0(@types/node@20.11.30)(sass@1.77.2) - dev: false - - /vite@5.2.0(@types/node@20.11.30)(sass@1.77.2): - resolution: {integrity: sha512-xMSLJNEjNk/3DJRgWlPADDwaU9AgYRodDH2t6oENhJnIlmU9Hx1Q6VpjyXua/JdMw1WJRbnAgHJ9xgET9gnIAg==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 - less: '*' - lightningcss: ^1.21.0 - sass: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - dependencies: - '@types/node': 20.11.30 - esbuild: 0.20.2 - postcss: 8.4.38 - rollup: 4.13.2 - sass: 1.77.2 - optionalDependencies: - fsevents: 2.3.3 - - /void-elements@3.1.0: - resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==} - engines: {node: '>=0.10.0'} - dev: false - - /vscode-jsonrpc@8.2.0: - resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} - engines: {node: '>=14.0.0'} - dev: false - - /vscode-languageserver-protocol@3.17.5: - resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} - dependencies: - vscode-jsonrpc: 8.2.0 - vscode-languageserver-types: 3.17.5 - dev: false - - /vscode-languageserver-textdocument@1.0.12: - resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} - dev: false - - /vscode-languageserver-types@3.17.5: - resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} - dev: false - - /vscode-languageserver@9.0.1: - resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} - hasBin: true - dependencies: - vscode-languageserver-protocol: 3.17.5 - dev: false - - /vscode-uri@3.0.8: - resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} - dev: false - - /w3c-xmlserializer@4.0.0: - resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==} - engines: {node: '>=14'} - dependencies: - xml-name-validator: 4.0.0 - dev: true - - /walker@1.0.8: - resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} - dependencies: - makeerror: 1.0.12 - - /warning@4.0.3: - resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==} - dependencies: - loose-envify: 1.4.0 - - /watchpack@2.4.1: - resolution: {integrity: sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==} - engines: {node: '>=10.13.0'} - dependencies: - glob-to-regexp: 0.4.1 - graceful-fs: 4.2.11 - dev: true - - /web-namespaces@2.0.1: - resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} - dev: false - - /webidl-conversions@7.0.0: - resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} - engines: {node: '>=12'} - dev: true - - /webpack-sources@3.2.3: - resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} - engines: {node: '>=10.13.0'} - dev: true - - /webpack@5.91.0: - resolution: {integrity: sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==} - engines: {node: '>=10.13.0'} - hasBin: true - peerDependencies: - webpack-cli: '*' - peerDependenciesMeta: - webpack-cli: - optional: true - dependencies: - '@types/eslint-scope': 3.7.7 - '@types/estree': 1.0.5 - '@webassemblyjs/ast': 1.12.1 - '@webassemblyjs/wasm-edit': 1.12.1 - '@webassemblyjs/wasm-parser': 1.12.1 - acorn: 8.14.0 - acorn-import-assertions: 1.9.0(acorn@8.14.0) - browserslist: 4.23.0 - chrome-trace-event: 1.0.4 - enhanced-resolve: 5.16.1 - es-module-lexer: 1.5.3 - eslint-scope: 5.1.1 - events: 3.3.0 - glob-to-regexp: 0.4.1 - graceful-fs: 4.2.11 - json-parse-even-better-errors: 2.3.1 - loader-runner: 4.3.0 - mime-types: 2.1.35 - neo-async: 2.6.2 - schema-utils: 3.3.0 - tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(webpack@5.91.0) - watchpack: 2.4.1 - webpack-sources: 3.2.3 - transitivePeerDependencies: - - '@swc/core' - - esbuild - - uglify-js - dev: true - - /whatwg-encoding@2.0.0: - resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} - engines: {node: '>=12'} - dependencies: - iconv-lite: 0.6.3 - dev: true - - /whatwg-mimetype@3.0.0: - resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} - engines: {node: '>=12'} - dev: true - - /whatwg-url@11.0.0: - resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==} - engines: {node: '>=12'} - dependencies: - tr46: 3.0.0 - webidl-conversions: 7.0.0 - dev: true - - /which-boxed-primitive@1.0.2: - resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} - dependencies: - is-bigint: 1.0.4 - is-boolean-object: 1.1.2 - is-number-object: 1.0.7 - is-string: 1.0.7 - is-symbol: 1.0.4 - dev: true - - /which-module@2.0.1: - resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} - dev: true - - /which-typed-array@1.1.15: - resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} - engines: {node: '>= 0.4'} - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.7 - for-each: 0.3.3 - gopd: 1.0.1 - has-tostringtag: 1.0.2 - dev: true - - /which@1.3.1: - resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} - hasBin: true - dependencies: - isexe: 2.0.0 - dev: true - - /which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - dependencies: - isexe: 2.0.0 - - /wrap-ansi@6.2.0: - resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} - engines: {node: '>=8'} - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - dev: true - - /wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - /wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - dependencies: - ansi-styles: 6.2.1 - string-width: 5.1.2 - strip-ansi: 7.1.0 - - /wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - /write-file-atomic@3.0.3: - resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} - dependencies: - imurmurhash: 0.1.4 - is-typedarray: 1.0.0 - signal-exit: 3.0.7 - typedarray-to-buffer: 3.1.5 - dev: true - - /write-file-atomic@4.0.2: - resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - dependencies: - imurmurhash: 0.1.4 - signal-exit: 3.0.7 - - /ws@8.16.0: - resolution: {integrity: sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - dev: true - - /xml-name-validator@4.0.0: - resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} - engines: {node: '>=12'} - dev: true - - /xmlbuilder@15.1.1: - resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==} - engines: {node: '>=8.0'} - dev: true - - /xmlchars@2.2.0: - resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} - dev: true - - /xtend@4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} - dev: true - - /y-indexeddb@9.0.12(yjs@14.0.0-1): - resolution: {integrity: sha512-9oCFRSPPzBK7/w5vOkJBaVCQZKHXB/v6SIT+WYhnJxlEC61juqG0hBrAf+y3gmSMLFLwICNH9nQ53uscuse6Hg==} - engines: {node: '>=16.0.0', npm: '>=8.0.0'} - peerDependencies: - yjs: ^13.0.0 - dependencies: - lib0: 0.2.94 - yjs: 14.0.0-1 - dev: false - - /y-protocols@1.0.6(yjs@14.0.0-1): - resolution: {integrity: sha512-vHRF2L6iT3rwj1jub/K5tYcTT/mEYDUppgNPXwp8fmLpui9f7Yeq3OEtTLVF012j39QnV+KEQpNqoN7CWU7Y9Q==} - engines: {node: '>=16.0.0', npm: '>=8.0.0'} - peerDependencies: - yjs: ^13.0.0 - dependencies: - lib0: 0.2.94 - yjs: 14.0.0-1 - dev: false - - /y18n@4.0.3: - resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} - dev: true - - /y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - - /yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - - /yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - - /yaml@1.10.2: - resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} - engines: {node: '>= 6'} - - /yaml@2.6.1: - resolution: {integrity: sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==} - engines: {node: '>= 14'} - hasBin: true - dev: false - - /yargs-parser@18.1.3: - resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} - engines: {node: '>=6'} - dependencies: - camelcase: 5.3.1 - decamelize: 1.2.0 - dev: true - - /yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} - - /yargs@15.4.1: - resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} - engines: {node: '>=8'} - dependencies: - cliui: 6.0.0 - decamelize: 1.2.0 - find-up: 4.1.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - require-main-filename: 2.0.0 - set-blocking: 2.0.0 - string-width: 4.2.3 - which-module: 2.0.1 - y18n: 4.0.3 - yargs-parser: 18.1.3 - dev: true - - /yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} - dependencies: - cliui: 8.0.1 - escalade: 3.1.2 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 21.1.1 - - /yauzl@2.10.0: - resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} - dependencies: - buffer-crc32: 0.2.13 - fd-slicer: 1.1.0 - dev: true - - /yjs@14.0.0-1: - resolution: {integrity: sha512-w0iJlEx+XvkvPkdBH0L8pb4Da2DvTEA7UdDl/dOFCQfA0siT4cUtbJ8LfoiliH2juYFqdIoqxbScHakKBiIv0g==} - requiresBuild: true - dependencies: - lib0: 0.2.94 - dev: false - - /yn@3.1.1: - resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} - engines: {node: '>=6'} - dev: true - - /yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - - /yocto-queue@1.0.0: - resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} - engines: {node: '>=12.20'} - dev: true - - /zwitch@2.0.4: - resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} - dev: false - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false diff --git a/frontend/appflowy_web_app/postcss.config.cjs b/frontend/appflowy_web_app/postcss.config.cjs deleted file mode 100644 index 12a703d900..0000000000 --- a/frontend/appflowy_web_app/postcss.config.cjs +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -}; diff --git a/frontend/appflowy_web_app/public/.well-known/apple-app-site-association b/frontend/appflowy_web_app/public/.well-known/apple-app-site-association deleted file mode 100644 index ae5a32410b..0000000000 --- a/frontend/appflowy_web_app/public/.well-known/apple-app-site-association +++ /dev/null @@ -1,25 +0,0 @@ -{ - "applinks": { - "apps": [], - "details": [ - { - "appIDs": [ - "VHB67HRSZG.com.appflowy.appflowy.flutter" - ], - "paths": [ - "*" - ], - "components": [ - { - "/": "/*" - } - ] - } - ] - }, - "webcredentials": { - "apps": [ - "VHB67HRSZG.com.appflowy.appflowy.flutter" - ] - } -} \ No newline at end of file diff --git a/frontend/appflowy_web_app/public/.well-known/assetlinks.json b/frontend/appflowy_web_app/public/.well-known/assetlinks.json deleted file mode 100644 index 9161081308..0000000000 --- a/frontend/appflowy_web_app/public/.well-known/assetlinks.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "relation": [ - "delegate_permission/common.handle_all_urls" - ], - "target": { - "namespace": "android_app", - "package_name": "io.appflowy.appflowy", - "sha256_cert_fingerprints": [ - "19:13:85:33:DB:B3:A2:FD:65:2F:61:D7:F2:35:95:79:FE:6E:CC:B5:AC:94:AA:02:9E:BE:E7:0E:02:6B:45:FF" - ] - } - } -] \ No newline at end of file diff --git a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-chip-spark.svg b/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-chip-spark.svg deleted file mode 100644 index 57bb666ba9..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-chip-spark.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-cloud-spark.svg b/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-cloud-spark.svg deleted file mode 100644 index 385aaf5a03..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-cloud-spark.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-edit-spark.svg b/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-edit-spark.svg deleted file mode 100644 index 96fddfc558..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-edit-spark.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-email-generator-spark.svg b/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-email-generator-spark.svg deleted file mode 100644 index 8238d69442..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-email-generator-spark.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-gaming-spark.svg b/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-gaming-spark.svg deleted file mode 100644 index a74a6eabb0..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-gaming-spark.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-generate-landscape-image-spark.svg b/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-generate-landscape-image-spark.svg deleted file mode 100644 index 0759443d47..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-generate-landscape-image-spark.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-generate-music-spark.svg b/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-generate-music-spark.svg deleted file mode 100644 index 98adcabbe6..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-generate-music-spark.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-generate-portrait-image-spark.svg b/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-generate-portrait-image-spark.svg deleted file mode 100644 index ebd118dd62..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-generate-portrait-image-spark.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-generate-variation-spark.svg b/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-generate-variation-spark.svg deleted file mode 100644 index c4400c215a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-generate-variation-spark.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-navigation-spark.svg b/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-navigation-spark.svg deleted file mode 100644 index 943e8354bd..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-navigation-spark.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-network-spark.svg b/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-network-spark.svg deleted file mode 100644 index ec21b6dc52..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-network-spark.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-prompt-spark.svg b/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-prompt-spark.svg deleted file mode 100644 index ecfcb6ad63..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-prompt-spark.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-redo-spark.svg b/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-redo-spark.svg deleted file mode 100644 index d67e5e3f1f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-redo-spark.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-science-spark.svg b/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-science-spark.svg deleted file mode 100644 index e9a0af9957..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-science-spark.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-settings-spark.svg b/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-settings-spark.svg deleted file mode 100644 index c0a1d6588b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-settings-spark.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-technology-spark.svg b/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-technology-spark.svg deleted file mode 100644 index 27b27f152a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-technology-spark.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-upscale-spark.svg b/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-upscale-spark.svg deleted file mode 100644 index 91975ee23c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-upscale-spark.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-vehicle-spark-1.svg b/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-vehicle-spark-1.svg deleted file mode 100644 index 48f3eda8dd..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/ai-vehicle-spark-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/artificial-intelligence-spark.svg b/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/artificial-intelligence-spark.svg deleted file mode 100644 index c4c7907937..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/artificial_intelligence/artificial-intelligence-spark.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/VPN-connection.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/VPN-connection.svg deleted file mode 100644 index c8f7a2fcb0..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/VPN-connection.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/adobe.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/adobe.svg deleted file mode 100644 index 877de4e094..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/adobe.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/alt.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/alt.svg deleted file mode 100644 index 6e08a0f9a3..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/alt.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/amazon.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/amazon.svg deleted file mode 100644 index d02e8a27c2..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/amazon.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/android.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/android.svg deleted file mode 100644 index acc983a1a7..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/android.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/app-store.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/app-store.svg deleted file mode 100644 index 59f17cc197..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/app-store.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/apple.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/apple.svg deleted file mode 100644 index 94bfcf6cec..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/apple.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/asterisk-1.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/asterisk-1.svg deleted file mode 100644 index c6b49a655d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/asterisk-1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/battery-alert-1.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/battery-alert-1.svg deleted file mode 100644 index dbfd40fefb..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/battery-alert-1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/battery-charging.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/battery-charging.svg deleted file mode 100644 index 22aa568e4b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/battery-charging.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/battery-empty-1.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/battery-empty-1.svg deleted file mode 100644 index d64921afe2..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/battery-empty-1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/battery-empty-2.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/battery-empty-2.svg deleted file mode 100644 index d7bac48cc8..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/battery-empty-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/battery-full-1.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/battery-full-1.svg deleted file mode 100644 index 4c7e68f5b5..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/battery-full-1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/battery-low-1.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/battery-low-1.svg deleted file mode 100644 index 6524eb3300..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/battery-low-1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/battery-medium-1.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/battery-medium-1.svg deleted file mode 100644 index 4620aa3da4..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/battery-medium-1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/bluetooth-disabled.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/bluetooth-disabled.svg deleted file mode 100644 index 47487db565..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/bluetooth-disabled.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/bluetooth-searching.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/bluetooth-searching.svg deleted file mode 100644 index 4535898788..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/bluetooth-searching.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/bluetooth.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/bluetooth.svg deleted file mode 100644 index 281960065f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/bluetooth.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/browser-wifi.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/browser-wifi.svg deleted file mode 100644 index a81eccedf2..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/browser-wifi.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/chrome.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/chrome.svg deleted file mode 100644 index 56fc7af710..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/chrome.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/command.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/command.svg deleted file mode 100644 index 367a6e6117..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/command.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/computer-chip-1.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/computer-chip-1.svg deleted file mode 100644 index 2a70e75274..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/computer-chip-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/computer-chip-2.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/computer-chip-2.svg deleted file mode 100644 index 0ef3150bec..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/computer-chip-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/computer-pc-desktop.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/computer-pc-desktop.svg deleted file mode 100644 index 98bea05196..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/computer-pc-desktop.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/controller-1.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/controller-1.svg deleted file mode 100644 index bf7bab0923..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/controller-1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/controller-wireless.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/controller-wireless.svg deleted file mode 100644 index 53045de283..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/controller-wireless.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/controller.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/controller.svg deleted file mode 100644 index 87ba8122db..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/controller.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/cursor-click.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/cursor-click.svg deleted file mode 100644 index 2ca4ede8d0..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/cursor-click.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/cyborg-2.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/cyborg-2.svg deleted file mode 100644 index f90dbd9ce3..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/cyborg-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/cyborg.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/cyborg.svg deleted file mode 100644 index cbdb10ea87..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/cyborg.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/database-check.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/database-check.svg deleted file mode 100644 index 462f928903..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/database-check.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/database-lock.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/database-lock.svg deleted file mode 100644 index 60ee0c76ba..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/database-lock.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/database-refresh.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/database-refresh.svg deleted file mode 100644 index 0aeb96c499..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/database-refresh.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/database-remove.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/database-remove.svg deleted file mode 100644 index d4e9971017..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/database-remove.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/database-server-1.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/database-server-1.svg deleted file mode 100644 index 0ca3030d20..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/database-server-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/database-server-2.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/database-server-2.svg deleted file mode 100644 index 15196de131..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/database-server-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/database-setting.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/database-setting.svg deleted file mode 100644 index ec6b34e6c1..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/database-setting.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/database-subtract-2-raid-storage-code-disk-programming-database-array-hard-disc-minus.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/database-subtract-2-raid-storage-code-disk-programming-database-array-hard-disc-minus.svg deleted file mode 100644 index e0c0e75c52..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/database-subtract-2-raid-storage-code-disk-programming-database-array-hard-disc-minus.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/database.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/database.svg deleted file mode 100644 index 31f57ca895..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/database.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/delete-keyboard.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/delete-keyboard.svg deleted file mode 100644 index cb25e3d0b7..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/delete-keyboard.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/desktop-chat.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/desktop-chat.svg deleted file mode 100644 index 774d3464f0..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/desktop-chat.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/desktop-check.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/desktop-check.svg deleted file mode 100644 index 3f2f30e2e8..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/desktop-check.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/desktop-code.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/desktop-code.svg deleted file mode 100644 index a4f0873ffc..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/desktop-code.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/desktop-delete.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/desktop-delete.svg deleted file mode 100644 index 45038bb01a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/desktop-delete.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/desktop-dollar.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/desktop-dollar.svg deleted file mode 100644 index 161a456ba0..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/desktop-dollar.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/desktop-emoji.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/desktop-emoji.svg deleted file mode 100644 index dd4cadfd51..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/desktop-emoji.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/desktop-favorite-star.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/desktop-favorite-star.svg deleted file mode 100644 index 276cc7833e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/desktop-favorite-star.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/desktop-game.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/desktop-game.svg deleted file mode 100644 index fa98bc4d46..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/desktop-game.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/desktop-help.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/desktop-help.svg deleted file mode 100644 index d651603e71..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/desktop-help.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/device-database-encryption-1.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/device-database-encryption-1.svg deleted file mode 100644 index 230e5f79a1..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/device-database-encryption-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/discord.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/discord.svg deleted file mode 100644 index 2cb14a8e6c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/discord.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/drone.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/drone.svg deleted file mode 100644 index 8ad4a4f775..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/drone.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/dropbox.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/dropbox.svg deleted file mode 100644 index 89f0cf0b8e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/dropbox.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/eject.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/eject.svg deleted file mode 100644 index acea3c2839..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/eject.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/electric-cord-1.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/electric-cord-1.svg deleted file mode 100644 index ef4bae5915..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/electric-cord-1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/electric-cord-3.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/electric-cord-3.svg deleted file mode 100644 index 59a85fabda..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/electric-cord-3.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/facebook-1.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/facebook-1.svg deleted file mode 100644 index 7687d0331a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/facebook-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/figma.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/figma.svg deleted file mode 100644 index 316aacd34e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/figma.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/floppy-disk.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/floppy-disk.svg deleted file mode 100644 index be1351ba03..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/floppy-disk.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/gmail.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/gmail.svg deleted file mode 100644 index ce9a3c7d36..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/gmail.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/google-drive.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/google-drive.svg deleted file mode 100644 index 521fe55ad8..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/google-drive.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/google.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/google.svg deleted file mode 100644 index 624af07bbb..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/google.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/hand-held-tablet-drawing.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/hand-held-tablet-drawing.svg deleted file mode 100644 index c9117d6916..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/hand-held-tablet-drawing.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/hand-held-tablet-writing.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/hand-held-tablet-writing.svg deleted file mode 100644 index d619e9d69a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/hand-held-tablet-writing.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/hand-held.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/hand-held.svg deleted file mode 100644 index 2cff3d5e04..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/hand-held.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/hard-disk.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/hard-disk.svg deleted file mode 100644 index 46a25c5d5a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/hard-disk.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/hard-drive-1.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/hard-drive-1.svg deleted file mode 100644 index 929887c741..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/hard-drive-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/instagram.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/instagram.svg deleted file mode 100644 index 2a0750b273..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/instagram.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/keyboard-virtual.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/keyboard-virtual.svg deleted file mode 100644 index 914dddf994..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/keyboard-virtual.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/keyboard-wireless-2.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/keyboard-wireless-2.svg deleted file mode 100644 index c3fb38cc92..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/keyboard-wireless-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/keyboard.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/keyboard.svg deleted file mode 100644 index 9a32238860..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/keyboard.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/laptop-charging.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/laptop-charging.svg deleted file mode 100644 index bbc233360c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/laptop-charging.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/linkedin.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/linkedin.svg deleted file mode 100644 index 6ed8fd3d8c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/linkedin.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/local-storage-folder.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/local-storage-folder.svg deleted file mode 100644 index cb0673ab60..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/local-storage-folder.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/meta.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/meta.svg deleted file mode 100644 index d0937137b6..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/meta.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/mouse-wireless-1.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/mouse-wireless-1.svg deleted file mode 100644 index 697fb76677..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/mouse-wireless-1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/mouse-wireless.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/mouse-wireless.svg deleted file mode 100644 index a2c554d6fb..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/mouse-wireless.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/mouse.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/mouse.svg deleted file mode 100644 index 972f69c52f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/mouse.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/netflix.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/netflix.svg deleted file mode 100644 index 6691a31086..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/netflix.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/network.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/network.svg deleted file mode 100644 index d33f91d839..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/network.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/next.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/next.svg deleted file mode 100644 index 1c68f75e7e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/next.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/paypal.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/paypal.svg deleted file mode 100644 index e366f8e86e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/paypal.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/play-store.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/play-store.svg deleted file mode 100644 index c84f1ca4c1..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/play-store.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/printer.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/printer.svg deleted file mode 100644 index 79eefa06a4..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/printer.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/return-2.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/return-2.svg deleted file mode 100644 index 45666d72b4..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/return-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/screen-1.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/screen-1.svg deleted file mode 100644 index e5007b7f5b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/screen-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/screen-2.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/screen-2.svg deleted file mode 100644 index 4b87d934f4..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/screen-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/screen-curve.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/screen-curve.svg deleted file mode 100644 index dc6418c205..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/screen-curve.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/screensaver-monitor-wallpaper.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/screensaver-monitor-wallpaper.svg deleted file mode 100644 index cc0431f192..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/screensaver-monitor-wallpaper.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/shift.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/shift.svg deleted file mode 100644 index 3dfc9de387..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/shift.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/shredder.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/shredder.svg deleted file mode 100644 index f6c9f4bffa..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/shredder.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/signal-loading.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/signal-loading.svg deleted file mode 100644 index d04c5d1c44..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/signal-loading.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/slack.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/slack.svg deleted file mode 100644 index 266a0018c4..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/slack.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/spotify.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/spotify.svg deleted file mode 100644 index f0f0365ae8..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/spotify.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/telegram.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/telegram.svg deleted file mode 100644 index 4bccbe1779..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/telegram.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/tiktok.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/tiktok.svg deleted file mode 100644 index 8f03d36c6b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/tiktok.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/tinder.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/tinder.svg deleted file mode 100644 index ca0e251a7f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/tinder.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/twitter.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/twitter.svg deleted file mode 100644 index f8e13c447c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/twitter.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/usb-drive.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/usb-drive.svg deleted file mode 100644 index 417555a5c3..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/usb-drive.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/virtual-reality.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/virtual-reality.svg deleted file mode 100644 index 6521aa7661..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/virtual-reality.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/voice-mail-off.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/voice-mail-off.svg deleted file mode 100644 index 175d036b30..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/voice-mail-off.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/voice-mail.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/voice-mail.svg deleted file mode 100644 index 78d4bd13b5..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/voice-mail.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/watch-1.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/watch-1.svg deleted file mode 100644 index 54039f5b8e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/watch-1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/watch-2.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/watch-2.svg deleted file mode 100644 index 87f6e84bf8..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/watch-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/watch-circle-charging.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/watch-circle-charging.svg deleted file mode 100644 index 95bf9a6a19..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/watch-circle-charging.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/watch-circle-heartbeat-monitor-1.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/watch-circle-heartbeat-monitor-1.svg deleted file mode 100644 index 7e0a9419ed..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/watch-circle-heartbeat-monitor-1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/watch-circle-heartbeat-monitor-2.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/watch-circle-heartbeat-monitor-2.svg deleted file mode 100644 index 575a4cdaf1..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/watch-circle-heartbeat-monitor-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/watch-circle-menu.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/watch-circle-menu.svg deleted file mode 100644 index f79637bcd5..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/watch-circle-menu.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/watch-circle-time.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/watch-circle-time.svg deleted file mode 100644 index 7b3145988d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/watch-circle-time.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/webcam-video-circle.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/webcam-video-circle.svg deleted file mode 100644 index d583495165..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/webcam-video-circle.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/webcam-video-off.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/webcam-video-off.svg deleted file mode 100644 index 9750416e99..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/webcam-video-off.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/webcam-video.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/webcam-video.svg deleted file mode 100644 index 30407900c1..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/webcam-video.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/webcam.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/webcam.svg deleted file mode 100644 index 67007be1ac..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/webcam.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/whatsapp.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/whatsapp.svg deleted file mode 100644 index bb7da75eb6..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/whatsapp.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/wifi-antenna.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/wifi-antenna.svg deleted file mode 100644 index b41ae562a4..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/wifi-antenna.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/wifi-disabled.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/wifi-disabled.svg deleted file mode 100644 index a561d55e84..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/wifi-disabled.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/wifi-horizontal.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/wifi-horizontal.svg deleted file mode 100644 index 9f0f3f20a6..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/wifi-horizontal.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/wifi-router.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/wifi-router.svg deleted file mode 100644 index d7d9490b1a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/wifi-router.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/wifi.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/wifi.svg deleted file mode 100644 index c6ebd0432c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/wifi.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/computer_devices/windows.svg b/frontend/appflowy_web_app/public/af_icons/computer_devices/windows.svg deleted file mode 100644 index b1923cc5f9..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/computer_devices/windows.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/culture/christian-cross-1.svg b/frontend/appflowy_web_app/public/af_icons/culture/christian-cross-1.svg deleted file mode 100644 index 8dea5f0109..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/culture/christian-cross-1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/culture/christian-cross-2.svg b/frontend/appflowy_web_app/public/af_icons/culture/christian-cross-2.svg deleted file mode 100644 index 4ac9b8ede7..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/culture/christian-cross-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/culture/christianity.svg b/frontend/appflowy_web_app/public/af_icons/culture/christianity.svg deleted file mode 100644 index 1a083b5329..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/culture/christianity.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/culture/dhammajak.svg b/frontend/appflowy_web_app/public/af_icons/culture/dhammajak.svg deleted file mode 100644 index 00ad062081..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/culture/dhammajak.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/culture/hexagram.svg b/frontend/appflowy_web_app/public/af_icons/culture/hexagram.svg deleted file mode 100644 index e9a5fbe428..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/culture/hexagram.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/culture/hinduism.svg b/frontend/appflowy_web_app/public/af_icons/culture/hinduism.svg deleted file mode 100644 index cca8164592..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/culture/hinduism.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/culture/islam.svg b/frontend/appflowy_web_app/public/af_icons/culture/islam.svg deleted file mode 100644 index c2af2b380e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/culture/islam.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/culture/news-paper.svg b/frontend/appflowy_web_app/public/af_icons/culture/news-paper.svg deleted file mode 100644 index 24d109d27d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/culture/news-paper.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/culture/peace-symbol.svg b/frontend/appflowy_web_app/public/af_icons/culture/peace-symbol.svg deleted file mode 100644 index 0249f8402e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/culture/peace-symbol.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/culture/politics-compaign.svg b/frontend/appflowy_web_app/public/af_icons/culture/politics-compaign.svg deleted file mode 100644 index 2333d4f883..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/culture/politics-compaign.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/culture/politics-speech.svg b/frontend/appflowy_web_app/public/af_icons/culture/politics-speech.svg deleted file mode 100644 index e199c705cb..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/culture/politics-speech.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/culture/politics-vote-2.svg b/frontend/appflowy_web_app/public/af_icons/culture/politics-vote-2.svg deleted file mode 100644 index 846d1522e9..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/culture/politics-vote-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/culture/ticket-1.svg b/frontend/appflowy_web_app/public/af_icons/culture/ticket-1.svg deleted file mode 100644 index 67ecf10328..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/culture/ticket-1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/culture/tickets.svg b/frontend/appflowy_web_app/public/af_icons/culture/tickets.svg deleted file mode 100644 index e06e36633f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/culture/tickets.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/culture/yin-yang-symbol.svg b/frontend/appflowy_web_app/public/af_icons/culture/yin-yang-symbol.svg deleted file mode 100644 index e645e68433..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/culture/yin-yang-symbol.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/culture/zodiac-1.svg b/frontend/appflowy_web_app/public/af_icons/culture/zodiac-1.svg deleted file mode 100644 index 721204e5e4..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/culture/zodiac-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/culture/zodiac-10.svg b/frontend/appflowy_web_app/public/af_icons/culture/zodiac-10.svg deleted file mode 100644 index 4fdf248b38..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/culture/zodiac-10.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/culture/zodiac-11.svg b/frontend/appflowy_web_app/public/af_icons/culture/zodiac-11.svg deleted file mode 100644 index 447b9c56c9..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/culture/zodiac-11.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/culture/zodiac-12.svg b/frontend/appflowy_web_app/public/af_icons/culture/zodiac-12.svg deleted file mode 100644 index fb2b1cb991..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/culture/zodiac-12.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/culture/zodiac-2.svg b/frontend/appflowy_web_app/public/af_icons/culture/zodiac-2.svg deleted file mode 100644 index d4425722d9..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/culture/zodiac-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/culture/zodiac-3.svg b/frontend/appflowy_web_app/public/af_icons/culture/zodiac-3.svg deleted file mode 100644 index 0208aea702..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/culture/zodiac-3.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/culture/zodiac-4.svg b/frontend/appflowy_web_app/public/af_icons/culture/zodiac-4.svg deleted file mode 100644 index 0469f30ae0..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/culture/zodiac-4.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/culture/zodiac-5.svg b/frontend/appflowy_web_app/public/af_icons/culture/zodiac-5.svg deleted file mode 100644 index 218ba4a391..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/culture/zodiac-5.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/culture/zodiac-6.svg b/frontend/appflowy_web_app/public/af_icons/culture/zodiac-6.svg deleted file mode 100644 index f02c49ee73..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/culture/zodiac-6.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/culture/zodiac-7.svg b/frontend/appflowy_web_app/public/af_icons/culture/zodiac-7.svg deleted file mode 100644 index b9de613da2..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/culture/zodiac-7.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/culture/zodiac-8.svg b/frontend/appflowy_web_app/public/af_icons/culture/zodiac-8.svg deleted file mode 100644 index 646ba98ea8..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/culture/zodiac-8.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/culture/zodiac-9.svg b/frontend/appflowy_web_app/public/af_icons/culture/zodiac-9.svg deleted file mode 100644 index 062bf1140f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/culture/zodiac-9.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/balloon.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/balloon.svg deleted file mode 100644 index 328aaaaaf1..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/balloon.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/bow.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/bow.svg deleted file mode 100644 index 2864709ca8..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/bow.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/button-fast-forward-1.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/button-fast-forward-1.svg deleted file mode 100644 index dd04b7e8c6..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/button-fast-forward-1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/button-fast-forward-2.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/button-fast-forward-2.svg deleted file mode 100644 index f3d3dc72bc..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/button-fast-forward-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/button-next.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/button-next.svg deleted file mode 100644 index c3b1a23a06..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/button-next.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/button-pause-2.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/button-pause-2.svg deleted file mode 100644 index 983544897a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/button-pause-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/button-play.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/button-play.svg deleted file mode 100644 index a07ab94655..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/button-play.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/button-power-1.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/button-power-1.svg deleted file mode 100644 index ef9e77f877..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/button-power-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/button-previous.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/button-previous.svg deleted file mode 100644 index 1f376dc16f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/button-previous.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/button-record-3.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/button-record-3.svg deleted file mode 100644 index 0e9332cb25..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/button-record-3.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/button-rewind-1.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/button-rewind-1.svg deleted file mode 100644 index d36b320fd9..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/button-rewind-1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/button-rewind-2.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/button-rewind-2.svg deleted file mode 100644 index beb36d9804..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/button-rewind-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/button-stop.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/button-stop.svg deleted file mode 100644 index a3339d0b1b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/button-stop.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/camera-video.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/camera-video.svg deleted file mode 100644 index 1dc4e57ea7..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/camera-video.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/cards.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/cards.svg deleted file mode 100644 index aa54a4dcc6..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/cards.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/chess-bishop.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/chess-bishop.svg deleted file mode 100644 index f667a4e84c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/chess-bishop.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/chess-king.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/chess-king.svg deleted file mode 100644 index 6cdbf1a76e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/chess-king.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/chess-knight.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/chess-knight.svg deleted file mode 100644 index 027afaedcc..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/chess-knight.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/chess-pawn.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/chess-pawn.svg deleted file mode 100644 index 9e995acb97..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/chess-pawn.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/cloud-gaming-1.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/cloud-gaming-1.svg deleted file mode 100644 index 874cac2023..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/cloud-gaming-1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/clubs-symbol.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/clubs-symbol.svg deleted file mode 100644 index 23207373a1..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/clubs-symbol.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/diamonds-symbol.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/diamonds-symbol.svg deleted file mode 100644 index d184ba7455..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/diamonds-symbol.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/dice-1.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/dice-1.svg deleted file mode 100644 index adfab0f74c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/dice-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/dice-2.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/dice-2.svg deleted file mode 100644 index 94ad5db18a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/dice-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/dice-3.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/dice-3.svg deleted file mode 100644 index 0e7571ae95..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/dice-3.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/dice-4.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/dice-4.svg deleted file mode 100644 index 37d68fcffc..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/dice-4.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/dice-5.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/dice-5.svg deleted file mode 100644 index eabbd0ed3b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/dice-5.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/dice-6.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/dice-6.svg deleted file mode 100644 index 36a19135ae..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/dice-6.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/dices-entertainment-gaming-dices.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/dices-entertainment-gaming-dices.svg deleted file mode 100644 index ea1f1d84ad..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/dices-entertainment-gaming-dices.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/earpods.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/earpods.svg deleted file mode 100644 index 890a89753e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/earpods.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/epic-games-1.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/epic-games-1.svg deleted file mode 100644 index d1eb2af8fe..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/epic-games-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/esports.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/esports.svg deleted file mode 100644 index 3f7bcd4c41..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/esports.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/fireworks-rocket.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/fireworks-rocket.svg deleted file mode 100644 index fcc4d96bcd..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/fireworks-rocket.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/gameboy.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/gameboy.svg deleted file mode 100644 index 402531f20a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/gameboy.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/gramophone.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/gramophone.svg deleted file mode 100644 index 0ed2f0b26f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/gramophone.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/hearts-symbol.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/hearts-symbol.svg deleted file mode 100644 index fc6cce023f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/hearts-symbol.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/music-equalizer.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/music-equalizer.svg deleted file mode 100644 index 9fbd4aba84..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/music-equalizer.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/music-note-1.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/music-note-1.svg deleted file mode 100644 index 644ba5553d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/music-note-1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/music-note-2.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/music-note-2.svg deleted file mode 100644 index 96efe68daa..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/music-note-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/music-note-off-1.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/music-note-off-1.svg deleted file mode 100644 index 5f5be24b37..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/music-note-off-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/music-note-off-2.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/music-note-off-2.svg deleted file mode 100644 index 8e6cffcfbd..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/music-note-off-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/nintendo-switch.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/nintendo-switch.svg deleted file mode 100644 index 31a17e97f1..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/nintendo-switch.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/one-vesus-one.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/one-vesus-one.svg deleted file mode 100644 index 31c3d7e265..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/one-vesus-one.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/pacman.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/pacman.svg deleted file mode 100644 index a42ee2bf02..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/pacman.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/party-popper.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/party-popper.svg deleted file mode 100644 index 2d7033ddb3..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/party-popper.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/play-list-4.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/play-list-4.svg deleted file mode 100644 index 6655dfb7d6..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/play-list-4.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/play-list-5.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/play-list-5.svg deleted file mode 100644 index 747bb3d86f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/play-list-5.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/play-list-8.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/play-list-8.svg deleted file mode 100644 index cda68aa414..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/play-list-8.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/play-list-9.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/play-list-9.svg deleted file mode 100644 index eb9df98361..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/play-list-9.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/play-list-folder.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/play-list-folder.svg deleted file mode 100644 index f6226c0d62..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/play-list-folder.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/play-station.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/play-station.svg deleted file mode 100644 index eb281023b8..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/play-station.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/radio.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/radio.svg deleted file mode 100644 index 068af297a2..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/radio.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/recording-tape-bubble-circle.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/recording-tape-bubble-circle.svg deleted file mode 100644 index fa5ba15b9e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/recording-tape-bubble-circle.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/recording-tape-bubble-square.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/recording-tape-bubble-square.svg deleted file mode 100644 index 69d0897329..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/recording-tape-bubble-square.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/song-recommendation.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/song-recommendation.svg deleted file mode 100644 index a53a018dae..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/song-recommendation.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/spades-symbol.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/spades-symbol.svg deleted file mode 100644 index 36a510d14b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/spades-symbol.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/speaker-1.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/speaker-1.svg deleted file mode 100644 index 105430d2ad..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/speaker-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/speaker-2.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/speaker-2.svg deleted file mode 100644 index 79cf8682b6..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/speaker-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/stream.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/stream.svg deleted file mode 100644 index 188e0c1a8f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/stream.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/tape-cassette-record.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/tape-cassette-record.svg deleted file mode 100644 index 1ecc8cb52f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/tape-cassette-record.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/volume-down.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/volume-down.svg deleted file mode 100644 index c86a4fd7d9..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/volume-down.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/volume-level-high.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/volume-level-high.svg deleted file mode 100644 index b560324f28..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/volume-level-high.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/volume-level-low.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/volume-level-low.svg deleted file mode 100644 index 726d2adef3..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/volume-level-low.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/volume-level-off.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/volume-level-off.svg deleted file mode 100644 index a4a4d827dd..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/volume-level-off.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/volume-mute.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/volume-mute.svg deleted file mode 100644 index 02c8c1da05..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/volume-mute.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/volume-off.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/volume-off.svg deleted file mode 100644 index 5d9afb737a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/volume-off.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/vr-headset-1.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/vr-headset-1.svg deleted file mode 100644 index 99a7bea697..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/vr-headset-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/vr-headset-2.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/vr-headset-2.svg deleted file mode 100644 index 88cd45c4ed..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/vr-headset-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/entertainment/xbox.svg b/frontend/appflowy_web_app/public/af_icons/entertainment/xbox.svg deleted file mode 100644 index 47efc6bc54..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/entertainment/xbox.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/beer-mug.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/beer-mug.svg deleted file mode 100644 index 01ecef5716..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/beer-mug.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/beer-pitch.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/beer-pitch.svg deleted file mode 100644 index 6eda98884b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/beer-pitch.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/burger.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/burger.svg deleted file mode 100644 index 12c6c9d249..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/burger.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/burrito-fastfood.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/burrito-fastfood.svg deleted file mode 100644 index 88abc83543..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/burrito-fastfood.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/cake-slice.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/cake-slice.svg deleted file mode 100644 index ec6132a520..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/cake-slice.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/candy-cane.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/candy-cane.svg deleted file mode 100644 index 12510b6fcb..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/candy-cane.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/champagne-party-alcohol.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/champagne-party-alcohol.svg deleted file mode 100644 index 01c22f9955..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/champagne-party-alcohol.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/cheese.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/cheese.svg deleted file mode 100644 index 721c865fb4..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/cheese.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/cherries.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/cherries.svg deleted file mode 100644 index df3d75d719..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/cherries.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/chicken-grilled-stream.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/chicken-grilled-stream.svg deleted file mode 100644 index b3410829f2..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/chicken-grilled-stream.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/cocktail.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/cocktail.svg deleted file mode 100644 index fa4f8a3c2f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/cocktail.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/coffee-bean.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/coffee-bean.svg deleted file mode 100644 index 17cd87ef52..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/coffee-bean.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/coffee-mug.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/coffee-mug.svg deleted file mode 100644 index 9d798f5761..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/coffee-mug.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/coffee-takeaway-cup.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/coffee-takeaway-cup.svg deleted file mode 100644 index c4db12c023..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/coffee-takeaway-cup.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/donut.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/donut.svg deleted file mode 100644 index 9e43a78ce1..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/donut.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/fork-knife.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/fork-knife.svg deleted file mode 100644 index c084ce727b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/fork-knife.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/fork-spoon.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/fork-spoon.svg deleted file mode 100644 index b1ac770721..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/fork-spoon.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/ice-cream-2.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/ice-cream-2.svg deleted file mode 100644 index de00d2d5d7..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/ice-cream-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/ice-cream-3.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/ice-cream-3.svg deleted file mode 100644 index 8b4d864570..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/ice-cream-3.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/lemon-fruit-seasoning.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/lemon-fruit-seasoning.svg deleted file mode 100644 index 3da07de679..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/lemon-fruit-seasoning.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/microwave.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/microwave.svg deleted file mode 100644 index 162c26c96d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/microwave.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/milkshake.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/milkshake.svg deleted file mode 100644 index 9a73d0d4e4..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/milkshake.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/popcorn.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/popcorn.svg deleted file mode 100644 index 33cf71d444..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/popcorn.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/pork-meat.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/pork-meat.svg deleted file mode 100644 index 081e550618..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/pork-meat.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/refrigerator.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/refrigerator.svg deleted file mode 100644 index 89f233c48d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/refrigerator.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/serving-dome.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/serving-dome.svg deleted file mode 100644 index 1bdc48d306..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/serving-dome.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/shrimp.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/shrimp.svg deleted file mode 100644 index b9a5add6da..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/shrimp.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/strawberry.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/strawberry.svg deleted file mode 100644 index 14aa7a9f8d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/strawberry.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/tea-cup.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/tea-cup.svg deleted file mode 100644 index e678274acc..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/tea-cup.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/toast.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/toast.svg deleted file mode 100644 index 5aa9be15ad..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/toast.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/water-glass.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/water-glass.svg deleted file mode 100644 index 8e9f674c8c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/water-glass.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/food_drink/wine.svg b/frontend/appflowy_web_app/public/af_icons/food_drink/wine.svg deleted file mode 100644 index 1f6be74e61..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/food_drink/wine.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/ambulance.svg b/frontend/appflowy_web_app/public/af_icons/health/ambulance.svg deleted file mode 100644 index c0747996ff..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/ambulance.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/bacteria-virus-cells-biology.svg b/frontend/appflowy_web_app/public/af_icons/health/bacteria-virus-cells-biology.svg deleted file mode 100644 index 39c0d6442a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/bacteria-virus-cells-biology.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/bandage.svg b/frontend/appflowy_web_app/public/af_icons/health/bandage.svg deleted file mode 100644 index ff4e17b118..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/bandage.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/blood-bag-donation.svg b/frontend/appflowy_web_app/public/af_icons/health/blood-bag-donation.svg deleted file mode 100644 index 6a558d54de..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/blood-bag-donation.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/blood-donate-drop.svg b/frontend/appflowy_web_app/public/af_icons/health/blood-donate-drop.svg deleted file mode 100644 index 6959a292d5..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/blood-donate-drop.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/blood-drop-donation.svg b/frontend/appflowy_web_app/public/af_icons/health/blood-drop-donation.svg deleted file mode 100644 index 8bd0bfc432..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/blood-drop-donation.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/brain-cognitive.svg b/frontend/appflowy_web_app/public/af_icons/health/brain-cognitive.svg deleted file mode 100644 index 9fdb4125d4..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/brain-cognitive.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/brain.svg b/frontend/appflowy_web_app/public/af_icons/health/brain.svg deleted file mode 100644 index e12682c2ef..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/brain.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/call-center-support-service.svg b/frontend/appflowy_web_app/public/af_icons/health/call-center-support-service.svg deleted file mode 100644 index 1593b6c790..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/call-center-support-service.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/checkup-medical-report-clipboard.svg b/frontend/appflowy_web_app/public/af_icons/health/checkup-medical-report-clipboard.svg deleted file mode 100644 index 2837b7f2e5..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/checkup-medical-report-clipboard.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/ear-hearing.svg b/frontend/appflowy_web_app/public/af_icons/health/ear-hearing.svg deleted file mode 100644 index 5fa596e560..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/ear-hearing.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/eye-optic.svg b/frontend/appflowy_web_app/public/af_icons/health/eye-optic.svg deleted file mode 100644 index 1617190a8e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/eye-optic.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/flu-mask.svg b/frontend/appflowy_web_app/public/af_icons/health/flu-mask.svg deleted file mode 100644 index 365d4cab84..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/flu-mask.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/health-care-2.svg b/frontend/appflowy_web_app/public/af_icons/health/health-care-2.svg deleted file mode 100644 index 495a02dc3d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/health-care-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/heart-rate-pulse-graph.svg b/frontend/appflowy_web_app/public/af_icons/health/heart-rate-pulse-graph.svg deleted file mode 100644 index 0351f05eb2..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/heart-rate-pulse-graph.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/heart-rate-search.svg b/frontend/appflowy_web_app/public/af_icons/health/heart-rate-search.svg deleted file mode 100644 index e8a6faa1db..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/heart-rate-search.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/hospital-sign-circle.svg b/frontend/appflowy_web_app/public/af_icons/health/hospital-sign-circle.svg deleted file mode 100644 index 964abce175..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/hospital-sign-circle.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/hospital-sign-square.svg b/frontend/appflowy_web_app/public/af_icons/health/hospital-sign-square.svg deleted file mode 100644 index 1648b17479..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/hospital-sign-square.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/insurance-hand.svg b/frontend/appflowy_web_app/public/af_icons/health/insurance-hand.svg deleted file mode 100644 index 70ae0036b9..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/insurance-hand.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/medical-bag.svg b/frontend/appflowy_web_app/public/af_icons/health/medical-bag.svg deleted file mode 100644 index c469e591d9..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/medical-bag.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/medical-cross-sign-healthcare.svg b/frontend/appflowy_web_app/public/af_icons/health/medical-cross-sign-healthcare.svg deleted file mode 100644 index fc35cba77e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/medical-cross-sign-healthcare.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/medical-cross-symbol.svg b/frontend/appflowy_web_app/public/af_icons/health/medical-cross-symbol.svg deleted file mode 100644 index 7906b49bd2..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/medical-cross-symbol.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/medical-files-report-history.svg b/frontend/appflowy_web_app/public/af_icons/health/medical-files-report-history.svg deleted file mode 100644 index b18c22e979..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/medical-files-report-history.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/medical-ribbon-1.svg b/frontend/appflowy_web_app/public/af_icons/health/medical-ribbon-1.svg deleted file mode 100644 index c53c3ef448..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/medical-ribbon-1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/medical-search-diagnosis.svg b/frontend/appflowy_web_app/public/af_icons/health/medical-search-diagnosis.svg deleted file mode 100644 index f5995068cc..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/medical-search-diagnosis.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/microscope-observation-sciene.svg b/frontend/appflowy_web_app/public/af_icons/health/microscope-observation-sciene.svg deleted file mode 100644 index be4a39d09e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/microscope-observation-sciene.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/nurse-assistant-emergency.svg b/frontend/appflowy_web_app/public/af_icons/health/nurse-assistant-emergency.svg deleted file mode 100644 index 43e1a0fcbf..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/nurse-assistant-emergency.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/nurse-hat.svg b/frontend/appflowy_web_app/public/af_icons/health/nurse-hat.svg deleted file mode 100644 index e8f3ca9dc3..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/nurse-hat.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/online-medical-call-service.svg b/frontend/appflowy_web_app/public/af_icons/health/online-medical-call-service.svg deleted file mode 100644 index 24190f5ed4..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/online-medical-call-service.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/online-medical-service-monitor.svg b/frontend/appflowy_web_app/public/af_icons/health/online-medical-service-monitor.svg deleted file mode 100644 index 85370f0d57..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/online-medical-service-monitor.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/online-medical-web-service.svg b/frontend/appflowy_web_app/public/af_icons/health/online-medical-web-service.svg deleted file mode 100644 index cf683b8d42..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/online-medical-web-service.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/petri-dish-lab-equipment.svg b/frontend/appflowy_web_app/public/af_icons/health/petri-dish-lab-equipment.svg deleted file mode 100644 index 46409cf4d8..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/petri-dish-lab-equipment.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/pharmacy.svg b/frontend/appflowy_web_app/public/af_icons/health/pharmacy.svg deleted file mode 100644 index c2f871ae9b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/pharmacy.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/prescription-pills-drugs-healthcare.svg b/frontend/appflowy_web_app/public/af_icons/health/prescription-pills-drugs-healthcare.svg deleted file mode 100644 index 0b297f59f0..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/prescription-pills-drugs-healthcare.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/sign-cross-square.svg b/frontend/appflowy_web_app/public/af_icons/health/sign-cross-square.svg deleted file mode 100644 index a3f893c951..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/sign-cross-square.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/sos-help-emergency-sign.svg b/frontend/appflowy_web_app/public/af_icons/health/sos-help-emergency-sign.svg deleted file mode 100644 index 850b037136..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/sos-help-emergency-sign.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/stethoscope.svg b/frontend/appflowy_web_app/public/af_icons/health/stethoscope.svg deleted file mode 100644 index f78716a7f8..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/stethoscope.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/syringe.svg b/frontend/appflowy_web_app/public/af_icons/health/syringe.svg deleted file mode 100644 index 07fe454cff..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/syringe.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/tablet-capsule.svg b/frontend/appflowy_web_app/public/af_icons/health/tablet-capsule.svg deleted file mode 100644 index 9553c056c3..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/tablet-capsule.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/tooth.svg b/frontend/appflowy_web_app/public/af_icons/health/tooth.svg deleted file mode 100644 index 6817c2b796..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/tooth.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/virus-antivirus.svg b/frontend/appflowy_web_app/public/af_icons/health/virus-antivirus.svg deleted file mode 100644 index ad972cff8b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/virus-antivirus.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/waiting-appointments-calendar.svg b/frontend/appflowy_web_app/public/af_icons/health/waiting-appointments-calendar.svg deleted file mode 100644 index 59ab62e17f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/waiting-appointments-calendar.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/health/wheelchair.svg b/frontend/appflowy_web_app/public/af_icons/health/wheelchair.svg deleted file mode 100644 index a29e32ca48..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/health/wheelchair.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/icons.json b/frontend/appflowy_web_app/public/af_icons/icons.json deleted file mode 100644 index b76b0d051a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/icons.json +++ /dev/null @@ -1 +0,0 @@ -{ "artificial_intelligence": [ { "id": "artificial_intelligence/ai-chip-spark", "name": "ai-chip-spark", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "artificial_intelligence/ai-cloud-spark", "name": "ai-cloud-spark", "keywords": [], "content": "\n \n \n \n \n \n \n \n \n\n" }, { "id": "artificial_intelligence/ai-edit-spark", "name": "ai-edit-spark", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "artificial_intelligence/ai-email-generator-spark", "name": "ai-email-generator-spark", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "artificial_intelligence/ai-gaming-spark", "name": "ai-gaming-spark", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "artificial_intelligence/ai-generate-landscape-image-spark", "name": "ai-generate-landscape-image-spark", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n\n" }, { "id": "artificial_intelligence/ai-generate-music-spark", "name": "ai-generate-music-spark", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "artificial_intelligence/ai-generate-portrait-image-spark", "name": "ai-generate-portrait-image-spark", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "artificial_intelligence/ai-generate-variation-spark", "name": "ai-generate-variation-spark", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "artificial_intelligence/ai-navigation-spark", "name": "ai-navigation-spark", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "artificial_intelligence/ai-network-spark", "name": "ai-network-spark", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "artificial_intelligence/ai-prompt-spark", "name": "ai-prompt-spark", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "artificial_intelligence/ai-redo-spark", "name": "ai-redo-spark", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "artificial_intelligence/ai-science-spark", "name": "ai-science-spark", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "artificial_intelligence/ai-settings-spark", "name": "ai-settings-spark", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "artificial_intelligence/ai-technology-spark", "name": "ai-technology-spark", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "artificial_intelligence/ai-upscale-spark", "name": "ai-upscale-spark", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "artificial_intelligence/ai-vehicle-spark-1", "name": "ai-vehicle-spark-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "artificial_intelligence/artificial-intelligence-spark", "name": "artificial-intelligence-spark", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" } ], "computer_devices": [ { "id": "computer_devices/adobe", "name": "adobe", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/alt", "name": "alt", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/amazon", "name": "amazon", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/android", "name": "android", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/app-store", "name": "app-store", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/apple", "name": "apple", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/asterisk-1", "name": "asterisk-1", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/battery-alert-1", "name": "battery-alert-1", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/battery-charging", "name": "battery-charging", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/battery-empty-1", "name": "battery-empty-1", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/battery-empty-2", "name": "battery-empty-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/battery-full-1", "name": "battery-full-1", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/battery-low-1", "name": "battery-low-1", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/battery-medium-1", "name": "battery-medium-1", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/bluetooth-disabled", "name": "bluetooth-disabled", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/bluetooth-searching", "name": "bluetooth-searching", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/bluetooth", "name": "bluetooth", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/browser-wifi", "name": "browser-wifi", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/chrome", "name": "chrome", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/command", "name": "command", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/computer-chip-1", "name": "computer-chip-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/computer-chip-2", "name": "computer-chip-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/computer-pc-desktop", "name": "computer-pc-desktop", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/controller-1", "name": "controller-1", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/controller-wireless", "name": "controller-wireless", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/controller", "name": "controller", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/cursor-click", "name": "cursor-click", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/cyborg-2", "name": "cyborg-2", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/cyborg", "name": "cyborg", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/database-check", "name": "database-check", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/database-lock", "name": "database-lock", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/database-refresh", "name": "database-refresh", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/database-remove", "name": "database-remove", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/database-server-1", "name": "database-server-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/database-server-2", "name": "database-server-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/database-setting", "name": "database-setting", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/database-subtract-2-raid-storage-code-disk-programming-database-array-hard-disc-minus", "name": "database-subtract-2-raid-storage-code-disk-programming-database-array-hard-disc-minus", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/database", "name": "database", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/delete-keyboard", "name": "delete-keyboard", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/desktop-chat", "name": "desktop-chat", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/desktop-check", "name": "desktop-check", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/desktop-code", "name": "desktop-code", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/desktop-delete", "name": "desktop-delete", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/desktop-dollar", "name": "desktop-dollar", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/desktop-emoji", "name": "desktop-emoji", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/desktop-favorite-star", "name": "desktop-favorite-star", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/desktop-game", "name": "desktop-game", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/desktop-help", "name": "desktop-help", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/device-database-encryption-1", "name": "device-database-encryption-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/discord", "name": "discord", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/drone", "name": "drone", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/dropbox", "name": "dropbox", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/eject", "name": "eject", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/electric-cord-1", "name": "electric-cord-1", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/electric-cord-3", "name": "electric-cord-3", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/facebook-1", "name": "facebook-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/figma", "name": "figma", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/floppy-disk", "name": "floppy-disk", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/gmail", "name": "gmail", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/google-drive", "name": "google-drive", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/google", "name": "google", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/hand-held-tablet-drawing", "name": "hand-held-tablet-drawing", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/hand-held-tablet-writing", "name": "hand-held-tablet-writing", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/hand-held", "name": "hand-held", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/hard-disk", "name": "hard-disk", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/hard-drive-1", "name": "hard-drive-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/instagram", "name": "instagram", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/keyboard-virtual", "name": "keyboard-virtual", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/keyboard-wireless-2", "name": "keyboard-wireless-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/keyboard", "name": "keyboard", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/laptop-charging", "name": "laptop-charging", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/linkedin", "name": "linkedin", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/local-storage-folder", "name": "local-storage-folder", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/meta", "name": "meta", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/mouse-wireless-1", "name": "mouse-wireless-1", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/mouse-wireless", "name": "mouse-wireless", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/mouse", "name": "mouse", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/netflix", "name": "netflix", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/network", "name": "network", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/next", "name": "next", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/paypal", "name": "paypal", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/play-store", "name": "play-store", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/printer", "name": "printer", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/return-2", "name": "return-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/screen-1", "name": "screen-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/screen-2", "name": "screen-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/screen-curve", "name": "screen-curve", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/screensaver-monitor-wallpaper", "name": "screensaver-monitor-wallpaper", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/shift", "name": "shift", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/shredder", "name": "shredder", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/signal-loading", "name": "signal-loading", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/slack", "name": "slack", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/spotify", "name": "spotify", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/telegram", "name": "telegram", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/tiktok", "name": "tiktok", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/tinder", "name": "tinder", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/twitter", "name": "twitter", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/usb-drive", "name": "usb-drive", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/virtual-reality", "name": "virtual-reality", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/voice-mail-off", "name": "voice-mail-off", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/voice-mail", "name": "voice-mail", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/VPN-connection", "name": "VPN-connection", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/watch-1", "name": "watch-1", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/watch-2", "name": "watch-2", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/watch-circle-charging", "name": "watch-circle-charging", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/watch-circle-heartbeat-monitor-1", "name": "watch-circle-heartbeat-monitor-1", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/watch-circle-heartbeat-monitor-2", "name": "watch-circle-heartbeat-monitor-2", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/watch-circle-menu", "name": "watch-circle-menu", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/watch-circle-time", "name": "watch-circle-time", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/webcam-video-circle", "name": "webcam-video-circle", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/webcam-video-off", "name": "webcam-video-off", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/webcam-video", "name": "webcam-video", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/webcam", "name": "webcam", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/whatsapp", "name": "whatsapp", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/wifi-antenna", "name": "wifi-antenna", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/wifi-disabled", "name": "wifi-disabled", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/wifi-horizontal", "name": "wifi-horizontal", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/wifi-router", "name": "wifi-router", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "computer_devices/wifi", "name": "wifi", "keywords": [], "content": "\n\n\n" }, { "id": "computer_devices/windows", "name": "windows", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" } ], "culture": [ { "id": "culture/christian-cross-1", "name": "christian-cross-1", "keywords": [], "content": "\n\n\n" }, { "id": "culture/christian-cross-2", "name": "christian-cross-2", "keywords": [], "content": "\n\n\n" }, { "id": "culture/christianity", "name": "christianity", "keywords": [], "content": "\n\n\n" }, { "id": "culture/dhammajak", "name": "dhammajak", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "culture/hexagram", "name": "hexagram", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "culture/hinduism", "name": "hinduism", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "culture/islam", "name": "islam", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "culture/news-paper", "name": "news-paper", "keywords": [], "content": "\n\n\n" }, { "id": "culture/peace-symbol", "name": "peace-symbol", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "culture/politics-compaign", "name": "politics-compaign", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "culture/politics-speech", "name": "politics-speech", "keywords": [], "content": "\n\n\n" }, { "id": "culture/politics-vote-2", "name": "politics-vote-2", "keywords": [], "content": "\n\n\n" }, { "id": "culture/ticket-1", "name": "ticket-1", "keywords": [], "content": "\n\n\n" }, { "id": "culture/tickets", "name": "tickets", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "culture/yin-yang-symbol", "name": "yin-yang-symbol", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "culture/zodiac-1", "name": "zodiac-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "culture/zodiac-10", "name": "zodiac-10", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "culture/zodiac-11", "name": "zodiac-11", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "culture/zodiac-12", "name": "zodiac-12", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "culture/zodiac-2", "name": "zodiac-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "culture/zodiac-3", "name": "zodiac-3", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "culture/zodiac-4", "name": "zodiac-4", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "culture/zodiac-5", "name": "zodiac-5", "keywords": [], "content": "\n\n\n" }, { "id": "culture/zodiac-6", "name": "zodiac-6", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "culture/zodiac-7", "name": "zodiac-7", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "culture/zodiac-8", "name": "zodiac-8", "keywords": [], "content": "\n\n\n" }, { "id": "culture/zodiac-9", "name": "zodiac-9", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" } ], "entertainment": [ { "id": "entertainment/balloon", "name": "balloon", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/bow", "name": "bow", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/button-fast-forward-1", "name": "button-fast-forward-1", "keywords": [], "content": "\n\n\n" }, { "id": "entertainment/button-fast-forward-2", "name": "button-fast-forward-2", "keywords": [], "content": "\n\n\n" }, { "id": "entertainment/button-next", "name": "button-next", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/button-pause-2", "name": "button-pause-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/button-play", "name": "button-play", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/button-power-1", "name": "button-power-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/button-previous", "name": "button-previous", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/button-record-3", "name": "button-record-3", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/button-rewind-1", "name": "button-rewind-1", "keywords": [], "content": "\n\n\n" }, { "id": "entertainment/button-rewind-2", "name": "button-rewind-2", "keywords": [], "content": "\n\n\n" }, { "id": "entertainment/button-stop", "name": "button-stop", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/camera-video", "name": "camera-video", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/cards", "name": "cards", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/chess-bishop", "name": "chess-bishop", "keywords": [], "content": "\n\n\n" }, { "id": "entertainment/chess-king", "name": "chess-king", "keywords": [], "content": "\n\n\n" }, { "id": "entertainment/chess-knight", "name": "chess-knight", "keywords": [], "content": "\n\n\n" }, { "id": "entertainment/chess-pawn", "name": "chess-pawn", "keywords": [], "content": "\n\n\n" }, { "id": "entertainment/cloud-gaming-1", "name": "cloud-gaming-1", "keywords": [], "content": "\n\n\n" }, { "id": "entertainment/clubs-symbol", "name": "clubs-symbol", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/diamonds-symbol", "name": "diamonds-symbol", "keywords": [], "content": "\n\n\n" }, { "id": "entertainment/dice-1", "name": "dice-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/dice-2", "name": "dice-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/dice-3", "name": "dice-3", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/dice-4", "name": "dice-4", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/dice-5", "name": "dice-5", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/dice-6", "name": "dice-6", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/dices-entertainment-gaming-dices", "name": "dices-entertainment-gaming-dices", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/earpods", "name": "earpods", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/epic-games-1", "name": "epic-games-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/esports", "name": "esports", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/fireworks-rocket", "name": "fireworks-rocket", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/gameboy", "name": "gameboy", "keywords": [], "content": "\n\n\n" }, { "id": "entertainment/gramophone", "name": "gramophone", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/hearts-symbol", "name": "hearts-symbol", "keywords": [], "content": "\n\n\n" }, { "id": "entertainment/music-equalizer", "name": "music-equalizer", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/music-note-1", "name": "music-note-1", "keywords": [], "content": "\n\n\n" }, { "id": "entertainment/music-note-2", "name": "music-note-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/music-note-off-1", "name": "music-note-off-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/music-note-off-2", "name": "music-note-off-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/nintendo-switch", "name": "nintendo-switch", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/one-vesus-one", "name": "one-vesus-one", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/pacman", "name": "pacman", "keywords": [], "content": "\n\n\n" }, { "id": "entertainment/party-popper", "name": "party-popper", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/play-list-4", "name": "play-list-4", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/play-list-5", "name": "play-list-5", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/play-list-8", "name": "play-list-8", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/play-list-9", "name": "play-list-9", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/play-list-folder", "name": "play-list-folder", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/play-station", "name": "play-station", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/radio", "name": "radio", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/recording-tape-bubble-circle", "name": "recording-tape-bubble-circle", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/recording-tape-bubble-square", "name": "recording-tape-bubble-square", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/song-recommendation", "name": "song-recommendation", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/spades-symbol", "name": "spades-symbol", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/speaker-1", "name": "speaker-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/speaker-2", "name": "speaker-2", "keywords": [], "content": "\n\n\n" }, { "id": "entertainment/stream", "name": "stream", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/tape-cassette-record", "name": "tape-cassette-record", "keywords": [], "content": "\n\n\n" }, { "id": "entertainment/volume-down", "name": "volume-down", "keywords": [], "content": "\n\n\n" }, { "id": "entertainment/volume-level-high", "name": "volume-level-high", "keywords": [], "content": "\n\n\n" }, { "id": "entertainment/volume-level-low", "name": "volume-level-low", "keywords": [], "content": "\n\n\n" }, { "id": "entertainment/volume-level-off", "name": "volume-level-off", "keywords": [], "content": "\n\n\n" }, { "id": "entertainment/volume-mute", "name": "volume-mute", "keywords": [], "content": "\n\n\n" }, { "id": "entertainment/volume-off", "name": "volume-off", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/vr-headset-1", "name": "vr-headset-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/vr-headset-2", "name": "vr-headset-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "entertainment/xbox", "name": "xbox", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" } ], "food_drink": [ { "id": "food_drink/beer-mug", "name": "beer-mug", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "food_drink/beer-pitch", "name": "beer-pitch", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "food_drink/burger", "name": "burger", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "food_drink/burrito-fastfood", "name": "burrito-fastfood", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "food_drink/cake-slice", "name": "cake-slice", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "food_drink/candy-cane", "name": "candy-cane", "keywords": [], "content": "\n\n\n" }, { "id": "food_drink/champagne-party-alcohol", "name": "champagne-party-alcohol", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "food_drink/cheese", "name": "cheese", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "food_drink/cherries", "name": "cherries", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "food_drink/chicken-grilled-stream", "name": "chicken-grilled-stream", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "food_drink/cocktail", "name": "cocktail", "keywords": [], "content": "\n\n\n" }, { "id": "food_drink/coffee-bean", "name": "coffee-bean", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "food_drink/coffee-mug", "name": "coffee-mug", "keywords": [], "content": "\n\n\n" }, { "id": "food_drink/coffee-takeaway-cup", "name": "coffee-takeaway-cup", "keywords": [], "content": "\n\n\n" }, { "id": "food_drink/donut", "name": "donut", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "food_drink/fork-knife", "name": "fork-knife", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "food_drink/fork-spoon", "name": "fork-spoon", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "food_drink/ice-cream-2", "name": "ice-cream-2", "keywords": [], "content": "\n\n\n" }, { "id": "food_drink/ice-cream-3", "name": "ice-cream-3", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "food_drink/lemon-fruit-seasoning", "name": "lemon-fruit-seasoning", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "food_drink/microwave", "name": "microwave", "keywords": [], "content": "\n\n\n" }, { "id": "food_drink/milkshake", "name": "milkshake", "keywords": [], "content": "\n\n\n" }, { "id": "food_drink/popcorn", "name": "popcorn", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "food_drink/pork-meat", "name": "pork-meat", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "food_drink/refrigerator", "name": "refrigerator", "keywords": [], "content": "\n\n\n" }, { "id": "food_drink/serving-dome", "name": "serving-dome", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "food_drink/shrimp", "name": "shrimp", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "food_drink/strawberry", "name": "strawberry", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "food_drink/tea-cup", "name": "tea-cup", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "food_drink/toast", "name": "toast", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n\n" }, { "id": "food_drink/water-glass", "name": "water-glass", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "food_drink/wine", "name": "wine", "keywords": [], "content": "\n\n\n" } ], "health": [ { "id": "health/ambulance", "name": "ambulance", "keywords": [], "content": "\n\n\n" }, { "id": "health/bacteria-virus-cells-biology", "name": "bacteria-virus-cells-biology", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "health/bandage", "name": "bandage", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "health/blood-bag-donation", "name": "blood-bag-donation", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "health/blood-donate-drop", "name": "blood-donate-drop", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "health/blood-drop-donation", "name": "blood-drop-donation", "keywords": [], "content": "\n\n\n" }, { "id": "health/brain-cognitive", "name": "brain-cognitive", "keywords": [], "content": "\n\n\n" }, { "id": "health/brain", "name": "brain", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "health/call-center-support-service", "name": "call-center-support-service", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "health/checkup-medical-report-clipboard", "name": "checkup-medical-report-clipboard", "keywords": [], "content": "\n\n\n" }, { "id": "health/ear-hearing", "name": "ear-hearing", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "health/eye-optic", "name": "eye-optic", "keywords": [], "content": "\n\n\n" }, { "id": "health/flu-mask", "name": "flu-mask", "keywords": [], "content": "\n\n\n" }, { "id": "health/health-care-2", "name": "health-care-2", "keywords": [], "content": "\n\n\n" }, { "id": "health/heart-rate-pulse-graph", "name": "heart-rate-pulse-graph", "keywords": [], "content": "\n\n\n" }, { "id": "health/heart-rate-search", "name": "heart-rate-search", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "health/hospital-sign-circle", "name": "hospital-sign-circle", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "health/hospital-sign-square", "name": "hospital-sign-square", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "health/insurance-hand", "name": "insurance-hand", "keywords": [], "content": "\n\n\n" }, { "id": "health/medical-bag", "name": "medical-bag", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "health/medical-cross-sign-healthcare", "name": "medical-cross-sign-healthcare", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "health/medical-cross-symbol", "name": "medical-cross-symbol", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "health/medical-files-report-history", "name": "medical-files-report-history", "keywords": [], "content": "\n\n\n" }, { "id": "health/medical-ribbon-1", "name": "medical-ribbon-1", "keywords": [], "content": "\n\n\n" }, { "id": "health/medical-search-diagnosis", "name": "medical-search-diagnosis", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "health/microscope-observation-sciene", "name": "microscope-observation-sciene", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "health/nurse-assistant-emergency", "name": "nurse-assistant-emergency", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "health/nurse-hat", "name": "nurse-hat", "keywords": [], "content": "\n\n\n" }, { "id": "health/online-medical-call-service", "name": "online-medical-call-service", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "health/online-medical-service-monitor", "name": "online-medical-service-monitor", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "health/online-medical-web-service", "name": "online-medical-web-service", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n\n" }, { "id": "health/petri-dish-lab-equipment", "name": "petri-dish-lab-equipment", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "health/pharmacy", "name": "pharmacy", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "health/prescription-pills-drugs-healthcare", "name": "prescription-pills-drugs-healthcare", "keywords": [], "content": "\n\n\n" }, { "id": "health/sign-cross-square", "name": "sign-cross-square", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "health/sos-help-emergency-sign", "name": "sos-help-emergency-sign", "keywords": [], "content": "\n\n\n" }, { "id": "health/stethoscope", "name": "stethoscope", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "health/syringe", "name": "syringe", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "health/tablet-capsule", "name": "tablet-capsule", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "health/tooth", "name": "tooth", "keywords": [], "content": "\n\n\n" }, { "id": "health/virus-antivirus", "name": "virus-antivirus", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "health/waiting-appointments-calendar", "name": "waiting-appointments-calendar", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "health/wheelchair", "name": "wheelchair", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" } ], "images_photography": [ { "id": "images_photography/auto-flash", "name": "auto-flash", "keywords": [], "content": "\n\n\n" }, { "id": "images_photography/camera-1", "name": "camera-1", "keywords": [], "content": "\n\n\n" }, { "id": "images_photography/camera-disabled", "name": "camera-disabled", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "images_photography/camera-loading", "name": "camera-loading", "keywords": [], "content": "\n\n\n" }, { "id": "images_photography/camera-square", "name": "camera-square", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "images_photography/composition-oval", "name": "composition-oval", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "images_photography/composition-vertical", "name": "composition-vertical", "keywords": [], "content": "\n\n\n" }, { "id": "images_photography/compsition-horizontal", "name": "compsition-horizontal", "keywords": [], "content": "\n\n\n" }, { "id": "images_photography/edit-image-photo", "name": "edit-image-photo", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "images_photography/film-roll-1", "name": "film-roll-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "images_photography/film-slate", "name": "film-slate", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "images_photography/flash-1", "name": "flash-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "images_photography/flash-2", "name": "flash-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "images_photography/flash-3", "name": "flash-3", "keywords": [], "content": "\n\n\n" }, { "id": "images_photography/flash-off", "name": "flash-off", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "images_photography/flower", "name": "flower", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "images_photography/focus-points", "name": "focus-points", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "images_photography/landscape-2", "name": "landscape-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "images_photography/landscape-setting", "name": "landscape-setting", "keywords": [], "content": "\n\n\n" }, { "id": "images_photography/laptop-camera", "name": "laptop-camera", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "images_photography/mobile-phone-camera", "name": "mobile-phone-camera", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "images_photography/orientation-landscape", "name": "orientation-landscape", "keywords": [], "content": "\n\n\n" }, { "id": "images_photography/orientation-portrait", "name": "orientation-portrait", "keywords": [], "content": "\n\n\n" }, { "id": "images_photography/polaroid-four", "name": "polaroid-four", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" } ], "interface_essential": [ { "id": "interface_essential/add-1", "name": "add-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/add-bell-notification", "name": "add-bell-notification", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/add-circle", "name": "add-circle", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/add-layer-2", "name": "add-layer-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/add-square", "name": "add-square", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/alarm-clock", "name": "alarm-clock", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/align-back-1", "name": "align-back-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/align-center", "name": "align-center", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/align-front-1", "name": "align-front-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/align-left", "name": "align-left", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/align-right", "name": "align-right", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/ampersand", "name": "ampersand", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/archive-box", "name": "archive-box", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/arrow-bend-left-down-2", "name": "arrow-bend-left-down-2", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/arrow-bend-right-down-2", "name": "arrow-bend-right-down-2", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/arrow-crossover-down", "name": "arrow-crossover-down", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/arrow-crossover-left", "name": "arrow-crossover-left", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/arrow-crossover-right", "name": "arrow-crossover-right", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/arrow-crossover-up", "name": "arrow-crossover-up", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/arrow-cursor-1", "name": "arrow-cursor-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/arrow-cursor-2", "name": "arrow-cursor-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/arrow-curvy-up-down-1", "name": "arrow-curvy-up-down-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/arrow-curvy-up-down-2", "name": "arrow-curvy-up-down-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/arrow-down-2", "name": "arrow-down-2", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/arrow-down-dashed-square", "name": "arrow-down-dashed-square", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/arrow-expand", "name": "arrow-expand", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/arrow-infinite-loop", "name": "arrow-infinite-loop", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/arrow-move", "name": "arrow-move", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/arrow-reload-horizontal-1", "name": "arrow-reload-horizontal-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/arrow-reload-horizontal-2", "name": "arrow-reload-horizontal-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/arrow-reload-vertical-1", "name": "arrow-reload-vertical-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/arrow-reload-vertical-2", "name": "arrow-reload-vertical-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/arrow-roadmap", "name": "arrow-roadmap", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/arrow-round-left", "name": "arrow-round-left", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/arrow-round-right", "name": "arrow-round-right", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/arrow-shrink-diagonal-1", "name": "arrow-shrink-diagonal-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/arrow-shrink-diagonal-2", "name": "arrow-shrink-diagonal-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/arrow-shrink", "name": "arrow-shrink", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/arrow-transfer-diagonal-1", "name": "arrow-transfer-diagonal-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/arrow-transfer-diagonal-2", "name": "arrow-transfer-diagonal-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/arrow-transfer-diagonal-3", "name": "arrow-transfer-diagonal-3", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/arrow-up-1", "name": "arrow-up-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/arrow-up-dashed-square", "name": "arrow-up-dashed-square", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/ascending-number-order", "name": "ascending-number-order", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/attribution", "name": "attribution", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/blank-calendar", "name": "blank-calendar", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/blank-notepad", "name": "blank-notepad", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/block-bell-notification", "name": "block-bell-notification", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/bomb", "name": "bomb", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/bookmark", "name": "bookmark", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/braces-circle", "name": "braces-circle", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/brightness-1", "name": "brightness-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/brightness-2", "name": "brightness-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/brightness-3", "name": "brightness-3", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/broken-link-2", "name": "broken-link-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/bullet-list", "name": "bullet-list", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/calendar-add", "name": "calendar-add", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/calendar-edit", "name": "calendar-edit", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/calendar-jump-to-date", "name": "calendar-jump-to-date", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/calendar-star", "name": "calendar-star", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/celsius", "name": "celsius", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/check-square", "name": "check-square", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/check", "name": "check", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/circle-clock", "name": "circle-clock", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/circle", "name": "circle", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/clipboard-add", "name": "clipboard-add", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/clipboard-check", "name": "clipboard-check", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/clipboard-remove", "name": "clipboard-remove", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/cloud", "name": "cloud", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/cog", "name": "cog", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/color-palette", "name": "color-palette", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/color-picker", "name": "color-picker", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/color-swatches", "name": "color-swatches", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/cone-shape", "name": "cone-shape", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/convert-PDF-2", "name": "convert-PDF-2", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/copy-paste", "name": "copy-paste", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/creative-commons", "name": "creative-commons", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/crop-selection", "name": "crop-selection", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/crown", "name": "crown", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/customer-support-1", "name": "customer-support-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/cut", "name": "cut", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/dark-dislay-mode", "name": "dark-dislay-mode", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/dashboard-3", "name": "dashboard-3", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/dashboard-circle", "name": "dashboard-circle", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/delete-1", "name": "delete-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/descending-number-order", "name": "descending-number-order", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/disable-bell-notification", "name": "disable-bell-notification", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/disable-heart", "name": "disable-heart", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/division-circle", "name": "division-circle", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/download-box-1", "name": "download-box-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/download-circle", "name": "download-circle", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/download-computer", "name": "download-computer", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/download-file", "name": "download-file", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/empty-clipboard", "name": "empty-clipboard", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/equal-sign", "name": "equal-sign", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/expand-horizontal-1", "name": "expand-horizontal-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/expand-window-2", "name": "expand-window-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/expand", "name": "expand", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/face-scan-1", "name": "face-scan-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/factorial", "name": "factorial", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/fahrenheit", "name": "fahrenheit", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/fastforward-clock", "name": "fastforward-clock", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/file-add-alternate", "name": "file-add-alternate", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/file-delete-alternate", "name": "file-delete-alternate", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/file-remove-alternate", "name": "file-remove-alternate", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/filter-2", "name": "filter-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/fingerprint-1", "name": "fingerprint-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/fingerprint-2", "name": "fingerprint-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/fist", "name": "fist", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/fit-to-height-square", "name": "fit-to-height-square", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/flip-vertical-arrow-2", "name": "flip-vertical-arrow-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/flip-vertical-circle-1", "name": "flip-vertical-circle-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/flip-vertical-square-2", "name": "flip-vertical-square-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/folder-add", "name": "folder-add", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/folder-check", "name": "folder-check", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/folder-delete", "name": "folder-delete", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/front-camera", "name": "front-camera", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/gif-format", "name": "gif-format", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/give-gift", "name": "give-gift", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/glasses", "name": "glasses", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/half-star-1", "name": "half-star-1", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/hand-cursor", "name": "hand-cursor", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/hand-grab", "name": "hand-grab", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/heading-1-paragraph-styles-heading", "name": "heading-1-paragraph-styles-heading", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/heading-2-paragraph-styles-heading", "name": "heading-2-paragraph-styles-heading", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/heading-3-paragraph-styles-heading", "name": "heading-3-paragraph-styles-heading", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/heart", "name": "heart", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/help-chat-2", "name": "help-chat-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/help-question-1", "name": "help-question-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/hierarchy-10", "name": "hierarchy-10", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/hierarchy-13", "name": "hierarchy-13", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/hierarchy-14", "name": "hierarchy-14", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/hierarchy-2", "name": "hierarchy-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/hierarchy-4", "name": "hierarchy-4", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/hierarchy-7", "name": "hierarchy-7", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/home-3", "name": "home-3", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/home-4", "name": "home-4", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/horizontal-menu-circle", "name": "horizontal-menu-circle", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/humidity-none", "name": "humidity-none", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/image-blur", "name": "image-blur", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/image-saturation", "name": "image-saturation", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/information-circle", "name": "information-circle", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/input-box", "name": "input-box", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/insert-side", "name": "insert-side", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/insert-top-left", "name": "insert-top-left", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/insert-top-right", "name": "insert-top-right", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/invisible-1", "name": "invisible-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/invisible-2", "name": "invisible-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/jump-object", "name": "jump-object", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/key", "name": "key", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/keyhole-lock-circle", "name": "keyhole-lock-circle", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/lasso-tool", "name": "lasso-tool", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/layers-1", "name": "layers-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/layers-2", "name": "layers-2", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/layout-window-1", "name": "layout-window-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/layout-window-11", "name": "layout-window-11", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/layout-window-2", "name": "layout-window-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/layout-window-8", "name": "layout-window-8", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/lightbulb", "name": "lightbulb", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/like-1", "name": "like-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/link-chain", "name": "link-chain", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/live-video", "name": "live-video", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/lock-rotation", "name": "lock-rotation", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/login-1", "name": "login-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/logout-1", "name": "logout-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/loop-1", "name": "loop-1", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/magic-wand-2", "name": "magic-wand-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/magnifying-glass-circle", "name": "magnifying-glass-circle", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/magnifying-glass", "name": "magnifying-glass", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/manual-book", "name": "manual-book", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/megaphone-2", "name": "megaphone-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/minimize-window-2", "name": "minimize-window-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/moon-cloud", "name": "moon-cloud", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/move-left", "name": "move-left", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/move-right", "name": "move-right", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/multiple-file-2", "name": "multiple-file-2", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/music-folder-song", "name": "music-folder-song", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/new-file", "name": "new-file", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/new-folder", "name": "new-folder", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/new-sticky-note", "name": "new-sticky-note", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/not-equal-sign", "name": "not-equal-sign", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/ok-hand", "name": "ok-hand", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/one-finger-drag-horizontal", "name": "one-finger-drag-horizontal", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/one-finger-drag-vertical", "name": "one-finger-drag-vertical", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/one-finger-hold", "name": "one-finger-hold", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/one-finger-tap", "name": "one-finger-tap", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/open-book", "name": "open-book", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/open-umbrella", "name": "open-umbrella", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/padlock-square-1", "name": "padlock-square-1", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/page-setting", "name": "page-setting", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/paint-bucket", "name": "paint-bucket", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/paint-palette", "name": "paint-palette", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/paintbrush-1", "name": "paintbrush-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/paintbrush-2", "name": "paintbrush-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/paperclip-1", "name": "paperclip-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/paragraph", "name": "paragraph", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/pathfinder-divide", "name": "pathfinder-divide", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/pathfinder-exclude", "name": "pathfinder-exclude", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/pathfinder-intersect", "name": "pathfinder-intersect", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/pathfinder-merge", "name": "pathfinder-merge", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/pathfinder-minus-front-1", "name": "pathfinder-minus-front-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/pathfinder-trim", "name": "pathfinder-trim", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/pathfinder-union", "name": "pathfinder-union", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/peace-hand", "name": "peace-hand", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/pen-3", "name": "pen-3", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/pen-draw", "name": "pen-draw", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/pen-tool", "name": "pen-tool", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/pencil", "name": "pencil", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/pentagon", "name": "pentagon", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/pi-symbol-circle", "name": "pi-symbol-circle", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/pictures-folder-memories", "name": "pictures-folder-memories", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/podium", "name": "podium", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/polygon", "name": "polygon", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/praying-hand", "name": "praying-hand", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/projector-board", "name": "projector-board", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/pyramid-shape", "name": "pyramid-shape", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/quotation-2", "name": "quotation-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/radioactive-2", "name": "radioactive-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/rain-cloud", "name": "rain-cloud", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/recycle-bin-2", "name": "recycle-bin-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/ringing-bell-notification", "name": "ringing-bell-notification", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/rock-and-roll-hand", "name": "rock-and-roll-hand", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/rotate-angle-45", "name": "rotate-angle-45", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/round-cap", "name": "round-cap", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/satellite-dish", "name": "satellite-dish", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/scanner", "name": "scanner", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/search-visual", "name": "search-visual", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/select-circle-area-1", "name": "select-circle-area-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/share-link", "name": "share-link", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/shield-1", "name": "shield-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/shield-2", "name": "shield-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/shield-check", "name": "shield-check", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/shield-cross", "name": "shield-cross", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/shrink-horizontal-1", "name": "shrink-horizontal-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/shuffle", "name": "shuffle", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/sigma", "name": "sigma", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/skull-1", "name": "skull-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/sleep", "name": "sleep", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/snow-flake", "name": "snow-flake", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/sort-descending", "name": "sort-descending", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/spiral-shape", "name": "spiral-shape", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/split-vertical", "name": "split-vertical", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/spray-paint", "name": "spray-paint", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/square-brackets-circle", "name": "square-brackets-circle", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/square-cap", "name": "square-cap", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/square-clock", "name": "square-clock", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/square-root-x-circle", "name": "square-root-x-circle", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/star-1", "name": "star-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/star-2", "name": "star-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/star-badge", "name": "star-badge", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/straight-cap", "name": "straight-cap", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/subtract-1", "name": "subtract-1", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/subtract-circle", "name": "subtract-circle", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/subtract-square", "name": "subtract-square", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/sun-cloud", "name": "sun-cloud", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/synchronize-disable", "name": "synchronize-disable", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/synchronize-warning", "name": "synchronize-warning", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/table-lamp-1", "name": "table-lamp-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/tag", "name": "tag", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/text-flow-rows", "name": "text-flow-rows", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/text-square", "name": "text-square", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/text-style", "name": "text-style", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/thermometer", "name": "thermometer", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/trending-content", "name": "trending-content", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/trophy", "name": "trophy", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/two-finger-drag-hotizontal", "name": "two-finger-drag-hotizontal", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/two-finger-tap", "name": "two-finger-tap", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/underline-text-1", "name": "underline-text-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/upload-box-1", "name": "upload-box-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/upload-circle", "name": "upload-circle", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/upload-computer", "name": "upload-computer", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/upload-file", "name": "upload-file", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/user-add-plus", "name": "user-add-plus", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/user-check-validate", "name": "user-check-validate", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/user-circle-single", "name": "user-circle-single", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/user-identifier-card", "name": "user-identifier-card", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/user-multiple-circle", "name": "user-multiple-circle", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/user-multiple-group", "name": "user-multiple-group", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/user-profile-focus", "name": "user-profile-focus", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/user-protection-2", "name": "user-protection-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/user-remove-subtract", "name": "user-remove-subtract", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/user-single-neutral-male", "name": "user-single-neutral-male", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/user-sync-online-in-person", "name": "user-sync-online-in-person", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/vertical-slider-square", "name": "vertical-slider-square", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/video-swap-camera", "name": "video-swap-camera", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/visible", "name": "visible", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/voice-scan-2", "name": "voice-scan-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/waning-cresent-moon", "name": "waning-cresent-moon", "keywords": [], "content": "\n\n\n" }, { "id": "interface_essential/warning-octagon", "name": "warning-octagon", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "interface_essential/warning-triangle", "name": "warning-triangle", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" } ], "mail": [ { "id": "mail/chat-bubble-oval-notification", "name": "chat-bubble-oval-notification", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/chat-bubble-oval-smiley-1", "name": "chat-bubble-oval-smiley-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/chat-bubble-oval-smiley-2", "name": "chat-bubble-oval-smiley-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/chat-bubble-oval", "name": "chat-bubble-oval", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/chat-bubble-square-block", "name": "chat-bubble-square-block", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/chat-bubble-square-question", "name": "chat-bubble-square-question", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/chat-bubble-square-warning", "name": "chat-bubble-square-warning", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/chat-bubble-square-write", "name": "chat-bubble-square-write", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/chat-bubble-text-square", "name": "chat-bubble-text-square", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/chat-bubble-typing-oval", "name": "chat-bubble-typing-oval", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/chat-two-bubbles-oval", "name": "chat-two-bubbles-oval", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/discussion-converstion-reply", "name": "discussion-converstion-reply", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/happy-face", "name": "happy-face", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/inbox-block", "name": "inbox-block", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/inbox-favorite-heart", "name": "inbox-favorite-heart", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/inbox-favorite", "name": "inbox-favorite", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/inbox-lock", "name": "inbox-lock", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/inbox-tray-1", "name": "inbox-tray-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/inbox-tray-2", "name": "inbox-tray-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/mail-incoming", "name": "mail-incoming", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/mail-search", "name": "mail-search", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/mail-send-email-message", "name": "mail-send-email-message", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/mail-send-envelope", "name": "mail-send-envelope", "keywords": [], "content": "\n\n\n" }, { "id": "mail/mail-send-reply-all", "name": "mail-send-reply-all", "keywords": [], "content": "\n\n\n" }, { "id": "mail/sad-face", "name": "sad-face", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/send-email", "name": "send-email", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/sign-at", "name": "sign-at", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/sign-hashtag", "name": "sign-hashtag", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/smiley-angry", "name": "smiley-angry", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/smiley-cool", "name": "smiley-cool", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/smiley-crying-1", "name": "smiley-crying-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/smiley-cute", "name": "smiley-cute", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/smiley-drool", "name": "smiley-drool", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/smiley-emoji-kiss-nervous", "name": "smiley-emoji-kiss-nervous", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/smiley-emoji-terrified", "name": "smiley-emoji-terrified", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/smiley-grumpy", "name": "smiley-grumpy", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/smiley-happy", "name": "smiley-happy", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/smiley-in-love", "name": "smiley-in-love", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/smiley-kiss", "name": "smiley-kiss", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "mail/smiley-laughing-3", "name": "smiley-laughing-3", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" } ], "map_travel": [ { "id": "map_travel/airplane", "name": "airplane", "keywords": [], "content": "\n\n\n" }, { "id": "map_travel/airport-plane-transit", "name": "airport-plane-transit", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/airport-plane", "name": "airport-plane", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/airport-security", "name": "airport-security", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/anchor", "name": "anchor", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/baggage", "name": "baggage", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/beach", "name": "beach", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/bicycle-bike", "name": "bicycle-bike", "keywords": [], "content": "\n\n\n" }, { "id": "map_travel/braille-blind", "name": "braille-blind", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/bus", "name": "bus", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/camping-tent", "name": "camping-tent", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/cane", "name": "cane", "keywords": [], "content": "\n\n\n" }, { "id": "map_travel/capitol", "name": "capitol", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/car-battery-charging", "name": "car-battery-charging", "keywords": [], "content": "\n\n\n" }, { "id": "map_travel/car-taxi-1", "name": "car-taxi-1", "keywords": [], "content": "\n\n\n\n\n" }, { "id": "map_travel/city-hall", "name": "city-hall", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/compass-navigator", "name": "compass-navigator", "keywords": [], "content": "\n\n\n" }, { "id": "map_travel/crutch", "name": "crutch", "keywords": [], "content": "\n\n\n" }, { "id": "map_travel/dangerous-zone-sign", "name": "dangerous-zone-sign", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/earth-1", "name": "earth-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/earth-airplane", "name": "earth-airplane", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/emergency-exit", "name": "emergency-exit", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/fire-alarm-2", "name": "fire-alarm-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/fire-extinguisher-sign", "name": "fire-extinguisher-sign", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/gas-station-fuel-petroleum", "name": "gas-station-fuel-petroleum", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/hearing-deaf-1", "name": "hearing-deaf-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/hearing-deaf-2", "name": "hearing-deaf-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/high-speed-train-front", "name": "high-speed-train-front", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/hot-spring", "name": "hot-spring", "keywords": [], "content": "\n\n\n" }, { "id": "map_travel/hotel-air-conditioner", "name": "hotel-air-conditioner", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/hotel-bed-2", "name": "hotel-bed-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/hotel-laundry", "name": "hotel-laundry", "keywords": [], "content": "\n\n\n" }, { "id": "map_travel/hotel-one-star", "name": "hotel-one-star", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/hotel-shower-head", "name": "hotel-shower-head", "keywords": [], "content": "\n\n\n" }, { "id": "map_travel/hotel-two-star", "name": "hotel-two-star", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/information-desk-customer", "name": "information-desk-customer", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/information-desk", "name": "information-desk", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/iron", "name": "iron", "keywords": [], "content": "\n\n\n" }, { "id": "map_travel/ladder", "name": "ladder", "keywords": [], "content": "\n\n\n" }, { "id": "map_travel/lift-disability", "name": "lift-disability", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/lift", "name": "lift", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/location-compass-1", "name": "location-compass-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/location-pin-3", "name": "location-pin-3", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/location-pin-disabled", "name": "location-pin-disabled", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/location-target-1", "name": "location-target-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/lost-and-found", "name": "lost-and-found", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/man-symbol", "name": "man-symbol", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/map-fold", "name": "map-fold", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/navigation-arrow-off", "name": "navigation-arrow-off", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/navigation-arrow-on", "name": "navigation-arrow-on", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/parking-sign", "name": "parking-sign", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/parliament", "name": "parliament", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/passport", "name": "passport", "keywords": [], "content": "\n\n\n" }, { "id": "map_travel/pet-paw", "name": "pet-paw", "keywords": [], "content": "\n\n\n" }, { "id": "map_travel/pets-allowed", "name": "pets-allowed", "keywords": [], "content": "\n\n\n" }, { "id": "map_travel/pool-ladder", "name": "pool-ladder", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/rock-slide", "name": "rock-slide", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/sail-ship", "name": "sail-ship", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/school-bus-side", "name": "school-bus-side", "keywords": [], "content": "\n\n\n" }, { "id": "map_travel/smoke-detector", "name": "smoke-detector", "keywords": [], "content": "\n\n\n" }, { "id": "map_travel/smoking-area", "name": "smoking-area", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/snorkle", "name": "snorkle", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/steering-wheel", "name": "steering-wheel", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/street-road", "name": "street-road", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/street-sign", "name": "street-sign", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/take-off", "name": "take-off", "keywords": [], "content": "\n\n\n" }, { "id": "map_travel/toilet-man", "name": "toilet-man", "keywords": [], "content": "\n\n\n" }, { "id": "map_travel/toilet-sign-man-woman-2", "name": "toilet-sign-man-woman-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/toilet-women", "name": "toilet-women", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/traffic-cone", "name": "traffic-cone", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "map_travel/triangle-flag", "name": "triangle-flag", "keywords": [], "content": "\n\n\n" }, { "id": "map_travel/wheelchair-1", "name": "wheelchair-1", "keywords": [], "content": "\n\n\n" }, { "id": "map_travel/woman-symbol", "name": "woman-symbol", "keywords": [], "content": "\n\n\n" } ], "money_shopping": [ { "id": "money_shopping/annoncement-megaphone", "name": "annoncement-megaphone", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/backpack", "name": "backpack", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/bag-dollar", "name": "bag-dollar", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/bag-pound", "name": "bag-pound", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/bag-rupee", "name": "bag-rupee", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/bag-suitcase-1", "name": "bag-suitcase-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/bag-suitcase-2", "name": "bag-suitcase-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/bag-yen", "name": "bag-yen", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/bag", "name": "bag", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/ball", "name": "ball", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/bank", "name": "bank", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/beanie", "name": "beanie", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/bill-1", "name": "bill-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/bill-2", "name": "bill-2", "keywords": [], "content": "\n\n\n" }, { "id": "money_shopping/bill-4", "name": "bill-4", "keywords": [], "content": "\n\n\n" }, { "id": "money_shopping/bill-cashless", "name": "bill-cashless", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/binance-circle", "name": "binance-circle", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/bitcoin", "name": "bitcoin", "keywords": [], "content": "\n\n\n" }, { "id": "money_shopping/bow-tie", "name": "bow-tie", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/briefcase-dollar", "name": "briefcase-dollar", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/building-2", "name": "building-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/business-card", "name": "business-card", "keywords": [], "content": "\n\n\n" }, { "id": "money_shopping/business-handshake", "name": "business-handshake", "keywords": [], "content": "\n\n\n" }, { "id": "money_shopping/business-idea-money", "name": "business-idea-money", "keywords": [], "content": "\n\n\n" }, { "id": "money_shopping/business-profession-home-office", "name": "business-profession-home-office", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/business-progress-bar-2", "name": "business-progress-bar-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/business-user-curriculum", "name": "business-user-curriculum", "keywords": [], "content": "\n\n\n" }, { "id": "money_shopping/calculator-1", "name": "calculator-1", "keywords": [], "content": "\n\n\n" }, { "id": "money_shopping/calculator-2", "name": "calculator-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/cane", "name": "cane", "keywords": [], "content": "\n\n\n" }, { "id": "money_shopping/chair", "name": "chair", "keywords": [], "content": "\n\n\n" }, { "id": "money_shopping/closet", "name": "closet", "keywords": [], "content": "\n\n\n" }, { "id": "money_shopping/coin-share", "name": "coin-share", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/coins-stack", "name": "coins-stack", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/credit-card-1", "name": "credit-card-1", "keywords": [], "content": "\n\n\n" }, { "id": "money_shopping/credit-card-2", "name": "credit-card-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/diamond-2", "name": "diamond-2", "keywords": [], "content": "\n\n\n" }, { "id": "money_shopping/discount-percent-badge", "name": "discount-percent-badge", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/discount-percent-circle", "name": "discount-percent-circle", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/discount-percent-coupon", "name": "discount-percent-coupon", "keywords": [], "content": "\n\n\n" }, { "id": "money_shopping/discount-percent-cutout", "name": "discount-percent-cutout", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/discount-percent-fire", "name": "discount-percent-fire", "keywords": [], "content": "\n\n\n" }, { "id": "money_shopping/dollar-coin-1", "name": "dollar-coin-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/dollar-coin", "name": "dollar-coin", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/dressing-table", "name": "dressing-table", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/ethereum-circle", "name": "ethereum-circle", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/ethereum", "name": "ethereum", "keywords": [], "content": "\n\n\n" }, { "id": "money_shopping/euro", "name": "euro", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/gift-2", "name": "gift-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/gift", "name": "gift", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/gold", "name": "gold", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/graph-arrow-decrease", "name": "graph-arrow-decrease", "keywords": [], "content": "\n\n\n" }, { "id": "money_shopping/graph-arrow-increase", "name": "graph-arrow-increase", "keywords": [], "content": "\n\n\n" }, { "id": "money_shopping/graph-bar-decrease", "name": "graph-bar-decrease", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/graph-bar-increase", "name": "graph-bar-increase", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/graph-dot", "name": "graph-dot", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/graph", "name": "graph", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/investment-selection", "name": "investment-selection", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/justice-hammer", "name": "justice-hammer", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/justice-scale-1", "name": "justice-scale-1", "keywords": [], "content": "\n\n\n" }, { "id": "money_shopping/justice-scale-2", "name": "justice-scale-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/lipstick", "name": "lipstick", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/make-up-brush", "name": "make-up-brush", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/moustache", "name": "moustache", "keywords": [], "content": "\n\n\n" }, { "id": "money_shopping/mouth-lip", "name": "mouth-lip", "keywords": [], "content": "\n\n\n" }, { "id": "money_shopping/necklace", "name": "necklace", "keywords": [], "content": "\n\n\n" }, { "id": "money_shopping/necktie", "name": "necktie", "keywords": [], "content": "\n\n\n" }, { "id": "money_shopping/payment-10", "name": "payment-10", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/payment-cash-out-3", "name": "payment-cash-out-3", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/pie-chart", "name": "pie-chart", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/piggy-bank", "name": "piggy-bank", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/polka-dot-circle", "name": "polka-dot-circle", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/production-belt", "name": "production-belt", "keywords": [], "content": "\n\n\n" }, { "id": "money_shopping/qr-code", "name": "qr-code", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/receipt-add", "name": "receipt-add", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/receipt-check", "name": "receipt-check", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/receipt-subtract", "name": "receipt-subtract", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/receipt", "name": "receipt", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/safe-vault", "name": "safe-vault", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/scanner-3", "name": "scanner-3", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/scanner-bar-code", "name": "scanner-bar-code", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/shelf", "name": "shelf", "keywords": [], "content": "\n\n\n" }, { "id": "money_shopping/shopping-bag-hand-bag-2", "name": "shopping-bag-hand-bag-2", "keywords": [], "content": "\n\n\n" }, { "id": "money_shopping/shopping-basket-1", "name": "shopping-basket-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/shopping-basket-2", "name": "shopping-basket-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/shopping-cart-1", "name": "shopping-cart-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/shopping-cart-2", "name": "shopping-cart-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/shopping-cart-3", "name": "shopping-cart-3", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/shopping-cart-add", "name": "shopping-cart-add", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/shopping-cart-check", "name": "shopping-cart-check", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/shopping-cart-subtract", "name": "shopping-cart-subtract", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/signage-3", "name": "signage-3", "keywords": [], "content": "\n\n\n" }, { "id": "money_shopping/signage-4", "name": "signage-4", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/startup", "name": "startup", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/stock", "name": "stock", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/store-1", "name": "store-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/store-2", "name": "store-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/store-computer", "name": "store-computer", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/subscription-cashflow", "name": "subscription-cashflow", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/tag", "name": "tag", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/tall-hat", "name": "tall-hat", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/target-3", "name": "target-3", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/target", "name": "target", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/wallet-purse", "name": "wallet-purse", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/wallet", "name": "wallet", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/xrp-circle", "name": "xrp-circle", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/yuan-circle", "name": "yuan-circle", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "money_shopping/yuan", "name": "yuan", "keywords": [], "content": "\n\n\n" } ], "nature_ecology": [ { "id": "nature_ecology/affordable-and-clean-energy", "name": "affordable-and-clean-energy", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "nature_ecology/alien", "name": "alien", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "nature_ecology/bone", "name": "bone", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "nature_ecology/cat-1", "name": "cat-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "nature_ecology/circle-flask", "name": "circle-flask", "keywords": [], "content": "\n\n\n" }, { "id": "nature_ecology/clean-water-and-sanitation", "name": "clean-water-and-sanitation", "keywords": [], "content": "\n\n\n" }, { "id": "nature_ecology/comet", "name": "comet", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "nature_ecology/decent-work-and-economic-growth", "name": "decent-work-and-economic-growth", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "nature_ecology/dna", "name": "dna", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "nature_ecology/erlenmeyer-flask", "name": "erlenmeyer-flask", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "nature_ecology/flower", "name": "flower", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "nature_ecology/galaxy-1", "name": "galaxy-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "nature_ecology/galaxy-2", "name": "galaxy-2", "keywords": [], "content": "\n\n\n" }, { "id": "nature_ecology/gender-equality", "name": "gender-equality", "keywords": [], "content": "\n\n\n" }, { "id": "nature_ecology/good-health-and-well-being", "name": "good-health-and-well-being", "keywords": [], "content": "\n\n\n" }, { "id": "nature_ecology/industry-innovation-and-infrastructure", "name": "industry-innovation-and-infrastructure", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "nature_ecology/leaf", "name": "leaf", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "nature_ecology/log", "name": "log", "keywords": [], "content": "\n\n\n" }, { "id": "nature_ecology/no-poverty", "name": "no-poverty", "keywords": [], "content": "\n\n\n" }, { "id": "nature_ecology/octopus", "name": "octopus", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "nature_ecology/planet", "name": "planet", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "nature_ecology/potted-flower-tulip", "name": "potted-flower-tulip", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "nature_ecology/quality-education", "name": "quality-education", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "nature_ecology/rainbow", "name": "rainbow", "keywords": [], "content": "\n\n\n" }, { "id": "nature_ecology/recycle-1", "name": "recycle-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "nature_ecology/reduced-inequalities", "name": "reduced-inequalities", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "nature_ecology/rose", "name": "rose", "keywords": [], "content": "\n\n\n" }, { "id": "nature_ecology/shell", "name": "shell", "keywords": [], "content": "\n\n\n" }, { "id": "nature_ecology/shovel-rake", "name": "shovel-rake", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "nature_ecology/sprout", "name": "sprout", "keywords": [], "content": "\n\n\n" }, { "id": "nature_ecology/telescope", "name": "telescope", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "nature_ecology/test-tube", "name": "test-tube", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "nature_ecology/tidal-wave", "name": "tidal-wave", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n\n" }, { "id": "nature_ecology/tree-2", "name": "tree-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "nature_ecology/tree-3", "name": "tree-3", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "nature_ecology/volcano", "name": "volcano", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "nature_ecology/windmill", "name": "windmill", "keywords": [], "content": "\n\n\n" }, { "id": "nature_ecology/zero-hunger", "name": "zero-hunger", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" } ], "phone": [ { "id": "phone/airplane-disabled", "name": "airplane-disabled", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "phone/airplane-enabled", "name": "airplane-enabled", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "phone/back-camera-1", "name": "back-camera-1", "keywords": [], "content": "\n\n\n" }, { "id": "phone/call-hang-up", "name": "call-hang-up", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "phone/cellular-network-4g", "name": "cellular-network-4g", "keywords": [], "content": "\n\n\n" }, { "id": "phone/cellular-network-5g", "name": "cellular-network-5g", "keywords": [], "content": "\n\n\n" }, { "id": "phone/cellular-network-lte", "name": "cellular-network-lte", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "phone/contact-phonebook-2", "name": "contact-phonebook-2", "keywords": [], "content": "\n\n\n" }, { "id": "phone/hang-up-1", "name": "hang-up-1", "keywords": [], "content": "\n\n\n" }, { "id": "phone/hang-up-2", "name": "hang-up-2", "keywords": [], "content": "\n\n\n" }, { "id": "phone/incoming-call", "name": "incoming-call", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "phone/missed-call", "name": "missed-call", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "phone/notification-alarm-2", "name": "notification-alarm-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "phone/notification-application-1", "name": "notification-application-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "phone/notification-application-2", "name": "notification-application-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "phone/notification-message-alert", "name": "notification-message-alert", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "phone/outgoing-call", "name": "outgoing-call", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "phone/phone-mobile-phone", "name": "phone-mobile-phone", "keywords": [], "content": "\n\n\n" }, { "id": "phone/phone-qr", "name": "phone-qr", "keywords": [], "content": "\n\n\n" }, { "id": "phone/phone-ringing-1", "name": "phone-ringing-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "phone/phone-ringing-2", "name": "phone-ringing-2", "keywords": [], "content": "\n\n\n" }, { "id": "phone/phone", "name": "phone", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "phone/signal-full", "name": "signal-full", "keywords": [], "content": "\n\n\n" }, { "id": "phone/signal-low", "name": "signal-low", "keywords": [], "content": "\n\n\n" }, { "id": "phone/signal-medium", "name": "signal-medium", "keywords": [], "content": "\n\n\n" }, { "id": "phone/signal-none", "name": "signal-none", "keywords": [], "content": "\n\n\n" } ], "programing": [ { "id": "programing/application-add", "name": "application-add", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/bracket", "name": "bracket", "keywords": [], "content": "\n\n\n" }, { "id": "programing/browser-add", "name": "browser-add", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/browser-block", "name": "browser-block", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/browser-build", "name": "browser-build", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/browser-check", "name": "browser-check", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/browser-delete", "name": "browser-delete", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/browser-hash", "name": "browser-hash", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/browser-lock", "name": "browser-lock", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/browser-multiple-window", "name": "browser-multiple-window", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/browser-remove", "name": "browser-remove", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/browser-website-1", "name": "browser-website-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/bug-antivirus-debugging", "name": "bug-antivirus-debugging", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/bug-antivirus-shield", "name": "bug-antivirus-shield", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/bug-virus-browser", "name": "bug-virus-browser", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/bug-virus-document", "name": "bug-virus-document", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/bug-virus-folder", "name": "bug-virus-folder", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/bug", "name": "bug", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/cloud-add", "name": "cloud-add", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/cloud-block", "name": "cloud-block", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/cloud-check", "name": "cloud-check", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/cloud-data-transfer", "name": "cloud-data-transfer", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/cloud-refresh", "name": "cloud-refresh", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/cloud-share", "name": "cloud-share", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/cloud-warning", "name": "cloud-warning", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/cloud-wifi", "name": "cloud-wifi", "keywords": [], "content": "\n\n\n" }, { "id": "programing/code-analysis", "name": "code-analysis", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/code-monitor-1", "name": "code-monitor-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/code-monitor-2", "name": "code-monitor-2", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/css-three", "name": "css-three", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/curly-brackets", "name": "curly-brackets", "keywords": [], "content": "\n\n\n" }, { "id": "programing/file-code-1", "name": "file-code-1", "keywords": [], "content": "\n\n\n" }, { "id": "programing/incognito-mode", "name": "incognito-mode", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/insert-cloud-video", "name": "insert-cloud-video", "keywords": [], "content": "\n\n\n" }, { "id": "programing/markdown-circle-programming", "name": "markdown-circle-programming", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/markdown-document-programming", "name": "markdown-document-programming", "keywords": [], "content": "\n\n\n" }, { "id": "programing/module-puzzle-1", "name": "module-puzzle-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/module-puzzle-3", "name": "module-puzzle-3", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/module-three", "name": "module-three", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "programing/rss-square", "name": "rss-square", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" } ], "shipping": [ { "id": "shipping/box-sign", "name": "box-sign", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "shipping/container", "name": "container", "keywords": [], "content": "\n\n\n" }, { "id": "shipping/fragile", "name": "fragile", "keywords": [], "content": "\n\n\n" }, { "id": "shipping/parachute-drop", "name": "parachute-drop", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "shipping/shipment-add", "name": "shipment-add", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "shipping/shipment-check", "name": "shipment-check", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "shipping/shipment-download", "name": "shipment-download", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "shipping/shipment-remove", "name": "shipment-remove", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "shipping/shipment-upload", "name": "shipment-upload", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "shipping/shipping-box-1", "name": "shipping-box-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "shipping/shipping-truck", "name": "shipping-truck", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "shipping/transfer-motorcycle", "name": "transfer-motorcycle", "keywords": [], "content": "\n\n\n" }, { "id": "shipping/transfer-van", "name": "transfer-van", "keywords": [], "content": "\n\n\n" }, { "id": "shipping/warehouse-1", "name": "warehouse-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" } ], "work_education": [ { "id": "work_education/book-reading", "name": "book-reading", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "work_education/class-lesson", "name": "class-lesson", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "work_education/collaborations-idea", "name": "collaborations-idea", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "work_education/definition-search-book", "name": "definition-search-book", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "work_education/dictionary-language-book", "name": "dictionary-language-book", "keywords": [], "content": "\n\n\n" }, { "id": "work_education/global-learning", "name": "global-learning", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "work_education/graduation-cap", "name": "graduation-cap", "keywords": [], "content": "\n\n\n" }, { "id": "work_education/group-meeting-call", "name": "group-meeting-call", "keywords": [], "content": "\n\n\n" }, { "id": "work_education/office-building-1", "name": "office-building-1", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "work_education/office-worker", "name": "office-worker", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "work_education/search-dollar", "name": "search-dollar", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" }, { "id": "work_education/strategy-tasks", "name": "strategy-tasks", "keywords": [], "content": "\n\n\n" }, { "id": "work_education/task-list", "name": "task-list", "keywords": [], "content": "\n\n\n" }, { "id": "work_education/workspace-desk", "name": "workspace-desk", "keywords": [], "content": "\n\n\n\n\n\n\n\n\n\n" } ] } \ No newline at end of file diff --git a/frontend/appflowy_web_app/public/af_icons/images_photography/auto-flash.svg b/frontend/appflowy_web_app/public/af_icons/images_photography/auto-flash.svg deleted file mode 100644 index 0c1936fb80..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/images_photography/auto-flash.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/images_photography/camera-1.svg b/frontend/appflowy_web_app/public/af_icons/images_photography/camera-1.svg deleted file mode 100644 index 6b6609071c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/images_photography/camera-1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/images_photography/camera-disabled.svg b/frontend/appflowy_web_app/public/af_icons/images_photography/camera-disabled.svg deleted file mode 100644 index 4f4c45d181..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/images_photography/camera-disabled.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/images_photography/camera-loading.svg b/frontend/appflowy_web_app/public/af_icons/images_photography/camera-loading.svg deleted file mode 100644 index ad3ec3d08d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/images_photography/camera-loading.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/images_photography/camera-square.svg b/frontend/appflowy_web_app/public/af_icons/images_photography/camera-square.svg deleted file mode 100644 index f90f048eaf..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/images_photography/camera-square.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/images_photography/composition-oval.svg b/frontend/appflowy_web_app/public/af_icons/images_photography/composition-oval.svg deleted file mode 100644 index 1799610d70..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/images_photography/composition-oval.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/images_photography/composition-vertical.svg b/frontend/appflowy_web_app/public/af_icons/images_photography/composition-vertical.svg deleted file mode 100644 index 758a66a9a2..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/images_photography/composition-vertical.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/images_photography/compsition-horizontal.svg b/frontend/appflowy_web_app/public/af_icons/images_photography/compsition-horizontal.svg deleted file mode 100644 index b4b5ed760d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/images_photography/compsition-horizontal.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/images_photography/edit-image-photo.svg b/frontend/appflowy_web_app/public/af_icons/images_photography/edit-image-photo.svg deleted file mode 100644 index fc9c7e8b3f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/images_photography/edit-image-photo.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/images_photography/film-roll-1.svg b/frontend/appflowy_web_app/public/af_icons/images_photography/film-roll-1.svg deleted file mode 100644 index d657abec5d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/images_photography/film-roll-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/images_photography/film-slate.svg b/frontend/appflowy_web_app/public/af_icons/images_photography/film-slate.svg deleted file mode 100644 index 8fd8f3fed8..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/images_photography/film-slate.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/images_photography/flash-1.svg b/frontend/appflowy_web_app/public/af_icons/images_photography/flash-1.svg deleted file mode 100644 index f1814e8186..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/images_photography/flash-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/images_photography/flash-2.svg b/frontend/appflowy_web_app/public/af_icons/images_photography/flash-2.svg deleted file mode 100644 index 24d2d68e07..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/images_photography/flash-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/images_photography/flash-3.svg b/frontend/appflowy_web_app/public/af_icons/images_photography/flash-3.svg deleted file mode 100644 index e98c8193e9..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/images_photography/flash-3.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/images_photography/flash-off.svg b/frontend/appflowy_web_app/public/af_icons/images_photography/flash-off.svg deleted file mode 100644 index 4260106b57..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/images_photography/flash-off.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/images_photography/flower.svg b/frontend/appflowy_web_app/public/af_icons/images_photography/flower.svg deleted file mode 100644 index 87981fa4a1..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/images_photography/flower.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/images_photography/focus-points.svg b/frontend/appflowy_web_app/public/af_icons/images_photography/focus-points.svg deleted file mode 100644 index 149495c4af..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/images_photography/focus-points.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/images_photography/landscape-2.svg b/frontend/appflowy_web_app/public/af_icons/images_photography/landscape-2.svg deleted file mode 100644 index ec970a9893..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/images_photography/landscape-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/images_photography/landscape-setting.svg b/frontend/appflowy_web_app/public/af_icons/images_photography/landscape-setting.svg deleted file mode 100644 index c87b58d38b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/images_photography/landscape-setting.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/images_photography/laptop-camera.svg b/frontend/appflowy_web_app/public/af_icons/images_photography/laptop-camera.svg deleted file mode 100644 index 88d1c7bb8f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/images_photography/laptop-camera.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/images_photography/mobile-phone-camera.svg b/frontend/appflowy_web_app/public/af_icons/images_photography/mobile-phone-camera.svg deleted file mode 100644 index b7b69aa738..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/images_photography/mobile-phone-camera.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/images_photography/orientation-landscape.svg b/frontend/appflowy_web_app/public/af_icons/images_photography/orientation-landscape.svg deleted file mode 100644 index c432b7b046..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/images_photography/orientation-landscape.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/images_photography/orientation-portrait.svg b/frontend/appflowy_web_app/public/af_icons/images_photography/orientation-portrait.svg deleted file mode 100644 index deaf60faf6..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/images_photography/orientation-portrait.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/images_photography/polaroid-four.svg b/frontend/appflowy_web_app/public/af_icons/images_photography/polaroid-four.svg deleted file mode 100644 index 6e9121cd50..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/images_photography/polaroid-four.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/add-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/add-1.svg deleted file mode 100644 index dedf45912b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/add-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/add-bell-notification.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/add-bell-notification.svg deleted file mode 100644 index d8af9e31e5..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/add-bell-notification.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/add-circle.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/add-circle.svg deleted file mode 100644 index 4e6af27c9a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/add-circle.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/add-layer-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/add-layer-2.svg deleted file mode 100644 index 5027acb248..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/add-layer-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/add-square.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/add-square.svg deleted file mode 100644 index 1900a45c11..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/add-square.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/alarm-clock.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/alarm-clock.svg deleted file mode 100644 index 773ca81fe6..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/alarm-clock.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/align-back-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/align-back-1.svg deleted file mode 100644 index 8045f92e3d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/align-back-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/align-center.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/align-center.svg deleted file mode 100644 index 25dd359f6a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/align-center.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/align-front-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/align-front-1.svg deleted file mode 100644 index 402eb326da..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/align-front-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/align-left.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/align-left.svg deleted file mode 100644 index e19e815cfb..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/align-left.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/align-right.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/align-right.svg deleted file mode 100644 index 3ff840a813..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/align-right.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/ampersand.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/ampersand.svg deleted file mode 100644 index 11da33fb20..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/ampersand.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/archive-box.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/archive-box.svg deleted file mode 100644 index 3816bf9f6c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/archive-box.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-bend-left-down-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-bend-left-down-2.svg deleted file mode 100644 index 7df296c604..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-bend-left-down-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-bend-right-down-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-bend-right-down-2.svg deleted file mode 100644 index 4d351c0f8e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-bend-right-down-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-crossover-down.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-crossover-down.svg deleted file mode 100644 index c824a1d2aa..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-crossover-down.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-crossover-left.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-crossover-left.svg deleted file mode 100644 index c64e0771b0..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-crossover-left.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-crossover-right.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-crossover-right.svg deleted file mode 100644 index 1e87e2927b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-crossover-right.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-crossover-up.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-crossover-up.svg deleted file mode 100644 index 8707846460..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-crossover-up.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-cursor-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-cursor-1.svg deleted file mode 100644 index 1d4948c62e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-cursor-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-cursor-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-cursor-2.svg deleted file mode 100644 index 2fddfa485d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-cursor-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-curvy-up-down-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-curvy-up-down-1.svg deleted file mode 100644 index 7df202b6df..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-curvy-up-down-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-curvy-up-down-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-curvy-up-down-2.svg deleted file mode 100644 index 65762b3f51..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-curvy-up-down-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-down-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-down-2.svg deleted file mode 100644 index 1eacf2b68d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-down-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-down-dashed-square.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-down-dashed-square.svg deleted file mode 100644 index 7e3f1a5a40..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-down-dashed-square.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-expand.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-expand.svg deleted file mode 100644 index 6a282421ea..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-expand.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-infinite-loop.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-infinite-loop.svg deleted file mode 100644 index a586e55081..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-infinite-loop.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-move.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-move.svg deleted file mode 100644 index b106268e87..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-move.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-reload-horizontal-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-reload-horizontal-1.svg deleted file mode 100644 index 0b0a93b630..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-reload-horizontal-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-reload-horizontal-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-reload-horizontal-2.svg deleted file mode 100644 index a649467631..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-reload-horizontal-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-reload-vertical-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-reload-vertical-1.svg deleted file mode 100644 index 933f27a9e9..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-reload-vertical-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-reload-vertical-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-reload-vertical-2.svg deleted file mode 100644 index a307381d2c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-reload-vertical-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-roadmap.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-roadmap.svg deleted file mode 100644 index 70870883bb..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-roadmap.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-round-left.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-round-left.svg deleted file mode 100644 index f952023502..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-round-left.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-round-right.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-round-right.svg deleted file mode 100644 index e335b2a94f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-round-right.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-shrink-diagonal-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-shrink-diagonal-1.svg deleted file mode 100644 index 613ce1cabf..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-shrink-diagonal-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-shrink-diagonal-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-shrink-diagonal-2.svg deleted file mode 100644 index 286c959465..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-shrink-diagonal-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-shrink.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-shrink.svg deleted file mode 100644 index 19489b132a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-shrink.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-transfer-diagonal-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-transfer-diagonal-1.svg deleted file mode 100644 index 3e6efceb00..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-transfer-diagonal-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-transfer-diagonal-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-transfer-diagonal-2.svg deleted file mode 100644 index db9160cbfd..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-transfer-diagonal-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-transfer-diagonal-3.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-transfer-diagonal-3.svg deleted file mode 100644 index 63b8361656..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-transfer-diagonal-3.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-up-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-up-1.svg deleted file mode 100644 index 6554aeefb4..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-up-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-up-dashed-square.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-up-dashed-square.svg deleted file mode 100644 index e583df64a3..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/arrow-up-dashed-square.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/ascending-number-order.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/ascending-number-order.svg deleted file mode 100644 index 8b8fe17bb3..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/ascending-number-order.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/attribution.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/attribution.svg deleted file mode 100644 index 9118a362ed..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/attribution.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/blank-calendar.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/blank-calendar.svg deleted file mode 100644 index cdbe62e663..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/blank-calendar.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/blank-notepad.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/blank-notepad.svg deleted file mode 100644 index b1f814264b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/blank-notepad.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/block-bell-notification.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/block-bell-notification.svg deleted file mode 100644 index fd9389548a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/block-bell-notification.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/bomb.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/bomb.svg deleted file mode 100644 index 972746f65e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/bomb.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/bookmark.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/bookmark.svg deleted file mode 100644 index 808752dc47..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/bookmark.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/braces-circle.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/braces-circle.svg deleted file mode 100644 index 9ce91cffd0..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/braces-circle.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/brightness-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/brightness-1.svg deleted file mode 100644 index 8374202149..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/brightness-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/brightness-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/brightness-2.svg deleted file mode 100644 index 343c13113d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/brightness-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/brightness-3.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/brightness-3.svg deleted file mode 100644 index d18adb4fc8..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/brightness-3.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/broken-link-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/broken-link-2.svg deleted file mode 100644 index f2dc320b50..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/broken-link-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/bullet-list.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/bullet-list.svg deleted file mode 100644 index a282a82f70..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/bullet-list.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/calendar-add.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/calendar-add.svg deleted file mode 100644 index 36d0fd2ef4..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/calendar-add.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/calendar-edit.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/calendar-edit.svg deleted file mode 100644 index 2d5296ae1c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/calendar-edit.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/calendar-jump-to-date.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/calendar-jump-to-date.svg deleted file mode 100644 index b9b39c8dbb..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/calendar-jump-to-date.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/calendar-star.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/calendar-star.svg deleted file mode 100644 index 18de81b0bf..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/calendar-star.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/celsius.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/celsius.svg deleted file mode 100644 index 42c694210d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/celsius.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/check-square.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/check-square.svg deleted file mode 100644 index fd78970303..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/check-square.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/check.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/check.svg deleted file mode 100644 index 1a0d205a49..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/check.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/circle-clock.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/circle-clock.svg deleted file mode 100644 index 66fea10946..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/circle-clock.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/circle.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/circle.svg deleted file mode 100644 index fc16218333..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/circle.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/clipboard-add.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/clipboard-add.svg deleted file mode 100644 index ceea0cddff..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/clipboard-add.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/clipboard-check.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/clipboard-check.svg deleted file mode 100644 index 5ac2b85299..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/clipboard-check.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/clipboard-remove.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/clipboard-remove.svg deleted file mode 100644 index db6feb1ee1..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/clipboard-remove.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/cloud.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/cloud.svg deleted file mode 100644 index 22a7dfa2cf..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/cloud.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/cog.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/cog.svg deleted file mode 100644 index f951e92137..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/cog.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/color-palette.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/color-palette.svg deleted file mode 100644 index 05b7489367..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/color-palette.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/color-picker.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/color-picker.svg deleted file mode 100644 index 9fde8baaa2..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/color-picker.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/color-swatches.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/color-swatches.svg deleted file mode 100644 index 5071d67161..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/color-swatches.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/cone-shape.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/cone-shape.svg deleted file mode 100644 index e5623415c6..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/cone-shape.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/convert-PDF-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/convert-PDF-2.svg deleted file mode 100644 index ed7db40584..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/convert-PDF-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/copy-paste.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/copy-paste.svg deleted file mode 100644 index ce0fd6383c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/copy-paste.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/creative-commons.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/creative-commons.svg deleted file mode 100644 index 7a3997e636..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/creative-commons.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/crop-selection.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/crop-selection.svg deleted file mode 100644 index 4c5166cb65..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/crop-selection.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/crown.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/crown.svg deleted file mode 100644 index 951fb68553..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/crown.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/customer-support-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/customer-support-1.svg deleted file mode 100644 index 5593196fbf..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/customer-support-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/cut.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/cut.svg deleted file mode 100644 index 8d63408f39..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/cut.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/dark-dislay-mode.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/dark-dislay-mode.svg deleted file mode 100644 index b5fddc9f7d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/dark-dislay-mode.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/dashboard-3.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/dashboard-3.svg deleted file mode 100644 index 54eb799a01..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/dashboard-3.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/dashboard-circle.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/dashboard-circle.svg deleted file mode 100644 index e60ce62cdf..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/dashboard-circle.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/delete-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/delete-1.svg deleted file mode 100644 index 534ae11cba..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/delete-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/descending-number-order.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/descending-number-order.svg deleted file mode 100644 index 9ce81193f3..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/descending-number-order.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/disable-bell-notification.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/disable-bell-notification.svg deleted file mode 100644 index 2e1a02036a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/disable-bell-notification.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/disable-heart.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/disable-heart.svg deleted file mode 100644 index d3943473ef..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/disable-heart.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/division-circle.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/division-circle.svg deleted file mode 100644 index 2695bb2aaa..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/division-circle.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/download-box-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/download-box-1.svg deleted file mode 100644 index 11bb09caba..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/download-box-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/download-circle.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/download-circle.svg deleted file mode 100644 index bf14c7df8d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/download-circle.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/download-computer.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/download-computer.svg deleted file mode 100644 index d7ea9900f4..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/download-computer.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/download-file.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/download-file.svg deleted file mode 100644 index a298a8eec1..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/download-file.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/empty-clipboard.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/empty-clipboard.svg deleted file mode 100644 index 5ea444ac50..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/empty-clipboard.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/equal-sign.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/equal-sign.svg deleted file mode 100644 index 94fa93fc41..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/equal-sign.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/expand-horizontal-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/expand-horizontal-1.svg deleted file mode 100644 index 108286e4b2..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/expand-horizontal-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/expand-window-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/expand-window-2.svg deleted file mode 100644 index f04b40d461..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/expand-window-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/expand.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/expand.svg deleted file mode 100644 index adad5b6fc5..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/expand.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/face-scan-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/face-scan-1.svg deleted file mode 100644 index 468f7b9d25..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/face-scan-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/factorial.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/factorial.svg deleted file mode 100644 index 127c8e2324..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/factorial.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/fahrenheit.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/fahrenheit.svg deleted file mode 100644 index 3336086ece..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/fahrenheit.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/fastforward-clock.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/fastforward-clock.svg deleted file mode 100644 index c7d02240ea..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/fastforward-clock.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/file-add-alternate.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/file-add-alternate.svg deleted file mode 100644 index 1df2d54768..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/file-add-alternate.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/file-delete-alternate.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/file-delete-alternate.svg deleted file mode 100644 index 1dc099eaa6..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/file-delete-alternate.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/file-remove-alternate.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/file-remove-alternate.svg deleted file mode 100644 index 9c019dea7a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/file-remove-alternate.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/filter-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/filter-2.svg deleted file mode 100644 index b8f72f9e89..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/filter-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/fingerprint-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/fingerprint-1.svg deleted file mode 100644 index 2d481b1916..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/fingerprint-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/fingerprint-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/fingerprint-2.svg deleted file mode 100644 index 8216298e20..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/fingerprint-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/fist.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/fist.svg deleted file mode 100644 index 7f9e043096..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/fist.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/fit-to-height-square.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/fit-to-height-square.svg deleted file mode 100644 index b8976428b2..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/fit-to-height-square.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/flip-vertical-arrow-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/flip-vertical-arrow-2.svg deleted file mode 100644 index 758b31b29a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/flip-vertical-arrow-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/flip-vertical-circle-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/flip-vertical-circle-1.svg deleted file mode 100644 index 1be2ef6ffb..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/flip-vertical-circle-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/flip-vertical-square-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/flip-vertical-square-2.svg deleted file mode 100644 index dfbb30b0c1..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/flip-vertical-square-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/folder-add.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/folder-add.svg deleted file mode 100644 index d21b28a584..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/folder-add.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/folder-check.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/folder-check.svg deleted file mode 100644 index e838527d46..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/folder-check.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/folder-delete.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/folder-delete.svg deleted file mode 100644 index 7f39330a0b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/folder-delete.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/front-camera.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/front-camera.svg deleted file mode 100644 index 1373e61f2d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/front-camera.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/gif-format.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/gif-format.svg deleted file mode 100644 index 432e410130..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/gif-format.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/give-gift.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/give-gift.svg deleted file mode 100644 index 8040687c5e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/give-gift.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/glasses.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/glasses.svg deleted file mode 100644 index d5feb7462d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/glasses.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/half-star-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/half-star-1.svg deleted file mode 100644 index 57e9efaf7c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/half-star-1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/hand-cursor.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/hand-cursor.svg deleted file mode 100644 index 2d09bc7926..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/hand-cursor.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/hand-grab.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/hand-grab.svg deleted file mode 100644 index 24eec4e453..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/hand-grab.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/heading-1-paragraph-styles-heading.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/heading-1-paragraph-styles-heading.svg deleted file mode 100644 index 7bdad9cff9..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/heading-1-paragraph-styles-heading.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/heading-2-paragraph-styles-heading.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/heading-2-paragraph-styles-heading.svg deleted file mode 100644 index 53c948d7b2..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/heading-2-paragraph-styles-heading.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/heading-3-paragraph-styles-heading.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/heading-3-paragraph-styles-heading.svg deleted file mode 100644 index 69d6db3d3d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/heading-3-paragraph-styles-heading.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/heart.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/heart.svg deleted file mode 100644 index e525ec4e3d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/heart.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/help-chat-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/help-chat-2.svg deleted file mode 100644 index ee9b036743..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/help-chat-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/help-question-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/help-question-1.svg deleted file mode 100644 index e709c34077..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/help-question-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/hierarchy-10.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/hierarchy-10.svg deleted file mode 100644 index a39558ce91..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/hierarchy-10.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/hierarchy-13.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/hierarchy-13.svg deleted file mode 100644 index 7a95cd6bb0..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/hierarchy-13.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/hierarchy-14.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/hierarchy-14.svg deleted file mode 100644 index 8d7650b3ef..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/hierarchy-14.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/hierarchy-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/hierarchy-2.svg deleted file mode 100644 index 7308894854..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/hierarchy-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/hierarchy-4.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/hierarchy-4.svg deleted file mode 100644 index 24e31540de..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/hierarchy-4.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/hierarchy-7.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/hierarchy-7.svg deleted file mode 100644 index 3485bea5e0..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/hierarchy-7.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/home-3.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/home-3.svg deleted file mode 100644 index 36d1d77fbb..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/home-3.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/home-4.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/home-4.svg deleted file mode 100644 index c7bc580449..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/home-4.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/horizontal-menu-circle.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/horizontal-menu-circle.svg deleted file mode 100644 index a3091c3357..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/horizontal-menu-circle.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/humidity-none.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/humidity-none.svg deleted file mode 100644 index c81c318039..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/humidity-none.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/image-blur.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/image-blur.svg deleted file mode 100644 index 132f437695..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/image-blur.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/image-saturation.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/image-saturation.svg deleted file mode 100644 index 5bfd1feb04..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/image-saturation.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/information-circle.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/information-circle.svg deleted file mode 100644 index 8ea9d8a04b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/information-circle.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/input-box.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/input-box.svg deleted file mode 100644 index 6369712e83..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/input-box.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/insert-side.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/insert-side.svg deleted file mode 100644 index a8cb471c5b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/insert-side.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/insert-top-left.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/insert-top-left.svg deleted file mode 100644 index 248fa83cb8..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/insert-top-left.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/insert-top-right.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/insert-top-right.svg deleted file mode 100644 index e8729e632b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/insert-top-right.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/invisible-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/invisible-1.svg deleted file mode 100644 index 2faf921e82..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/invisible-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/invisible-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/invisible-2.svg deleted file mode 100644 index df9c4e5e42..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/invisible-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/jump-object.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/jump-object.svg deleted file mode 100644 index 2859e74ef9..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/jump-object.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/key.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/key.svg deleted file mode 100644 index 738976a249..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/key.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/keyhole-lock-circle.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/keyhole-lock-circle.svg deleted file mode 100644 index ef1cd3be8e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/keyhole-lock-circle.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/lasso-tool.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/lasso-tool.svg deleted file mode 100644 index ff0238cdf7..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/lasso-tool.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/layers-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/layers-1.svg deleted file mode 100644 index 8475e73e3b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/layers-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/layers-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/layers-2.svg deleted file mode 100644 index 80ad0566b4..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/layers-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/layout-window-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/layout-window-1.svg deleted file mode 100644 index 111f879265..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/layout-window-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/layout-window-11.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/layout-window-11.svg deleted file mode 100644 index 5ecd8b291b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/layout-window-11.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/layout-window-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/layout-window-2.svg deleted file mode 100644 index 9539f79aca..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/layout-window-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/layout-window-8.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/layout-window-8.svg deleted file mode 100644 index 8ddfa4d969..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/layout-window-8.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/lightbulb.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/lightbulb.svg deleted file mode 100644 index 84f1978687..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/lightbulb.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/like-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/like-1.svg deleted file mode 100644 index ab7b5ac62c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/like-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/link-chain.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/link-chain.svg deleted file mode 100644 index 1e8c9ebf03..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/link-chain.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/live-video.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/live-video.svg deleted file mode 100644 index 74eaac7e5d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/live-video.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/lock-rotation.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/lock-rotation.svg deleted file mode 100644 index 641f61bca4..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/lock-rotation.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/login-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/login-1.svg deleted file mode 100644 index 1bed479e06..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/login-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/logout-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/logout-1.svg deleted file mode 100644 index d5aa2c018b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/logout-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/loop-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/loop-1.svg deleted file mode 100644 index 2ec2b2bd1b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/loop-1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/magic-wand-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/magic-wand-2.svg deleted file mode 100644 index 4ddba53433..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/magic-wand-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/magnifying-glass-circle.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/magnifying-glass-circle.svg deleted file mode 100644 index 7c34859cc8..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/magnifying-glass-circle.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/magnifying-glass.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/magnifying-glass.svg deleted file mode 100644 index d7884201c5..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/magnifying-glass.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/manual-book.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/manual-book.svg deleted file mode 100644 index 2057e661ed..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/manual-book.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/megaphone-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/megaphone-2.svg deleted file mode 100644 index 4f3236db97..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/megaphone-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/minimize-window-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/minimize-window-2.svg deleted file mode 100644 index 0c898ad6b3..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/minimize-window-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/moon-cloud.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/moon-cloud.svg deleted file mode 100644 index 75a3f4d90e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/moon-cloud.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/move-left.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/move-left.svg deleted file mode 100644 index 3d1f5c3b1f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/move-left.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/move-right.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/move-right.svg deleted file mode 100644 index 333693da80..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/move-right.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/multiple-file-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/multiple-file-2.svg deleted file mode 100644 index a65117c01f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/multiple-file-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/music-folder-song.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/music-folder-song.svg deleted file mode 100644 index 13e2814e47..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/music-folder-song.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/new-file.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/new-file.svg deleted file mode 100644 index 0618a6f84e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/new-file.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/new-folder.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/new-folder.svg deleted file mode 100644 index 897ec68112..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/new-folder.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/new-sticky-note.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/new-sticky-note.svg deleted file mode 100644 index 2b9c67b187..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/new-sticky-note.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/not-equal-sign.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/not-equal-sign.svg deleted file mode 100644 index f24755f0d1..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/not-equal-sign.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/ok-hand.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/ok-hand.svg deleted file mode 100644 index 7a101d56e1..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/ok-hand.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/one-finger-drag-horizontal.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/one-finger-drag-horizontal.svg deleted file mode 100644 index 0f20eee768..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/one-finger-drag-horizontal.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/one-finger-drag-vertical.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/one-finger-drag-vertical.svg deleted file mode 100644 index 44d28b3a69..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/one-finger-drag-vertical.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/one-finger-hold.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/one-finger-hold.svg deleted file mode 100644 index 1945cd18fe..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/one-finger-hold.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/one-finger-tap.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/one-finger-tap.svg deleted file mode 100644 index af6d35a12f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/one-finger-tap.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/open-book.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/open-book.svg deleted file mode 100644 index 7d0258d9ae..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/open-book.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/open-umbrella.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/open-umbrella.svg deleted file mode 100644 index 694edaee6e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/open-umbrella.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/padlock-square-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/padlock-square-1.svg deleted file mode 100644 index c15e161f51..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/padlock-square-1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/page-setting.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/page-setting.svg deleted file mode 100644 index 05a50047b7..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/page-setting.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/paint-bucket.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/paint-bucket.svg deleted file mode 100644 index bc0bb97da8..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/paint-bucket.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/paint-palette.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/paint-palette.svg deleted file mode 100644 index 05a7cd2c17..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/paint-palette.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/paintbrush-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/paintbrush-1.svg deleted file mode 100644 index ac1d931e84..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/paintbrush-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/paintbrush-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/paintbrush-2.svg deleted file mode 100644 index ac8cbe43b4..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/paintbrush-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/paperclip-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/paperclip-1.svg deleted file mode 100644 index 2c42341300..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/paperclip-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/paragraph.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/paragraph.svg deleted file mode 100644 index 077a43d46a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/paragraph.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/pathfinder-divide.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/pathfinder-divide.svg deleted file mode 100644 index 11617189b9..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/pathfinder-divide.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/pathfinder-exclude.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/pathfinder-exclude.svg deleted file mode 100644 index 6e791326e0..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/pathfinder-exclude.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/pathfinder-intersect.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/pathfinder-intersect.svg deleted file mode 100644 index 84050733ca..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/pathfinder-intersect.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/pathfinder-merge.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/pathfinder-merge.svg deleted file mode 100644 index 81e6775419..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/pathfinder-merge.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/pathfinder-minus-front-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/pathfinder-minus-front-1.svg deleted file mode 100644 index ee9f455c92..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/pathfinder-minus-front-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/pathfinder-trim.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/pathfinder-trim.svg deleted file mode 100644 index 6a8d72d908..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/pathfinder-trim.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/pathfinder-union.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/pathfinder-union.svg deleted file mode 100644 index 470996d2bb..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/pathfinder-union.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/peace-hand.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/peace-hand.svg deleted file mode 100644 index 6791449f42..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/peace-hand.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/pen-3.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/pen-3.svg deleted file mode 100644 index 651b4a383a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/pen-3.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/pen-draw.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/pen-draw.svg deleted file mode 100644 index 1923942612..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/pen-draw.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/pen-tool.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/pen-tool.svg deleted file mode 100644 index db0e8c253f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/pen-tool.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/pencil.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/pencil.svg deleted file mode 100644 index 95251d86d9..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/pencil.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/pentagon.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/pentagon.svg deleted file mode 100644 index c3a5663ef7..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/pentagon.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/pi-symbol-circle.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/pi-symbol-circle.svg deleted file mode 100644 index 5656f8155a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/pi-symbol-circle.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/pictures-folder-memories.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/pictures-folder-memories.svg deleted file mode 100644 index f7db57e6e7..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/pictures-folder-memories.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/podium.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/podium.svg deleted file mode 100644 index 5914889c53..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/podium.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/polygon.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/polygon.svg deleted file mode 100644 index 93d003ae12..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/polygon.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/praying-hand.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/praying-hand.svg deleted file mode 100644 index 64e54f8d71..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/praying-hand.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/projector-board.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/projector-board.svg deleted file mode 100644 index e79950e656..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/projector-board.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/pyramid-shape.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/pyramid-shape.svg deleted file mode 100644 index a8544363c6..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/pyramid-shape.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/quotation-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/quotation-2.svg deleted file mode 100644 index 941b957351..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/quotation-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/radioactive-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/radioactive-2.svg deleted file mode 100644 index 4bb4a1ad8f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/radioactive-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/rain-cloud.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/rain-cloud.svg deleted file mode 100644 index 0ea0fc0369..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/rain-cloud.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/recycle-bin-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/recycle-bin-2.svg deleted file mode 100644 index 71e8570525..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/recycle-bin-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/ringing-bell-notification.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/ringing-bell-notification.svg deleted file mode 100644 index fc194e471c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/ringing-bell-notification.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/rock-and-roll-hand.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/rock-and-roll-hand.svg deleted file mode 100644 index 12b685624f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/rock-and-roll-hand.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/rotate-angle-45.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/rotate-angle-45.svg deleted file mode 100644 index 697a2d232a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/rotate-angle-45.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/round-cap.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/round-cap.svg deleted file mode 100644 index ca90db1ada..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/round-cap.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/satellite-dish.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/satellite-dish.svg deleted file mode 100644 index 900f43faa2..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/satellite-dish.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/scanner.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/scanner.svg deleted file mode 100644 index a539ba8bcc..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/scanner.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/search-visual.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/search-visual.svg deleted file mode 100644 index 01ae1a1551..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/search-visual.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/select-circle-area-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/select-circle-area-1.svg deleted file mode 100644 index aa4caab372..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/select-circle-area-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/share-link.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/share-link.svg deleted file mode 100644 index b7871bc70d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/share-link.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/shield-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/shield-1.svg deleted file mode 100644 index 55ec4a5498..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/shield-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/shield-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/shield-2.svg deleted file mode 100644 index 95eac8b8d1..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/shield-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/shield-check.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/shield-check.svg deleted file mode 100644 index a50dedecf3..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/shield-check.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/shield-cross.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/shield-cross.svg deleted file mode 100644 index a9d68aeb87..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/shield-cross.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/shrink-horizontal-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/shrink-horizontal-1.svg deleted file mode 100644 index 5211f9f9b0..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/shrink-horizontal-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/shuffle.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/shuffle.svg deleted file mode 100644 index 794fdec2c4..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/shuffle.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/sigma.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/sigma.svg deleted file mode 100644 index c552e8c07d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/sigma.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/skull-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/skull-1.svg deleted file mode 100644 index 44937e48cc..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/skull-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/sleep.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/sleep.svg deleted file mode 100644 index 20d55c012a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/sleep.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/snow-flake.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/snow-flake.svg deleted file mode 100644 index d1bb1f1d45..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/snow-flake.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/sort-descending.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/sort-descending.svg deleted file mode 100644 index 912b92b88c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/sort-descending.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/spiral-shape.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/spiral-shape.svg deleted file mode 100644 index dfd002ef8b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/spiral-shape.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/split-vertical.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/split-vertical.svg deleted file mode 100644 index ce7c2bda52..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/split-vertical.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/spray-paint.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/spray-paint.svg deleted file mode 100644 index 8e18a39c40..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/spray-paint.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/square-brackets-circle.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/square-brackets-circle.svg deleted file mode 100644 index 3b75475809..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/square-brackets-circle.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/square-cap.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/square-cap.svg deleted file mode 100644 index 91c82353fc..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/square-cap.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/square-clock.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/square-clock.svg deleted file mode 100644 index cdcaf984fa..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/square-clock.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/square-root-x-circle.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/square-root-x-circle.svg deleted file mode 100644 index 3b2dc980b8..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/square-root-x-circle.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/star-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/star-1.svg deleted file mode 100644 index 58a5c88759..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/star-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/star-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/star-2.svg deleted file mode 100644 index 660bbbe347..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/star-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/star-badge.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/star-badge.svg deleted file mode 100644 index 78307418c7..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/star-badge.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/straight-cap.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/straight-cap.svg deleted file mode 100644 index a1b64c1675..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/straight-cap.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/subtract-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/subtract-1.svg deleted file mode 100644 index 32300958c5..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/subtract-1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/subtract-circle.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/subtract-circle.svg deleted file mode 100644 index 6bcdece9d1..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/subtract-circle.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/subtract-square.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/subtract-square.svg deleted file mode 100644 index 0384f63da5..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/subtract-square.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/sun-cloud.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/sun-cloud.svg deleted file mode 100644 index 1606b89874..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/sun-cloud.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/synchronize-disable.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/synchronize-disable.svg deleted file mode 100644 index fe5ae9bc25..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/synchronize-disable.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/synchronize-warning.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/synchronize-warning.svg deleted file mode 100644 index 3f773ad3ff..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/synchronize-warning.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/table-lamp-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/table-lamp-1.svg deleted file mode 100644 index a5859f39fc..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/table-lamp-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/tag.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/tag.svg deleted file mode 100644 index b79a4ff92f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/tag.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/text-flow-rows.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/text-flow-rows.svg deleted file mode 100644 index 15977a72f7..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/text-flow-rows.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/text-square.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/text-square.svg deleted file mode 100644 index b297b154de..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/text-square.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/text-style.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/text-style.svg deleted file mode 100644 index 9c5ae09c44..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/text-style.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/thermometer.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/thermometer.svg deleted file mode 100644 index 362672a374..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/thermometer.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/trending-content.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/trending-content.svg deleted file mode 100644 index 42e3618f54..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/trending-content.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/trophy.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/trophy.svg deleted file mode 100644 index d05a23292b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/trophy.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/two-finger-drag-hotizontal.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/two-finger-drag-hotizontal.svg deleted file mode 100644 index fc07dc2fb2..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/two-finger-drag-hotizontal.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/two-finger-tap.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/two-finger-tap.svg deleted file mode 100644 index 638319a091..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/two-finger-tap.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/underline-text-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/underline-text-1.svg deleted file mode 100644 index daa443a4d9..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/underline-text-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/upload-box-1.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/upload-box-1.svg deleted file mode 100644 index 787314f476..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/upload-box-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/upload-circle.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/upload-circle.svg deleted file mode 100644 index 886fa014f3..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/upload-circle.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/upload-computer.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/upload-computer.svg deleted file mode 100644 index 8784850a7e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/upload-computer.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/upload-file.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/upload-file.svg deleted file mode 100644 index c0696194ef..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/upload-file.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/user-add-plus.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/user-add-plus.svg deleted file mode 100644 index a95ceab231..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/user-add-plus.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/user-check-validate.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/user-check-validate.svg deleted file mode 100644 index d9bd0051e6..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/user-check-validate.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/user-circle-single.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/user-circle-single.svg deleted file mode 100644 index a40b12549e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/user-circle-single.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/user-identifier-card.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/user-identifier-card.svg deleted file mode 100644 index bab5c06ac5..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/user-identifier-card.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/user-multiple-circle.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/user-multiple-circle.svg deleted file mode 100644 index 6740649fa4..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/user-multiple-circle.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/user-multiple-group.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/user-multiple-group.svg deleted file mode 100644 index 07c4e2ffd0..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/user-multiple-group.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/user-profile-focus.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/user-profile-focus.svg deleted file mode 100644 index 72f1381092..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/user-profile-focus.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/user-protection-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/user-protection-2.svg deleted file mode 100644 index adcd16e31d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/user-protection-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/user-remove-subtract.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/user-remove-subtract.svg deleted file mode 100644 index 8b84f56060..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/user-remove-subtract.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/user-single-neutral-male.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/user-single-neutral-male.svg deleted file mode 100644 index 7f81c29bfb..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/user-single-neutral-male.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/user-sync-online-in-person.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/user-sync-online-in-person.svg deleted file mode 100644 index 71b68e2f37..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/user-sync-online-in-person.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/vertical-slider-square.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/vertical-slider-square.svg deleted file mode 100644 index 0cf86e26e0..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/vertical-slider-square.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/video-swap-camera.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/video-swap-camera.svg deleted file mode 100644 index f7cdad5918..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/video-swap-camera.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/visible.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/visible.svg deleted file mode 100644 index 343ffa0ca9..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/visible.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/voice-scan-2.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/voice-scan-2.svg deleted file mode 100644 index 8a083f13ad..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/voice-scan-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/waning-cresent-moon.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/waning-cresent-moon.svg deleted file mode 100644 index 91dde28366..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/waning-cresent-moon.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/warning-octagon.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/warning-octagon.svg deleted file mode 100644 index 52cc420522..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/warning-octagon.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/interface_essential/warning-triangle.svg b/frontend/appflowy_web_app/public/af_icons/interface_essential/warning-triangle.svg deleted file mode 100644 index 5f205c4c95..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/interface_essential/warning-triangle.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/chat-bubble-oval-notification.svg b/frontend/appflowy_web_app/public/af_icons/mail/chat-bubble-oval-notification.svg deleted file mode 100644 index 320ab16d19..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/chat-bubble-oval-notification.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/chat-bubble-oval-smiley-1.svg b/frontend/appflowy_web_app/public/af_icons/mail/chat-bubble-oval-smiley-1.svg deleted file mode 100644 index b79fed59f7..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/chat-bubble-oval-smiley-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/chat-bubble-oval-smiley-2.svg b/frontend/appflowy_web_app/public/af_icons/mail/chat-bubble-oval-smiley-2.svg deleted file mode 100644 index 6f20c07292..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/chat-bubble-oval-smiley-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/chat-bubble-oval.svg b/frontend/appflowy_web_app/public/af_icons/mail/chat-bubble-oval.svg deleted file mode 100644 index 74bfb2b1f1..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/chat-bubble-oval.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/chat-bubble-square-block.svg b/frontend/appflowy_web_app/public/af_icons/mail/chat-bubble-square-block.svg deleted file mode 100644 index 81e9132b68..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/chat-bubble-square-block.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/chat-bubble-square-question.svg b/frontend/appflowy_web_app/public/af_icons/mail/chat-bubble-square-question.svg deleted file mode 100644 index b09ee92a09..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/chat-bubble-square-question.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/chat-bubble-square-warning.svg b/frontend/appflowy_web_app/public/af_icons/mail/chat-bubble-square-warning.svg deleted file mode 100644 index e784e77e10..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/chat-bubble-square-warning.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/chat-bubble-square-write.svg b/frontend/appflowy_web_app/public/af_icons/mail/chat-bubble-square-write.svg deleted file mode 100644 index 9dc5174dca..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/chat-bubble-square-write.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/chat-bubble-text-square.svg b/frontend/appflowy_web_app/public/af_icons/mail/chat-bubble-text-square.svg deleted file mode 100644 index 60f7c3032b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/chat-bubble-text-square.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/chat-bubble-typing-oval.svg b/frontend/appflowy_web_app/public/af_icons/mail/chat-bubble-typing-oval.svg deleted file mode 100644 index 05f9dc722d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/chat-bubble-typing-oval.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/chat-two-bubbles-oval.svg b/frontend/appflowy_web_app/public/af_icons/mail/chat-two-bubbles-oval.svg deleted file mode 100644 index 068e2de39e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/chat-two-bubbles-oval.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/discussion-converstion-reply.svg b/frontend/appflowy_web_app/public/af_icons/mail/discussion-converstion-reply.svg deleted file mode 100644 index 4d63be9a85..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/discussion-converstion-reply.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/happy-face.svg b/frontend/appflowy_web_app/public/af_icons/mail/happy-face.svg deleted file mode 100644 index 1f5f581da6..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/happy-face.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/inbox-block.svg b/frontend/appflowy_web_app/public/af_icons/mail/inbox-block.svg deleted file mode 100644 index 251a31897f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/inbox-block.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/inbox-favorite-heart.svg b/frontend/appflowy_web_app/public/af_icons/mail/inbox-favorite-heart.svg deleted file mode 100644 index 68d266fe49..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/inbox-favorite-heart.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/inbox-favorite.svg b/frontend/appflowy_web_app/public/af_icons/mail/inbox-favorite.svg deleted file mode 100644 index 9a27890605..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/inbox-favorite.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/inbox-lock.svg b/frontend/appflowy_web_app/public/af_icons/mail/inbox-lock.svg deleted file mode 100644 index 721163917d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/inbox-lock.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/inbox-tray-1.svg b/frontend/appflowy_web_app/public/af_icons/mail/inbox-tray-1.svg deleted file mode 100644 index 25cf8d1ace..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/inbox-tray-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/inbox-tray-2.svg b/frontend/appflowy_web_app/public/af_icons/mail/inbox-tray-2.svg deleted file mode 100644 index 2d2b6afa4c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/inbox-tray-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/mail-incoming.svg b/frontend/appflowy_web_app/public/af_icons/mail/mail-incoming.svg deleted file mode 100644 index e3cebdbffa..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/mail-incoming.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/mail-search.svg b/frontend/appflowy_web_app/public/af_icons/mail/mail-search.svg deleted file mode 100644 index 280d7cb363..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/mail-search.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/mail-send-email-message.svg b/frontend/appflowy_web_app/public/af_icons/mail/mail-send-email-message.svg deleted file mode 100644 index 5a01764607..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/mail-send-email-message.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/mail-send-envelope.svg b/frontend/appflowy_web_app/public/af_icons/mail/mail-send-envelope.svg deleted file mode 100644 index ee32a0d2d5..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/mail-send-envelope.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/mail-send-reply-all.svg b/frontend/appflowy_web_app/public/af_icons/mail/mail-send-reply-all.svg deleted file mode 100644 index 5a4bbd13ae..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/mail-send-reply-all.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/sad-face.svg b/frontend/appflowy_web_app/public/af_icons/mail/sad-face.svg deleted file mode 100644 index cb07b814ff..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/sad-face.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/send-email.svg b/frontend/appflowy_web_app/public/af_icons/mail/send-email.svg deleted file mode 100644 index 0431ab66eb..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/send-email.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/sign-at.svg b/frontend/appflowy_web_app/public/af_icons/mail/sign-at.svg deleted file mode 100644 index 764b2bf312..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/sign-at.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/sign-hashtag.svg b/frontend/appflowy_web_app/public/af_icons/mail/sign-hashtag.svg deleted file mode 100644 index 545e661007..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/sign-hashtag.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/smiley-angry.svg b/frontend/appflowy_web_app/public/af_icons/mail/smiley-angry.svg deleted file mode 100644 index 3e9ad9ee33..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/smiley-angry.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/smiley-cool.svg b/frontend/appflowy_web_app/public/af_icons/mail/smiley-cool.svg deleted file mode 100644 index 71d44d8279..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/smiley-cool.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/smiley-crying-1.svg b/frontend/appflowy_web_app/public/af_icons/mail/smiley-crying-1.svg deleted file mode 100644 index c5cfe2df8d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/smiley-crying-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/smiley-cute.svg b/frontend/appflowy_web_app/public/af_icons/mail/smiley-cute.svg deleted file mode 100644 index 918f29f705..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/smiley-cute.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/smiley-drool.svg b/frontend/appflowy_web_app/public/af_icons/mail/smiley-drool.svg deleted file mode 100644 index acc73cee7c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/smiley-drool.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/smiley-emoji-kiss-nervous.svg b/frontend/appflowy_web_app/public/af_icons/mail/smiley-emoji-kiss-nervous.svg deleted file mode 100644 index 2e8ce83c87..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/smiley-emoji-kiss-nervous.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/smiley-emoji-terrified.svg b/frontend/appflowy_web_app/public/af_icons/mail/smiley-emoji-terrified.svg deleted file mode 100644 index 2ee952b07c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/smiley-emoji-terrified.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/smiley-grumpy.svg b/frontend/appflowy_web_app/public/af_icons/mail/smiley-grumpy.svg deleted file mode 100644 index e0f6d4c939..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/smiley-grumpy.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/smiley-happy.svg b/frontend/appflowy_web_app/public/af_icons/mail/smiley-happy.svg deleted file mode 100644 index 1849e6fc5f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/smiley-happy.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/smiley-in-love.svg b/frontend/appflowy_web_app/public/af_icons/mail/smiley-in-love.svg deleted file mode 100644 index cb3f446338..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/smiley-in-love.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/smiley-kiss.svg b/frontend/appflowy_web_app/public/af_icons/mail/smiley-kiss.svg deleted file mode 100644 index f86db7c10c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/smiley-kiss.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/mail/smiley-laughing-3.svg b/frontend/appflowy_web_app/public/af_icons/mail/smiley-laughing-3.svg deleted file mode 100644 index df01420baa..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/mail/smiley-laughing-3.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/airplane.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/airplane.svg deleted file mode 100644 index 85b9018e5b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/airplane.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/airport-plane-transit.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/airport-plane-transit.svg deleted file mode 100644 index 723a23d913..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/airport-plane-transit.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/airport-plane.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/airport-plane.svg deleted file mode 100644 index 6731fd8992..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/airport-plane.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/airport-security.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/airport-security.svg deleted file mode 100644 index 30d2c370a6..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/airport-security.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/anchor.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/anchor.svg deleted file mode 100644 index 8b05191e02..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/anchor.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/baggage.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/baggage.svg deleted file mode 100644 index 674be5b254..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/baggage.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/beach.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/beach.svg deleted file mode 100644 index 8e38eaea77..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/beach.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/bicycle-bike.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/bicycle-bike.svg deleted file mode 100644 index 0f01d9cdd1..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/bicycle-bike.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/braille-blind.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/braille-blind.svg deleted file mode 100644 index 8c8f531003..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/braille-blind.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/bus.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/bus.svg deleted file mode 100644 index 2bf0c8ab84..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/bus.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/camping-tent.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/camping-tent.svg deleted file mode 100644 index 46d9e7fcc7..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/camping-tent.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/cane.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/cane.svg deleted file mode 100644 index 6778b91182..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/cane.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/capitol.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/capitol.svg deleted file mode 100644 index c9f7106687..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/capitol.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/car-battery-charging.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/car-battery-charging.svg deleted file mode 100644 index 610323ea42..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/car-battery-charging.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/car-taxi-1.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/car-taxi-1.svg deleted file mode 100644 index 156ee2113a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/car-taxi-1.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/city-hall.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/city-hall.svg deleted file mode 100644 index 379f9a974a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/city-hall.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/compass-navigator.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/compass-navigator.svg deleted file mode 100644 index 63ead58975..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/compass-navigator.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/crutch.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/crutch.svg deleted file mode 100644 index 6f46d47b87..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/crutch.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/dangerous-zone-sign.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/dangerous-zone-sign.svg deleted file mode 100644 index 675fbfb386..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/dangerous-zone-sign.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/earth-1.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/earth-1.svg deleted file mode 100644 index b38deea54b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/earth-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/earth-airplane.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/earth-airplane.svg deleted file mode 100644 index 44103e0c82..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/earth-airplane.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/emergency-exit.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/emergency-exit.svg deleted file mode 100644 index 047192a9f1..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/emergency-exit.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/fire-alarm-2.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/fire-alarm-2.svg deleted file mode 100644 index 17cd4f4304..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/fire-alarm-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/fire-extinguisher-sign.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/fire-extinguisher-sign.svg deleted file mode 100644 index 54da68657a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/fire-extinguisher-sign.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/gas-station-fuel-petroleum.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/gas-station-fuel-petroleum.svg deleted file mode 100644 index 3fb3f024a7..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/gas-station-fuel-petroleum.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/hearing-deaf-1.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/hearing-deaf-1.svg deleted file mode 100644 index 5abe2f60a0..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/hearing-deaf-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/hearing-deaf-2.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/hearing-deaf-2.svg deleted file mode 100644 index 7623a86ae7..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/hearing-deaf-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/high-speed-train-front.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/high-speed-train-front.svg deleted file mode 100644 index 0334e072ee..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/high-speed-train-front.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/hot-spring.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/hot-spring.svg deleted file mode 100644 index 3f72df09e3..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/hot-spring.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/hotel-air-conditioner.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/hotel-air-conditioner.svg deleted file mode 100644 index b8006f8ab8..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/hotel-air-conditioner.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/hotel-bed-2.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/hotel-bed-2.svg deleted file mode 100644 index 2af7f57ce1..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/hotel-bed-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/hotel-laundry.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/hotel-laundry.svg deleted file mode 100644 index 5a7291b7da..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/hotel-laundry.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/hotel-one-star.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/hotel-one-star.svg deleted file mode 100644 index c7d69e2d42..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/hotel-one-star.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/hotel-shower-head.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/hotel-shower-head.svg deleted file mode 100644 index 5e3cf1de40..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/hotel-shower-head.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/hotel-two-star.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/hotel-two-star.svg deleted file mode 100644 index c1ae54056c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/hotel-two-star.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/information-desk-customer.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/information-desk-customer.svg deleted file mode 100644 index 7add1d0610..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/information-desk-customer.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/information-desk.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/information-desk.svg deleted file mode 100644 index d13cba2fc5..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/information-desk.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/iron.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/iron.svg deleted file mode 100644 index 641099f09a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/iron.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/ladder.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/ladder.svg deleted file mode 100644 index 9963129445..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/ladder.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/lift-disability.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/lift-disability.svg deleted file mode 100644 index 588edf9c83..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/lift-disability.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/lift.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/lift.svg deleted file mode 100644 index aafccb263e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/lift.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/location-compass-1.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/location-compass-1.svg deleted file mode 100644 index 00647fbe0f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/location-compass-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/location-pin-3.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/location-pin-3.svg deleted file mode 100644 index bc88a620b8..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/location-pin-3.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/location-pin-disabled.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/location-pin-disabled.svg deleted file mode 100644 index 2ff5e3cbd3..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/location-pin-disabled.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/location-target-1.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/location-target-1.svg deleted file mode 100644 index 9c015e2f47..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/location-target-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/lost-and-found.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/lost-and-found.svg deleted file mode 100644 index 047a764906..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/lost-and-found.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/man-symbol.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/man-symbol.svg deleted file mode 100644 index 0f2a3dcef7..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/man-symbol.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/map-fold.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/map-fold.svg deleted file mode 100644 index 5f059cb98b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/map-fold.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/navigation-arrow-off.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/navigation-arrow-off.svg deleted file mode 100644 index d40bbffe6f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/navigation-arrow-off.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/navigation-arrow-on.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/navigation-arrow-on.svg deleted file mode 100644 index ca83a1f7b6..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/navigation-arrow-on.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/parking-sign.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/parking-sign.svg deleted file mode 100644 index 3708cca823..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/parking-sign.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/parliament.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/parliament.svg deleted file mode 100644 index aae1a5f440..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/parliament.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/passport.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/passport.svg deleted file mode 100644 index 848f049fbc..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/passport.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/pet-paw.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/pet-paw.svg deleted file mode 100644 index 27cf85c318..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/pet-paw.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/pets-allowed.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/pets-allowed.svg deleted file mode 100644 index 48bd43e11a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/pets-allowed.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/pool-ladder.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/pool-ladder.svg deleted file mode 100644 index dabe71440e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/pool-ladder.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/rock-slide.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/rock-slide.svg deleted file mode 100644 index 0a6a5a711d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/rock-slide.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/sail-ship.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/sail-ship.svg deleted file mode 100644 index 982767a3c5..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/sail-ship.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/school-bus-side.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/school-bus-side.svg deleted file mode 100644 index 7b5856e7ff..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/school-bus-side.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/smoke-detector.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/smoke-detector.svg deleted file mode 100644 index 89862439b9..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/smoke-detector.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/smoking-area.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/smoking-area.svg deleted file mode 100644 index 4e39d94df2..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/smoking-area.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/snorkle.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/snorkle.svg deleted file mode 100644 index 626e2cf484..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/snorkle.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/steering-wheel.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/steering-wheel.svg deleted file mode 100644 index 2b3adeb5bb..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/steering-wheel.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/street-road.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/street-road.svg deleted file mode 100644 index 5ff4e3c03c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/street-road.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/street-sign.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/street-sign.svg deleted file mode 100644 index d5f80c30a8..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/street-sign.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/take-off.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/take-off.svg deleted file mode 100644 index 7d5aafc2c3..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/take-off.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/toilet-man.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/toilet-man.svg deleted file mode 100644 index 67df34ada5..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/toilet-man.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/toilet-sign-man-woman-2.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/toilet-sign-man-woman-2.svg deleted file mode 100644 index 44f3680273..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/toilet-sign-man-woman-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/toilet-women.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/toilet-women.svg deleted file mode 100644 index 916f4591c7..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/toilet-women.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/traffic-cone.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/traffic-cone.svg deleted file mode 100644 index 1cd16d0c2f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/traffic-cone.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/triangle-flag.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/triangle-flag.svg deleted file mode 100644 index a6f86c16b3..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/triangle-flag.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/wheelchair-1.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/wheelchair-1.svg deleted file mode 100644 index a0d539594c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/wheelchair-1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/map_travel/woman-symbol.svg b/frontend/appflowy_web_app/public/af_icons/map_travel/woman-symbol.svg deleted file mode 100644 index a9c4377ec1..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/map_travel/woman-symbol.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/annoncement-megaphone.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/annoncement-megaphone.svg deleted file mode 100644 index 472f766f0e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/annoncement-megaphone.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/backpack.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/backpack.svg deleted file mode 100644 index d9800f6d76..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/backpack.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/bag-dollar.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/bag-dollar.svg deleted file mode 100644 index 176e8d38de..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/bag-dollar.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/bag-pound.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/bag-pound.svg deleted file mode 100644 index a282df85fb..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/bag-pound.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/bag-rupee.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/bag-rupee.svg deleted file mode 100644 index eb5c02be60..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/bag-rupee.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/bag-suitcase-1.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/bag-suitcase-1.svg deleted file mode 100644 index 82a1e7ff31..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/bag-suitcase-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/bag-suitcase-2.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/bag-suitcase-2.svg deleted file mode 100644 index 706aab5df3..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/bag-suitcase-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/bag-yen.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/bag-yen.svg deleted file mode 100644 index 2a54180e78..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/bag-yen.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/bag.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/bag.svg deleted file mode 100644 index d1abe11443..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/bag.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/ball.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/ball.svg deleted file mode 100644 index 6f7e278919..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/ball.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/bank.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/bank.svg deleted file mode 100644 index fd6cf58939..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/bank.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/beanie.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/beanie.svg deleted file mode 100644 index 374557ac4f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/beanie.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/bill-1.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/bill-1.svg deleted file mode 100644 index 6df0d554df..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/bill-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/bill-2.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/bill-2.svg deleted file mode 100644 index 61f55ac5d5..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/bill-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/bill-4.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/bill-4.svg deleted file mode 100644 index 2ffbf22977..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/bill-4.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/bill-cashless.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/bill-cashless.svg deleted file mode 100644 index aa77bf8d67..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/bill-cashless.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/binance-circle.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/binance-circle.svg deleted file mode 100644 index 6b489a693e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/binance-circle.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/bitcoin.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/bitcoin.svg deleted file mode 100644 index 211aee40c6..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/bitcoin.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/bow-tie.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/bow-tie.svg deleted file mode 100644 index 2f2bbdaa55..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/bow-tie.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/briefcase-dollar.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/briefcase-dollar.svg deleted file mode 100644 index 14c68c87c4..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/briefcase-dollar.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/building-2.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/building-2.svg deleted file mode 100644 index 56c3585f37..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/building-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/business-card.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/business-card.svg deleted file mode 100644 index f980629bce..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/business-card.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/business-handshake.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/business-handshake.svg deleted file mode 100644 index 62ce5e55c4..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/business-handshake.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/business-idea-money.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/business-idea-money.svg deleted file mode 100644 index d4eb07175f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/business-idea-money.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/business-profession-home-office.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/business-profession-home-office.svg deleted file mode 100644 index f634c96f5f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/business-profession-home-office.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/business-progress-bar-2.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/business-progress-bar-2.svg deleted file mode 100644 index b73b5e9015..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/business-progress-bar-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/business-user-curriculum.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/business-user-curriculum.svg deleted file mode 100644 index 19714b52b1..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/business-user-curriculum.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/calculator-1.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/calculator-1.svg deleted file mode 100644 index d14d7b0050..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/calculator-1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/calculator-2.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/calculator-2.svg deleted file mode 100644 index bf48853eaa..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/calculator-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/cane.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/cane.svg deleted file mode 100644 index 4c7c27073c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/cane.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/chair.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/chair.svg deleted file mode 100644 index a6d33f000f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/chair.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/closet.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/closet.svg deleted file mode 100644 index 16991a60e9..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/closet.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/coin-share.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/coin-share.svg deleted file mode 100644 index 9aa28f013f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/coin-share.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/coins-stack.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/coins-stack.svg deleted file mode 100644 index fceb62950c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/coins-stack.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/credit-card-1.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/credit-card-1.svg deleted file mode 100644 index e730a703d5..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/credit-card-1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/credit-card-2.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/credit-card-2.svg deleted file mode 100644 index f6ba16a213..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/credit-card-2.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/diamond-2.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/diamond-2.svg deleted file mode 100644 index e226caaf38..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/diamond-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/discount-percent-badge.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/discount-percent-badge.svg deleted file mode 100644 index 5cda678b87..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/discount-percent-badge.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/discount-percent-circle.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/discount-percent-circle.svg deleted file mode 100644 index 6fa8b79900..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/discount-percent-circle.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/discount-percent-coupon.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/discount-percent-coupon.svg deleted file mode 100644 index 9ff5dac1f4..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/discount-percent-coupon.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/discount-percent-cutout.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/discount-percent-cutout.svg deleted file mode 100644 index 82a7587a6e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/discount-percent-cutout.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/discount-percent-fire.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/discount-percent-fire.svg deleted file mode 100644 index 00a78332c4..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/discount-percent-fire.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/dollar-coin-1.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/dollar-coin-1.svg deleted file mode 100644 index 0db0836aca..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/dollar-coin-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/dollar-coin.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/dollar-coin.svg deleted file mode 100644 index b285d8d4b2..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/dollar-coin.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/dressing-table.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/dressing-table.svg deleted file mode 100644 index 90e453e2b7..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/dressing-table.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/ethereum-circle.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/ethereum-circle.svg deleted file mode 100644 index 06db44f70d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/ethereum-circle.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/ethereum.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/ethereum.svg deleted file mode 100644 index 40f205e9d7..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/ethereum.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/euro.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/euro.svg deleted file mode 100644 index 73121ce282..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/euro.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/gift-2.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/gift-2.svg deleted file mode 100644 index e64b42428e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/gift-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/gift.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/gift.svg deleted file mode 100644 index ba78839102..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/gift.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/gold.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/gold.svg deleted file mode 100644 index 33ef845ad2..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/gold.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/graph-arrow-decrease.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/graph-arrow-decrease.svg deleted file mode 100644 index 1177b1acfb..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/graph-arrow-decrease.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/graph-arrow-increase.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/graph-arrow-increase.svg deleted file mode 100644 index 82415d32c6..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/graph-arrow-increase.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/graph-bar-decrease.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/graph-bar-decrease.svg deleted file mode 100644 index 79f523f8d9..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/graph-bar-decrease.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/graph-bar-increase.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/graph-bar-increase.svg deleted file mode 100644 index 801daa256b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/graph-bar-increase.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/graph-dot.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/graph-dot.svg deleted file mode 100644 index 7ca8308d52..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/graph-dot.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/graph.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/graph.svg deleted file mode 100644 index 4c692f6ca0..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/graph.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/investment-selection.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/investment-selection.svg deleted file mode 100644 index 478b852ad1..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/investment-selection.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/justice-hammer.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/justice-hammer.svg deleted file mode 100644 index d9bf64217e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/justice-hammer.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/justice-scale-1.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/justice-scale-1.svg deleted file mode 100644 index 6e531dd1b8..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/justice-scale-1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/justice-scale-2.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/justice-scale-2.svg deleted file mode 100644 index e90aa2594b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/justice-scale-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/lipstick.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/lipstick.svg deleted file mode 100644 index e2ffa968c6..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/lipstick.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/make-up-brush.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/make-up-brush.svg deleted file mode 100644 index d5e28774ae..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/make-up-brush.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/moustache.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/moustache.svg deleted file mode 100644 index 049e343f18..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/moustache.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/mouth-lip.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/mouth-lip.svg deleted file mode 100644 index 22cb09ed17..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/mouth-lip.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/necklace.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/necklace.svg deleted file mode 100644 index 7e615d5fc1..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/necklace.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/necktie.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/necktie.svg deleted file mode 100644 index dd502d68ec..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/necktie.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/payment-10.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/payment-10.svg deleted file mode 100644 index 42935055e5..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/payment-10.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/payment-cash-out-3.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/payment-cash-out-3.svg deleted file mode 100644 index 8f451cb9f8..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/payment-cash-out-3.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/pie-chart.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/pie-chart.svg deleted file mode 100644 index 2966a8ef8f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/pie-chart.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/piggy-bank.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/piggy-bank.svg deleted file mode 100644 index ef796838b2..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/piggy-bank.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/polka-dot-circle.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/polka-dot-circle.svg deleted file mode 100644 index f50f1edc76..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/polka-dot-circle.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/production-belt.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/production-belt.svg deleted file mode 100644 index b56f95f19f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/production-belt.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/qr-code.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/qr-code.svg deleted file mode 100644 index c93d63f060..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/qr-code.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/receipt-add.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/receipt-add.svg deleted file mode 100644 index 50cc1f57da..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/receipt-add.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/receipt-check.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/receipt-check.svg deleted file mode 100644 index 99762d138f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/receipt-check.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/receipt-subtract.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/receipt-subtract.svg deleted file mode 100644 index 9a18ed6d06..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/receipt-subtract.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/receipt.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/receipt.svg deleted file mode 100644 index da4dab6340..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/receipt.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/safe-vault.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/safe-vault.svg deleted file mode 100644 index 9c3d9d7230..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/safe-vault.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/scanner-3.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/scanner-3.svg deleted file mode 100644 index f7db012503..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/scanner-3.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/scanner-bar-code.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/scanner-bar-code.svg deleted file mode 100644 index b60571a669..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/scanner-bar-code.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/shelf.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/shelf.svg deleted file mode 100644 index 3ea11815bf..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/shelf.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/shopping-bag-hand-bag-2.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/shopping-bag-hand-bag-2.svg deleted file mode 100644 index ae8ef2d315..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/shopping-bag-hand-bag-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/shopping-basket-1.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/shopping-basket-1.svg deleted file mode 100644 index b33b5304fb..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/shopping-basket-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/shopping-basket-2.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/shopping-basket-2.svg deleted file mode 100644 index f2610aab4c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/shopping-basket-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/shopping-cart-1.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/shopping-cart-1.svg deleted file mode 100644 index 74529f1ae5..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/shopping-cart-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/shopping-cart-2.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/shopping-cart-2.svg deleted file mode 100644 index eecaeadf06..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/shopping-cart-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/shopping-cart-3.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/shopping-cart-3.svg deleted file mode 100644 index 681b1653e9..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/shopping-cart-3.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/shopping-cart-add.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/shopping-cart-add.svg deleted file mode 100644 index 50d05a70b7..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/shopping-cart-add.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/shopping-cart-check.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/shopping-cart-check.svg deleted file mode 100644 index 6d7b9fb635..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/shopping-cart-check.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/shopping-cart-subtract.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/shopping-cart-subtract.svg deleted file mode 100644 index 41c96d3ec9..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/shopping-cart-subtract.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/signage-3.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/signage-3.svg deleted file mode 100644 index 92c321b4b5..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/signage-3.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/signage-4.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/signage-4.svg deleted file mode 100644 index 42b4f8c6a9..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/signage-4.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/startup.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/startup.svg deleted file mode 100644 index 77ed266e67..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/startup.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/stock.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/stock.svg deleted file mode 100644 index d350449bcf..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/stock.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/store-1.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/store-1.svg deleted file mode 100644 index 53751414d5..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/store-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/store-2.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/store-2.svg deleted file mode 100644 index 7e2312aa57..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/store-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/store-computer.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/store-computer.svg deleted file mode 100644 index 4ea808bf31..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/store-computer.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/subscription-cashflow.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/subscription-cashflow.svg deleted file mode 100644 index c7f45631ac..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/subscription-cashflow.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/tag.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/tag.svg deleted file mode 100644 index 3aae6ba5c3..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/tag.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/tall-hat.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/tall-hat.svg deleted file mode 100644 index 1f5650785c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/tall-hat.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/target-3.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/target-3.svg deleted file mode 100644 index 35591b9238..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/target-3.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/target.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/target.svg deleted file mode 100644 index 632d7c4c3c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/target.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/wallet-purse.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/wallet-purse.svg deleted file mode 100644 index 6df7533528..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/wallet-purse.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/wallet.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/wallet.svg deleted file mode 100644 index 39042c9f04..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/wallet.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/xrp-circle.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/xrp-circle.svg deleted file mode 100644 index 4250e70555..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/xrp-circle.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/yuan-circle.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/yuan-circle.svg deleted file mode 100644 index 138056c639..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/yuan-circle.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/money_shopping/yuan.svg b/frontend/appflowy_web_app/public/af_icons/money_shopping/yuan.svg deleted file mode 100644 index 65976287fe..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/money_shopping/yuan.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/affordable-and-clean-energy.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/affordable-and-clean-energy.svg deleted file mode 100644 index 70421c5d33..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/affordable-and-clean-energy.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/alien.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/alien.svg deleted file mode 100644 index 5cfaf813bf..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/alien.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/bone.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/bone.svg deleted file mode 100644 index 669f8e9755..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/bone.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/cat-1.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/cat-1.svg deleted file mode 100644 index d23a378b47..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/cat-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/circle-flask.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/circle-flask.svg deleted file mode 100644 index fc75c75d0d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/circle-flask.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/clean-water-and-sanitation.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/clean-water-and-sanitation.svg deleted file mode 100644 index c7b4738cee..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/clean-water-and-sanitation.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/comet.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/comet.svg deleted file mode 100644 index 3d0012a1fc..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/comet.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/decent-work-and-economic-growth.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/decent-work-and-economic-growth.svg deleted file mode 100644 index 3aa52629d7..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/decent-work-and-economic-growth.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/dna.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/dna.svg deleted file mode 100644 index dda09e350c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/dna.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/erlenmeyer-flask.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/erlenmeyer-flask.svg deleted file mode 100644 index 14e078c476..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/erlenmeyer-flask.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/flower.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/flower.svg deleted file mode 100644 index 675f917956..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/flower.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/galaxy-1.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/galaxy-1.svg deleted file mode 100644 index 59d4638f8c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/galaxy-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/galaxy-2.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/galaxy-2.svg deleted file mode 100644 index d8948d5f35..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/galaxy-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/gender-equality.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/gender-equality.svg deleted file mode 100644 index 3f0a9cdecc..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/gender-equality.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/good-health-and-well-being.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/good-health-and-well-being.svg deleted file mode 100644 index c92374cff5..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/good-health-and-well-being.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/industry-innovation-and-infrastructure.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/industry-innovation-and-infrastructure.svg deleted file mode 100644 index 7a58eecbca..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/industry-innovation-and-infrastructure.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/leaf.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/leaf.svg deleted file mode 100644 index 2a53ca6fd5..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/leaf.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/log.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/log.svg deleted file mode 100644 index 1a3deaa880..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/log.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/no-poverty.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/no-poverty.svg deleted file mode 100644 index f12c0b4111..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/no-poverty.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/octopus.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/octopus.svg deleted file mode 100644 index 38855cbe54..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/octopus.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/planet.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/planet.svg deleted file mode 100644 index 9ee346f584..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/planet.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/potted-flower-tulip.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/potted-flower-tulip.svg deleted file mode 100644 index 2be0dc0cb5..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/potted-flower-tulip.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/quality-education.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/quality-education.svg deleted file mode 100644 index c6e0335e9b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/quality-education.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/rainbow.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/rainbow.svg deleted file mode 100644 index 8c99192cee..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/rainbow.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/recycle-1.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/recycle-1.svg deleted file mode 100644 index 30ebcf8fb2..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/recycle-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/reduced-inequalities.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/reduced-inequalities.svg deleted file mode 100644 index cc3de7a365..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/reduced-inequalities.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/rose.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/rose.svg deleted file mode 100644 index 3459894b5b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/rose.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/shell.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/shell.svg deleted file mode 100644 index bde5159a0a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/shell.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/shovel-rake.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/shovel-rake.svg deleted file mode 100644 index 832d2a4f1d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/shovel-rake.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/sprout.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/sprout.svg deleted file mode 100644 index 3b8ea11ba3..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/sprout.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/telescope.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/telescope.svg deleted file mode 100644 index 9a29c5483d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/telescope.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/test-tube.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/test-tube.svg deleted file mode 100644 index 01d12af810..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/test-tube.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/tidal-wave.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/tidal-wave.svg deleted file mode 100644 index 56c80ec200..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/tidal-wave.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/tree-2.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/tree-2.svg deleted file mode 100644 index da32de6697..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/tree-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/tree-3.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/tree-3.svg deleted file mode 100644 index db8bfe987b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/tree-3.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/volcano.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/volcano.svg deleted file mode 100644 index 989f5e68b9..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/volcano.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/windmill.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/windmill.svg deleted file mode 100644 index edfb759883..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/windmill.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/nature_ecology/zero-hunger.svg b/frontend/appflowy_web_app/public/af_icons/nature_ecology/zero-hunger.svg deleted file mode 100644 index 7cd7b52a34..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/nature_ecology/zero-hunger.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/phone/airplane-disabled.svg b/frontend/appflowy_web_app/public/af_icons/phone/airplane-disabled.svg deleted file mode 100644 index dd6bf8d91a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/phone/airplane-disabled.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/phone/airplane-enabled.svg b/frontend/appflowy_web_app/public/af_icons/phone/airplane-enabled.svg deleted file mode 100644 index af74878ae4..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/phone/airplane-enabled.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/phone/back-camera-1.svg b/frontend/appflowy_web_app/public/af_icons/phone/back-camera-1.svg deleted file mode 100644 index 9c38dd55e2..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/phone/back-camera-1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/phone/call-hang-up.svg b/frontend/appflowy_web_app/public/af_icons/phone/call-hang-up.svg deleted file mode 100644 index 41d0886a0e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/phone/call-hang-up.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/phone/cellular-network-4g.svg b/frontend/appflowy_web_app/public/af_icons/phone/cellular-network-4g.svg deleted file mode 100644 index 559284654e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/phone/cellular-network-4g.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/phone/cellular-network-5g.svg b/frontend/appflowy_web_app/public/af_icons/phone/cellular-network-5g.svg deleted file mode 100644 index a4b9a8626f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/phone/cellular-network-5g.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/phone/cellular-network-lte.svg b/frontend/appflowy_web_app/public/af_icons/phone/cellular-network-lte.svg deleted file mode 100644 index 373d8e8640..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/phone/cellular-network-lte.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/phone/contact-phonebook-2.svg b/frontend/appflowy_web_app/public/af_icons/phone/contact-phonebook-2.svg deleted file mode 100644 index 8968279a29..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/phone/contact-phonebook-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/phone/hang-up-1.svg b/frontend/appflowy_web_app/public/af_icons/phone/hang-up-1.svg deleted file mode 100644 index fb4c797028..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/phone/hang-up-1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/phone/hang-up-2.svg b/frontend/appflowy_web_app/public/af_icons/phone/hang-up-2.svg deleted file mode 100644 index dbd92cb2a3..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/phone/hang-up-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/phone/incoming-call.svg b/frontend/appflowy_web_app/public/af_icons/phone/incoming-call.svg deleted file mode 100644 index dfb4e8a02e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/phone/incoming-call.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/phone/missed-call.svg b/frontend/appflowy_web_app/public/af_icons/phone/missed-call.svg deleted file mode 100644 index 9efc8a2eb9..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/phone/missed-call.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/phone/notification-alarm-2.svg b/frontend/appflowy_web_app/public/af_icons/phone/notification-alarm-2.svg deleted file mode 100644 index 7eec376825..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/phone/notification-alarm-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/phone/notification-application-1.svg b/frontend/appflowy_web_app/public/af_icons/phone/notification-application-1.svg deleted file mode 100644 index 4908995f02..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/phone/notification-application-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/phone/notification-application-2.svg b/frontend/appflowy_web_app/public/af_icons/phone/notification-application-2.svg deleted file mode 100644 index 32db7b5be0..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/phone/notification-application-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/phone/notification-message-alert.svg b/frontend/appflowy_web_app/public/af_icons/phone/notification-message-alert.svg deleted file mode 100644 index 7d1123fbee..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/phone/notification-message-alert.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/phone/outgoing-call.svg b/frontend/appflowy_web_app/public/af_icons/phone/outgoing-call.svg deleted file mode 100644 index 70ea90ca9e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/phone/outgoing-call.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/phone/phone-mobile-phone.svg b/frontend/appflowy_web_app/public/af_icons/phone/phone-mobile-phone.svg deleted file mode 100644 index 299a57b66c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/phone/phone-mobile-phone.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/phone/phone-qr.svg b/frontend/appflowy_web_app/public/af_icons/phone/phone-qr.svg deleted file mode 100644 index f572d984d6..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/phone/phone-qr.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/phone/phone-ringing-1.svg b/frontend/appflowy_web_app/public/af_icons/phone/phone-ringing-1.svg deleted file mode 100644 index 1b13856b30..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/phone/phone-ringing-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/phone/phone-ringing-2.svg b/frontend/appflowy_web_app/public/af_icons/phone/phone-ringing-2.svg deleted file mode 100644 index 889998fa68..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/phone/phone-ringing-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/phone/phone.svg b/frontend/appflowy_web_app/public/af_icons/phone/phone.svg deleted file mode 100644 index fc6ab0ad9c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/phone/phone.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/phone/signal-full.svg b/frontend/appflowy_web_app/public/af_icons/phone/signal-full.svg deleted file mode 100644 index 58823e23ea..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/phone/signal-full.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/phone/signal-low.svg b/frontend/appflowy_web_app/public/af_icons/phone/signal-low.svg deleted file mode 100644 index e96c4af8a2..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/phone/signal-low.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/phone/signal-medium.svg b/frontend/appflowy_web_app/public/af_icons/phone/signal-medium.svg deleted file mode 100644 index 1af4305dd0..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/phone/signal-medium.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/phone/signal-none.svg b/frontend/appflowy_web_app/public/af_icons/phone/signal-none.svg deleted file mode 100644 index a4eb9031e2..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/phone/signal-none.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/application-add.svg b/frontend/appflowy_web_app/public/af_icons/programing/application-add.svg deleted file mode 100644 index 3206d90bff..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/application-add.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/bracket.svg b/frontend/appflowy_web_app/public/af_icons/programing/bracket.svg deleted file mode 100644 index 19ec5e423d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/bracket.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/browser-add.svg b/frontend/appflowy_web_app/public/af_icons/programing/browser-add.svg deleted file mode 100644 index f202217042..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/browser-add.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/browser-block.svg b/frontend/appflowy_web_app/public/af_icons/programing/browser-block.svg deleted file mode 100644 index 6eb1b7be24..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/browser-block.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/browser-build.svg b/frontend/appflowy_web_app/public/af_icons/programing/browser-build.svg deleted file mode 100644 index 2fb2366c85..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/browser-build.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/browser-check.svg b/frontend/appflowy_web_app/public/af_icons/programing/browser-check.svg deleted file mode 100644 index 198d2aad19..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/browser-check.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/browser-delete.svg b/frontend/appflowy_web_app/public/af_icons/programing/browser-delete.svg deleted file mode 100644 index f6dc39ae72..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/browser-delete.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/browser-hash.svg b/frontend/appflowy_web_app/public/af_icons/programing/browser-hash.svg deleted file mode 100644 index 92664d39a7..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/browser-hash.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/browser-lock.svg b/frontend/appflowy_web_app/public/af_icons/programing/browser-lock.svg deleted file mode 100644 index fec83df52f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/browser-lock.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/browser-multiple-window.svg b/frontend/appflowy_web_app/public/af_icons/programing/browser-multiple-window.svg deleted file mode 100644 index 7c8b3043cd..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/browser-multiple-window.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/browser-remove.svg b/frontend/appflowy_web_app/public/af_icons/programing/browser-remove.svg deleted file mode 100644 index eb5a5b1741..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/browser-remove.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/browser-website-1.svg b/frontend/appflowy_web_app/public/af_icons/programing/browser-website-1.svg deleted file mode 100644 index a49315daca..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/browser-website-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/bug-antivirus-debugging.svg b/frontend/appflowy_web_app/public/af_icons/programing/bug-antivirus-debugging.svg deleted file mode 100644 index be5811df0f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/bug-antivirus-debugging.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/bug-antivirus-shield.svg b/frontend/appflowy_web_app/public/af_icons/programing/bug-antivirus-shield.svg deleted file mode 100644 index 84af3bb9cc..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/bug-antivirus-shield.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/bug-virus-browser.svg b/frontend/appflowy_web_app/public/af_icons/programing/bug-virus-browser.svg deleted file mode 100644 index 4f42f54326..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/bug-virus-browser.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/bug-virus-document.svg b/frontend/appflowy_web_app/public/af_icons/programing/bug-virus-document.svg deleted file mode 100644 index a7c24e2ce6..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/bug-virus-document.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/bug-virus-folder.svg b/frontend/appflowy_web_app/public/af_icons/programing/bug-virus-folder.svg deleted file mode 100644 index eeea1baf39..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/bug-virus-folder.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/bug.svg b/frontend/appflowy_web_app/public/af_icons/programing/bug.svg deleted file mode 100644 index a2d2795e45..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/bug.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/cloud-add.svg b/frontend/appflowy_web_app/public/af_icons/programing/cloud-add.svg deleted file mode 100644 index 00a46324d0..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/cloud-add.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/cloud-block.svg b/frontend/appflowy_web_app/public/af_icons/programing/cloud-block.svg deleted file mode 100644 index b321169629..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/cloud-block.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/cloud-check.svg b/frontend/appflowy_web_app/public/af_icons/programing/cloud-check.svg deleted file mode 100644 index a56b3fbbff..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/cloud-check.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/cloud-data-transfer.svg b/frontend/appflowy_web_app/public/af_icons/programing/cloud-data-transfer.svg deleted file mode 100644 index 74d319d7f1..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/cloud-data-transfer.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/cloud-refresh.svg b/frontend/appflowy_web_app/public/af_icons/programing/cloud-refresh.svg deleted file mode 100644 index 9096ac8796..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/cloud-refresh.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/cloud-share.svg b/frontend/appflowy_web_app/public/af_icons/programing/cloud-share.svg deleted file mode 100644 index e69919e88c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/cloud-share.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/cloud-warning.svg b/frontend/appflowy_web_app/public/af_icons/programing/cloud-warning.svg deleted file mode 100644 index ebd5917d01..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/cloud-warning.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/cloud-wifi.svg b/frontend/appflowy_web_app/public/af_icons/programing/cloud-wifi.svg deleted file mode 100644 index 51072ad76f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/cloud-wifi.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/code-analysis.svg b/frontend/appflowy_web_app/public/af_icons/programing/code-analysis.svg deleted file mode 100644 index ef9dd46dbd..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/code-analysis.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/code-monitor-1.svg b/frontend/appflowy_web_app/public/af_icons/programing/code-monitor-1.svg deleted file mode 100644 index 34c51a7ab2..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/code-monitor-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/code-monitor-2.svg b/frontend/appflowy_web_app/public/af_icons/programing/code-monitor-2.svg deleted file mode 100644 index 5719d9699f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/code-monitor-2.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/css-three.svg b/frontend/appflowy_web_app/public/af_icons/programing/css-three.svg deleted file mode 100644 index f20ce41833..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/css-three.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/curly-brackets.svg b/frontend/appflowy_web_app/public/af_icons/programing/curly-brackets.svg deleted file mode 100644 index 6b6eb7d645..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/curly-brackets.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/file-code-1.svg b/frontend/appflowy_web_app/public/af_icons/programing/file-code-1.svg deleted file mode 100644 index 7e12e3847b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/file-code-1.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/incognito-mode.svg b/frontend/appflowy_web_app/public/af_icons/programing/incognito-mode.svg deleted file mode 100644 index 84197023a4..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/incognito-mode.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/insert-cloud-video.svg b/frontend/appflowy_web_app/public/af_icons/programing/insert-cloud-video.svg deleted file mode 100644 index 4f883df73f..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/insert-cloud-video.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/markdown-circle-programming.svg b/frontend/appflowy_web_app/public/af_icons/programing/markdown-circle-programming.svg deleted file mode 100644 index 7c03684a41..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/markdown-circle-programming.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/markdown-document-programming.svg b/frontend/appflowy_web_app/public/af_icons/programing/markdown-document-programming.svg deleted file mode 100644 index 514d68e7f8..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/markdown-document-programming.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/module-puzzle-1.svg b/frontend/appflowy_web_app/public/af_icons/programing/module-puzzle-1.svg deleted file mode 100644 index c285965ff6..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/module-puzzle-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/module-puzzle-3.svg b/frontend/appflowy_web_app/public/af_icons/programing/module-puzzle-3.svg deleted file mode 100644 index 60c754e08d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/module-puzzle-3.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/module-three.svg b/frontend/appflowy_web_app/public/af_icons/programing/module-three.svg deleted file mode 100644 index d517266362..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/module-three.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/programing/rss-square.svg b/frontend/appflowy_web_app/public/af_icons/programing/rss-square.svg deleted file mode 100644 index d84eeaabd1..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/programing/rss-square.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/shipping/box-sign.svg b/frontend/appflowy_web_app/public/af_icons/shipping/box-sign.svg deleted file mode 100644 index 4edefc3583..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/shipping/box-sign.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/shipping/container.svg b/frontend/appflowy_web_app/public/af_icons/shipping/container.svg deleted file mode 100644 index 6b85af7c6c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/shipping/container.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/shipping/fragile.svg b/frontend/appflowy_web_app/public/af_icons/shipping/fragile.svg deleted file mode 100644 index 0c69404528..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/shipping/fragile.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/shipping/parachute-drop.svg b/frontend/appflowy_web_app/public/af_icons/shipping/parachute-drop.svg deleted file mode 100644 index b4a08a3417..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/shipping/parachute-drop.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/shipping/shipment-add.svg b/frontend/appflowy_web_app/public/af_icons/shipping/shipment-add.svg deleted file mode 100644 index 96060a0e5c..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/shipping/shipment-add.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/shipping/shipment-check.svg b/frontend/appflowy_web_app/public/af_icons/shipping/shipment-check.svg deleted file mode 100644 index 80046da591..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/shipping/shipment-check.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/shipping/shipment-download.svg b/frontend/appflowy_web_app/public/af_icons/shipping/shipment-download.svg deleted file mode 100644 index aff16bb56a..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/shipping/shipment-download.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/shipping/shipment-remove.svg b/frontend/appflowy_web_app/public/af_icons/shipping/shipment-remove.svg deleted file mode 100644 index cb2759d0c7..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/shipping/shipment-remove.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/shipping/shipment-upload.svg b/frontend/appflowy_web_app/public/af_icons/shipping/shipment-upload.svg deleted file mode 100644 index aa76150c54..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/shipping/shipment-upload.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/shipping/shipping-box-1.svg b/frontend/appflowy_web_app/public/af_icons/shipping/shipping-box-1.svg deleted file mode 100644 index 42a5129d1e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/shipping/shipping-box-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/shipping/shipping-truck.svg b/frontend/appflowy_web_app/public/af_icons/shipping/shipping-truck.svg deleted file mode 100644 index 6835708103..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/shipping/shipping-truck.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/shipping/transfer-motorcycle.svg b/frontend/appflowy_web_app/public/af_icons/shipping/transfer-motorcycle.svg deleted file mode 100644 index 2ab10a3db4..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/shipping/transfer-motorcycle.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/shipping/transfer-van.svg b/frontend/appflowy_web_app/public/af_icons/shipping/transfer-van.svg deleted file mode 100644 index 8c4a0cebc2..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/shipping/transfer-van.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/shipping/warehouse-1.svg b/frontend/appflowy_web_app/public/af_icons/shipping/warehouse-1.svg deleted file mode 100644 index 57e18260f0..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/shipping/warehouse-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/work_education/book-reading.svg b/frontend/appflowy_web_app/public/af_icons/work_education/book-reading.svg deleted file mode 100644 index c792c95b11..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/work_education/book-reading.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/work_education/class-lesson.svg b/frontend/appflowy_web_app/public/af_icons/work_education/class-lesson.svg deleted file mode 100644 index ea2bef46a0..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/work_education/class-lesson.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/work_education/collaborations-idea.svg b/frontend/appflowy_web_app/public/af_icons/work_education/collaborations-idea.svg deleted file mode 100644 index 633d1811a6..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/work_education/collaborations-idea.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/work_education/definition-search-book.svg b/frontend/appflowy_web_app/public/af_icons/work_education/definition-search-book.svg deleted file mode 100644 index 14039daefb..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/work_education/definition-search-book.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/work_education/dictionary-language-book.svg b/frontend/appflowy_web_app/public/af_icons/work_education/dictionary-language-book.svg deleted file mode 100644 index 60591372bb..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/work_education/dictionary-language-book.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/work_education/global-learning.svg b/frontend/appflowy_web_app/public/af_icons/work_education/global-learning.svg deleted file mode 100644 index bbcea6ce49..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/work_education/global-learning.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/work_education/graduation-cap.svg b/frontend/appflowy_web_app/public/af_icons/work_education/graduation-cap.svg deleted file mode 100644 index 10dcd1e834..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/work_education/graduation-cap.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/work_education/group-meeting-call.svg b/frontend/appflowy_web_app/public/af_icons/work_education/group-meeting-call.svg deleted file mode 100644 index 786fad824d..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/work_education/group-meeting-call.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/work_education/office-building-1.svg b/frontend/appflowy_web_app/public/af_icons/work_education/office-building-1.svg deleted file mode 100644 index cba001bb4e..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/work_education/office-building-1.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/work_education/office-worker.svg b/frontend/appflowy_web_app/public/af_icons/work_education/office-worker.svg deleted file mode 100644 index 42e2938530..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/work_education/office-worker.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/work_education/search-dollar.svg b/frontend/appflowy_web_app/public/af_icons/work_education/search-dollar.svg deleted file mode 100644 index acb98adab2..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/work_education/search-dollar.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/af_icons/work_education/strategy-tasks.svg b/frontend/appflowy_web_app/public/af_icons/work_education/strategy-tasks.svg deleted file mode 100644 index 3938e55804..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/work_education/strategy-tasks.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/work_education/task-list.svg b/frontend/appflowy_web_app/public/af_icons/work_education/task-list.svg deleted file mode 100644 index 24f3080a8b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/work_education/task-list.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/appflowy_web_app/public/af_icons/work_education/workspace-desk.svg b/frontend/appflowy_web_app/public/af_icons/work_education/workspace-desk.svg deleted file mode 100644 index 9c2e287d7b..0000000000 --- a/frontend/appflowy_web_app/public/af_icons/work_education/workspace-desk.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/appflowy.ico b/frontend/appflowy_web_app/public/appflowy.ico deleted file mode 100644 index 42570b2d50..0000000000 Binary files a/frontend/appflowy_web_app/public/appflowy.ico and /dev/null differ diff --git a/frontend/appflowy_web_app/public/appflowy.svg b/frontend/appflowy_web_app/public/appflowy.svg deleted file mode 100644 index e8ad422794..0000000000 --- a/frontend/appflowy_web_app/public/appflowy.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/frontend/appflowy_web_app/public/covers/m_cover_image_1.png b/frontend/appflowy_web_app/public/covers/m_cover_image_1.png deleted file mode 100644 index fb72022287..0000000000 Binary files a/frontend/appflowy_web_app/public/covers/m_cover_image_1.png and /dev/null differ diff --git a/frontend/appflowy_web_app/public/covers/m_cover_image_2.png b/frontend/appflowy_web_app/public/covers/m_cover_image_2.png deleted file mode 100644 index 9ecf02d253..0000000000 Binary files a/frontend/appflowy_web_app/public/covers/m_cover_image_2.png and /dev/null differ diff --git a/frontend/appflowy_web_app/public/covers/m_cover_image_3.png b/frontend/appflowy_web_app/public/covers/m_cover_image_3.png deleted file mode 100644 index 97072b04f4..0000000000 Binary files a/frontend/appflowy_web_app/public/covers/m_cover_image_3.png and /dev/null differ diff --git a/frontend/appflowy_web_app/public/covers/m_cover_image_4.png b/frontend/appflowy_web_app/public/covers/m_cover_image_4.png deleted file mode 100644 index 00d26a0500..0000000000 Binary files a/frontend/appflowy_web_app/public/covers/m_cover_image_4.png and /dev/null differ diff --git a/frontend/appflowy_web_app/public/covers/m_cover_image_5.png b/frontend/appflowy_web_app/public/covers/m_cover_image_5.png deleted file mode 100644 index 3ecc9546c1..0000000000 Binary files a/frontend/appflowy_web_app/public/covers/m_cover_image_5.png and /dev/null differ diff --git a/frontend/appflowy_web_app/public/covers/m_cover_image_6.png b/frontend/appflowy_web_app/public/covers/m_cover_image_6.png deleted file mode 100644 index 0abd2700e8..0000000000 Binary files a/frontend/appflowy_web_app/public/covers/m_cover_image_6.png and /dev/null differ diff --git a/frontend/appflowy_web_app/public/og-image.png b/frontend/appflowy_web_app/public/og-image.png deleted file mode 100644 index a43f64a46e..0000000000 Binary files a/frontend/appflowy_web_app/public/og-image.png and /dev/null differ diff --git a/frontend/appflowy_web_app/scripts/create-symlink.cjs b/frontend/appflowy_web_app/scripts/create-symlink.cjs deleted file mode 100644 index 472f511f27..0000000000 --- a/frontend/appflowy_web_app/scripts/create-symlink.cjs +++ /dev/null @@ -1,43 +0,0 @@ -const fs = require('fs'); -const path = require('path'); -const chalk = require('chalk'); - -const sourcePath = process.argv[2]; -const targetPath = process.argv[3]; - -// ensure source and target paths are provided -if (!sourcePath || !targetPath) { - console.error(chalk.red('source and target paths are required')); - process.exit(1); -} - -const fullSourcePath = path.resolve(sourcePath); -const fullTargetPath = path.resolve(targetPath); -// ensure source path exists -if (!fs.existsSync(fullSourcePath)) { - console.error(chalk.red(`source path does not exist: ${fullSourcePath}`)); - process.exit(1); -} - -// ensure target path exists -if (!fs.existsSync(fullTargetPath)) { - console.error(chalk.red(`target path does not exist: ${fullTargetPath}`)); - process.exit(1); -} - - -if (fs.existsSync(fullTargetPath)) { - // unlink existing symlink - console.log(chalk.yellow(`unlinking existing symlink: `) + chalk.blue(`${fullTargetPath}`)); - fs.unlinkSync(fullTargetPath); -} - -// create symlink -fs.symlink(fullSourcePath, fullTargetPath, 'junction', (err) => { - if (err) { - console.error(chalk.red(`error creating symlink: ${err.message}`)); - process.exit(1); - } - console.log(chalk.green(`symlink created: `) + chalk.blue(`${fullSourcePath}`) + ' -> ' + chalk.blue(`${fullTargetPath}`)); - -}); diff --git a/frontend/appflowy_web_app/scripts/generateTailwindColors.cjs b/frontend/appflowy_web_app/scripts/generateTailwindColors.cjs deleted file mode 100644 index 83f5bb25d5..0000000000 --- a/frontend/appflowy_web_app/scripts/generateTailwindColors.cjs +++ /dev/null @@ -1,61 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -// Read CSS file -const cssFilePath = path.join(__dirname, '../src/styles/variables/light.variables.css'); -const cssContent = fs.readFileSync(cssFilePath, 'utf-8'); - -// Extract color variables -const shadowVariables = cssContent.match(/--shadow:\s.*;/g); -const colorVariables = cssContent.match(/--[\w-]+:\s*#[0-9a-fA-F]{6}/g); - -if (!colorVariables) { - console.error('No color variables found in CSS file.'); - process.exit(1); -} - -const shadows = shadowVariables.reduce((shadows, variable) => { - const [name, value] = variable.split(':').map(str => str.trim()); - const formattedName = name.replace('--', '').replace(/-/g, '_'); - const key = 'md'; - - shadows[key] = `var(${name})`; - return shadows; -}, {}); -// Generate Tailwind CSS colors configuration -// Replace -- with _ and - with _ in color variable names -const tailwindColors = colorVariables.reduce((colors, variable) => { - const [name, value] = variable.split(':').map(str => str.trim()); - const formattedName = name.replace('--', '').replace(/-/g, '_'); - const category = formattedName.split('_')[0]; - const key = formattedName.replace(`${category}_`, ''); - - if (!colors[category]) { - colors[category] = {}; - } - colors[category][key] = `var(${name})`; - return colors; -}, {}); - -const tailwindColorsFormatted = JSON.stringify(tailwindColors, null, 2) - .replace(/_/g, '-'); -const header = `/**\n` + '* Do not edit directly\n' + `* Generated on ${new Date().toUTCString()}\n` + `* Generated from $pnpm css:variables \n` + `*/\n\n`; - -// Write Tailwind CSS colors configuration to file -const tailwindColorTemplate = ` -${header} -module.exports = ${tailwindColorsFormatted}; -`; - -const tailwindShadowTemplate = ` -${header} -module.exports = ${JSON.stringify(shadows, null, 2).replace(/_/g, '-')}; -`; - -const tailwindConfigFilePath = path.join(__dirname, '../tailwind/colors.cjs'); -fs.writeFileSync(tailwindConfigFilePath, tailwindColorTemplate, 'utf-8'); - -const tailwindShadowFilePath = path.join(__dirname, '../tailwind/box-shadow.cjs'); -fs.writeFileSync(tailwindShadowFilePath, tailwindShadowTemplate, 'utf-8'); - -console.log('Tailwind CSS colors configuration generated successfully.'); diff --git a/frontend/appflowy_web_app/scripts/generate_af_icons.cjs b/frontend/appflowy_web_app/scripts/generate_af_icons.cjs deleted file mode 100644 index 9763beba56..0000000000 --- a/frontend/appflowy_web_app/scripts/generate_af_icons.cjs +++ /dev/null @@ -1,64 +0,0 @@ -const fs = require('fs'); -const path = require('path'); - -const getIconsDir = () => path.resolve(__dirname, '../public/af_icons'); - -const readSvgFile = (filePath) => { - return fs.readFileSync(filePath, 'utf8'); -}; - -const renameSvgFile = (filePath, newName) => { - const newPath = path.join(path.dirname(filePath), newName); - fs.renameSync(filePath, newPath); -}; - -const processSvgFiles = (dirPath) => { - const categories = {}; - - const traverseDir = (currentPath) => { - const items = fs.readdirSync(currentPath); - - items.forEach((item) => { - const itemPath = path.join(currentPath, item); - const stat = fs.statSync(itemPath); - - if (stat.isDirectory()) { - traverseDir(itemPath); - } else if (stat.isFile() && path.extname(item) === '.svg') { - const category = path.basename(currentPath); - const [namePart, ...keywordParts] = path.basename(item, '.svg').split('--'); - const name = namePart; - const keywords = keywordParts.length > 0 ? keywordParts[0].split('-') : []; - const svgContent = readSvgFile(itemPath); - renameSvgFile(itemPath, `${name}.svg`); - if (!categories[category]) { - categories[category] = []; - } - - categories[category].push({ - id: `${category}/${name}`, - name, - keywords, - content: svgContent, - }); - } - }); - }; - - traverseDir(dirPath); - return categories; -}; - -const outputJson = (data, outputFilePath) => { - fs.writeFileSync(outputFilePath, JSON.stringify(data, null, 2)); -}; - -const main = () => { - const iconsDirPath = getIconsDir(); - const categories = processSvgFiles(iconsDirPath); - const outputFilePath = path.join(iconsDirPath, 'icons.json'); - outputJson(categories, outputFilePath); - console.log(`JSON data has been written to ${outputFilePath}`); -}; - -main(); diff --git a/frontend/appflowy_web_app/scripts/i18n.cjs b/frontend/appflowy_web_app/scripts/i18n.cjs deleted file mode 100644 index 407a03694a..0000000000 --- a/frontend/appflowy_web_app/scripts/i18n.cjs +++ /dev/null @@ -1,63 +0,0 @@ -const languages = [ - 'ar-SA', - 'ca-ES', - 'de-DE', - 'en', - 'es-VE', - 'eu-ES', - 'fr-FR', - 'hu-HU', - 'id-ID', - 'it-IT', - 'ja-JP', - 'ko-KR', - 'pl-PL', - 'pt-BR', - 'pt-PT', - 'ru-RU', - 'sv-SE', - 'th-TH', - 'tr-TR', - 'zh-CN', - 'zh-TW', -]; - -const fs = require('fs'); -languages.forEach(language => { - const json = require(`../../resources/translations/${language}.json`); - const outputJSON = flattenJSON(json); - const output = JSON.stringify(outputJSON); - const isExistDir = fs.existsSync('./src/@types/translations'); - if (!isExistDir) { - fs.mkdirSync('./src/@types/translations'); - } - fs.writeFile(`./src/@types/translations/${language}.json`, new Uint8Array(Buffer.from(output)), (res) => { - if (res) { - console.error(res); - } - }) -}); - - -function flattenJSON(obj, prefix = '') { - let result = {}; - const pluralsKey = ["one", "other", "few", "many", "two", "zero"]; - - for (let key in obj) { - if (typeof obj[key] === 'object' && obj[key] !== null) { - - const nestedKeys = flattenJSON(obj[key], `${prefix}${key}.`); - result = { ...result, ...nestedKeys }; - } else { - let newKey = `${prefix}${key}`; - let replaceChar = '{' - if (pluralsKey.includes(key)) { - newKey = `${prefix.slice(0, -1)}_${key}`; - } - result[newKey] = obj[key].replaceAll('{', '{{').replaceAll('}', '}}'); - } - } - - return result; -} - diff --git a/frontend/appflowy_web_app/scripts/merge-coverage.cjs b/frontend/appflowy_web_app/scripts/merge-coverage.cjs deleted file mode 100644 index 1939ca4ef9..0000000000 --- a/frontend/appflowy_web_app/scripts/merge-coverage.cjs +++ /dev/null @@ -1,35 +0,0 @@ -const { execSync } = require('child_process'); -const fs = require('fs'); -const path = require('path'); -const jestCoverageFile = path.join(__dirname, '../coverage/jest/coverage-final.json'); -const cypressCoverageFile = path.join(__dirname, '../coverage/cypress/coverage-final.json'); -const nycOutputDir = path.join(__dirname, '../coverage/.nyc_output'); - -// Ensure .nyc_output directory exists -if (fs.existsSync(nycOutputDir)) { - fs.rmSync(nycOutputDir, { recursive: true }); -} -fs.mkdirSync(nycOutputDir, { recursive: true }); - -if (fs.existsSync(path.join(__dirname, '../coverage/merged'))) { - fs.rmSync(path.join(__dirname, '../coverage/merged'), { recursive: true }); -} -// Copy Jest coverage file -fs.copyFileSync(jestCoverageFile, path.join(nycOutputDir, 'jest-coverage.json')); -// Copy Cypress E2E coverage file -fs.copyFileSync(cypressCoverageFile, path.join(nycOutputDir, 'cypress-coverage.json')); - -// Merge coverage files -execSync('nyc merge ./coverage/.nyc_output ./coverage/merged/coverage-final.json', { stdio: 'inherit' }); - -// Move the merged result to the .nyc_output directory -fs.rmSync(nycOutputDir, { recursive: true }); -fs.mkdirSync(nycOutputDir, { recursive: true }); -fs.copyFileSync(path.join(__dirname, '../coverage/merged/coverage-final.json'), path.join(nycOutputDir, 'out.json')); - -// Generate final merged report -execSync('nyc report --reporter=html --reporter=text-summary --report-dir=coverage/merged --temp-dir=coverage/.nyc_output', { stdio: 'inherit' }); -console.log(`Merged coverage report written to coverage/merged`); - - - diff --git a/frontend/appflowy_web_app/src-tauri/.gitignore b/frontend/appflowy_web_app/src-tauri/.gitignore deleted file mode 100644 index 9e4914893d..0000000000 --- a/frontend/appflowy_web_app/src-tauri/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -# Generated by Cargo -# will have compiled files and executables -/target/ -.env \ No newline at end of file diff --git a/frontend/appflowy_web_app/src-tauri/Cargo.lock b/frontend/appflowy_web_app/src-tauri/Cargo.lock deleted file mode 100644 index ea71138635..0000000000 --- a/frontend/appflowy_web_app/src-tauri/Cargo.lock +++ /dev/null @@ -1,9469 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "accessory" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "850bb534b9dc04744fbbb71d30ad6d25a7e4cf6dc33e223c81ef3a92ebab4e0b" -dependencies = [ - "macroific", - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "addr2line" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common", - "generic-array", -] - -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "aes-gcm" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", - "subtle", -] - -[[package]] -name = "again" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05802a5ad4d172eaf796f7047b42d0af9db513585d16d4169660a21613d34b93" -dependencies = [ - "log", - "rand 0.7.3", - "wasm-timer", -] - -[[package]] -name = "ahash" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" -dependencies = [ - "getrandom 0.2.12", - "once_cell", - "version_check", -] - -[[package]] -name = "ahash" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "getrandom 0.2.12", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "allo-isolate" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b6d794345b06592d0ebeed8e477e41b71e5a0a49df4fc0e4184d5938b99509" -dependencies = [ - "atomic", - "pin-project", -] - -[[package]] -name = "alloc-no-stdlib" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" -dependencies = [ - "alloc-no-stdlib", -] - -[[package]] -name = "allocator-api2" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anyhow" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" - -[[package]] -name = "app-error" -version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ea131f0baab67defe7591067357eced490072372#ea131f0baab67defe7591067357eced490072372" -dependencies = [ - "anyhow", - "bincode", - "getrandom 0.2.12", - "reqwest", - "serde", - "serde_json", - "serde_repr", - "thiserror", - "tokio", - "tsify", - "url", - "uuid", - "wasm-bindgen", -] - -[[package]] -name = "appflowy-ai-client" -version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ea131f0baab67defe7591067357eced490072372#ea131f0baab67defe7591067357eced490072372" -dependencies = [ - "anyhow", - "bytes", - "futures", - "pin-project", - "serde", - "serde_json", - "serde_repr", - "thiserror", -] - -[[package]] -name = "appflowy-local-ai" -version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=6f064efe232268f8d396edbb4b84d57fbb640f13#6f064efe232268f8d396edbb4b84d57fbb640f13" -dependencies = [ - "anyhow", - "appflowy-plugin", - "bytes", - "reqwest", - "serde", - "serde_json", - "tokio", - "tokio-stream", - "tokio-util", - "tracing", - "zip 2.2.0", - "zip-extensions", -] - -[[package]] -name = "appflowy-plugin" -version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-LocalAI?rev=6f064efe232268f8d396edbb4b84d57fbb640f13#6f064efe232268f8d396edbb4b84d57fbb640f13" -dependencies = [ - "anyhow", - "cfg-if", - "crossbeam-utils", - "log", - "once_cell", - "parking_lot 0.12.1", - "serde", - "serde_json", - "thiserror", - "tokio", - "tokio-stream", - "tracing", - "xattr", -] - -[[package]] -name = "appflowy_tauri" -version = "0.0.0" -dependencies = [ - "bytes", - "dotenv", - "flowy-ai", - "flowy-config", - "flowy-core", - "flowy-date", - "flowy-document", - "flowy-error", - "flowy-notification", - "flowy-user", - "lib-dispatch", - "semver", - "serde", - "serde_json", - "tauri", - "tauri-build", - "tauri-plugin-deep-link", - "tauri-utils", - "tracing", - "uuid", -] - -[[package]] -name = "arbitrary" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" -dependencies = [ - "derive_arbitrary", -] - -[[package]] -name = "arboard" -version = "3.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2041f1943049c7978768d84e6d0fd95de98b76d6c4727b09e78ec253d29fa58" -dependencies = [ - "clipboard-win", - "core-graphics 0.23.1", - "image", - "log", - "objc", - "objc-foundation", - "objc_id", - "parking_lot 0.12.1", - "thiserror", - "windows-sys 0.48.0", - "wl-clipboard-rs", - "x11rb", -] - -[[package]] -name = "arc-swap" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" - -[[package]] -name = "arrayvec" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" - -[[package]] -name = "async-compression" -version = "0.4.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "103db485efc3e41214fe4fda9f3dbeae2eb9082f48fd236e6095627a9422066e" -dependencies = [ - "bzip2", - "deflate64", - "flate2", - "futures-core", - "futures-io", - "memchr", - "pin-project-lite", - "xz2", - "zstd 0.13.2", - "zstd-safe 7.2.0", -] - -[[package]] -name = "async-lock" -version = "3.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" -dependencies = [ - "event-listener", - "event-listener-strategy", - "pin-project-lite", -] - -[[package]] -name = "async-recursion" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "async-stream" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "async-trait" -version = "0.1.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "async_zip" -version = "0.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b9f7252833d5ed4b00aa9604b563529dd5e11de9c23615de2dcdf91eb87b52" -dependencies = [ - "async-compression", - "chrono", - "crc32fast", - "futures-lite", - "pin-project", - "thiserror", - "tokio", - "tokio-util", -] - -[[package]] -name = "atk" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c3d816ce6f0e2909a96830d6911c2aff044370b1ef92d7f267b43bae5addedd" -dependencies = [ - "atk-sys", - "bitflags 1.3.2", - "glib", - "libc", -] - -[[package]] -name = "atk-sys" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58aeb089fb698e06db8089971c7ee317ab9644bade33383f63631437b03aafb6" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps 6.2.2", -] - -[[package]] -name = "atomic" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" - -[[package]] -name = "atomic_refcell" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41e67cd8309bbd06cd603a9e693a784ac2e5d1e955f11286e355089fcab3047c" - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "backtrace" -version = "0.3.71" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "base64ct" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bindgen" -version = "0.69.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" -dependencies = [ - "bitflags 2.5.0", - "cexpr", - "clang-sys", - "itertools 0.12.1", - "lazy_static", - "lazycell", - "proc-macro2", - "quote", - "regex", - "rustc-hash", - "shlex", - "syn 2.0.55", -] - -[[package]] -name = "bit-set" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" - -[[package]] -name = "bitpacking" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c1d3e2bfd8d06048a179f7b17afc3188effa10385e7b00dc65af6aae732ea92" -dependencies = [ - "crunchy", -] - -[[package]] -name = "bitvec" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" -dependencies = [ - "funty", - "radium", - "tap", - "wyz", -] - -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "borsh" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f58b559fd6448c6e2fd0adb5720cd98a2506594cafa4737ff98c396f3e82f667" -dependencies = [ - "borsh-derive", - "cfg_aliases", -] - -[[package]] -name = "borsh-derive" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7aadb5b6ccbd078890f6d7003694e33816e6b784358f18e15e7e6d9f065a57cd" -dependencies = [ - "once_cell", - "proc-macro-crate 3.1.0", - "proc-macro2", - "quote", - "syn 2.0.55", - "syn_derive", -] - -[[package]] -name = "brotli" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d640d25bc63c50fb1f0b545ffd80207d2e10a4c965530809b40ba3386825c391" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", - "brotli-decompressor", -] - -[[package]] -name = "brotli-decompressor" -version = "2.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e2e4afe60d7dd600fdd3de8d0f08c2b7ec039712e3b6137ff98b7004e82de4f" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - -[[package]] -name = "bstr" -version = "1.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" -dependencies = [ - "memchr", - "serde", -] - -[[package]] -name = "bumpalo" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - -[[package]] -name = "bytecheck" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" -dependencies = [ - "bytecheck_derive", - "ptr_meta", - "simdutf8", -] - -[[package]] -name = "bytecheck_derive" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "bytemuck" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" -dependencies = [ - "serde", -] - -[[package]] -name = "bzip2" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" -dependencies = [ - "bzip2-sys", - "libc", -] - -[[package]] -name = "bzip2-sys" -version = "0.1.11+1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - -[[package]] -name = "cairo-rs" -version = "0.15.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c76ee391b03d35510d9fa917357c7f1855bd9a6659c95a1b392e33f49b3369bc" -dependencies = [ - "bitflags 1.3.2", - "cairo-sys-rs", - "glib", - "libc", - "thiserror", -] - -[[package]] -name = "cairo-sys-rs" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c55d429bef56ac9172d25fecb85dc8068307d17acd74b377866b7a1ef25d3c8" -dependencies = [ - "glib-sys", - "libc", - "system-deps 6.2.2", -] - -[[package]] -name = "cargo_toml" -version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "599aa35200ffff8f04c1925aa1acc92fa2e08874379ef42e210a80e527e60838" -dependencies = [ - "serde", - "toml 0.7.8", -] - -[[package]] -name = "cc" -version = "1.0.90" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" -dependencies = [ - "jobserver", - "libc", -] - -[[package]] -name = "census" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f4c707c6a209cbe82d10abd08e1ea8995e9ea937d2550646e02798948992be0" - -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - -[[package]] -name = "cexpr" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] - -[[package]] -name = "cfb" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" -dependencies = [ - "byteorder", - "fnv", - "uuid", -] - -[[package]] -name = "cfg-expr" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3431df59f28accaf4cb4eed4a9acc66bea3f3c3753aa6cdc2f024174ef232af7" -dependencies = [ - "smallvec", -] - -[[package]] -name = "cfg-expr" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa50868b64a9a6fda9d593ce778849ea8715cd2a3d2cc17ffdb4a2f2f2f1961d" -dependencies = [ - "smallvec", - "target-lexicon", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "cfg_aliases" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" - -[[package]] -name = "chrono" -version = "0.4.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "serde", - "wasm-bindgen", - "windows-targets 0.52.4", -] - -[[package]] -name = "chrono-tz" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59ae0466b83e838b81a54256c39d5d7c20b9d7daa10510a242d9b75abd5936e" -dependencies = [ - "chrono", - "chrono-tz-build 0.2.1", - "phf 0.11.2", -] - -[[package]] -name = "chrono-tz" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6dd8046d00723a59a2f8c5f295c515b9bb9a331ee4f8f3d4dd49e428acd3b6" -dependencies = [ - "chrono", - "chrono-tz-build 0.4.0", - "phf 0.11.2", -] - -[[package]] -name = "chrono-tz-build" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433e39f13c9a060046954e0592a8d0a4bcb1040125cbf91cb8ee58964cfb350f" -dependencies = [ - "parse-zoneinfo", - "phf 0.11.2", - "phf_codegen 0.11.2", -] - -[[package]] -name = "chrono-tz-build" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94fea34d77a245229e7746bd2beb786cd2a896f306ff491fb8cecb3074b10a7" -dependencies = [ - "parse-zoneinfo", - "phf_codegen 0.11.2", -] - -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - -[[package]] -name = "clang-sys" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" -dependencies = [ - "glob", - "libc", - "libloading", -] - -[[package]] -name = "client-api" -version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ea131f0baab67defe7591067357eced490072372#ea131f0baab67defe7591067357eced490072372" -dependencies = [ - "again", - "anyhow", - "app-error", - "arc-swap", - "async-trait", - "base64 0.22.1", - "bincode", - "brotli", - "bytes", - "chrono", - "client-api-entity", - "client-websocket", - "collab", - "collab-rt-entity", - "collab-rt-protocol", - "futures", - "futures-core", - "futures-util", - "getrandom 0.2.12", - "gotrue", - "infra", - "lazy_static", - "md5", - "mime", - "mime_guess", - "parking_lot 0.12.1", - "percent-encoding", - "pin-project", - "prost 0.13.3", - "rayon", - "reqwest", - "scraper 0.17.1", - "semver", - "serde", - "serde_json", - "serde_repr", - "serde_urlencoded", - "shared-entity", - "thiserror", - "tokio", - "tokio-retry", - "tokio-stream", - "tokio-util", - "tracing", - "url", - "uuid", - "wasm-bindgen-futures", - "yrs", - "zstd 0.13.2", -] - -[[package]] -name = "client-api-entity" -version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ea131f0baab67defe7591067357eced490072372#ea131f0baab67defe7591067357eced490072372" -dependencies = [ - "collab-entity", - "collab-rt-entity", - "database-entity", - "gotrue-entity", - "shared-entity", - "uuid", -] - -[[package]] -name = "client-websocket" -version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ea131f0baab67defe7591067357eced490072372#ea131f0baab67defe7591067357eced490072372" -dependencies = [ - "futures-channel", - "futures-util", - "http", - "httparse", - "js-sys", - "percent-encoding", - "thiserror", - "tokio", - "tokio-tungstenite", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "clipboard-win" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d517d4b86184dbb111d3556a10f1c8a04da7428d2987bf1081602bf11c3aa9ee" -dependencies = [ - "error-code", -] - -[[package]] -name = "cmd_lib" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5f4cbdcab51ca635c5b19c85ece4072ea42e0d2360242826a6fc96fb11f0d40" -dependencies = [ - "cmd_lib_macros", - "env_logger", - "faccess", - "lazy_static", - "log", - "os_pipe", -] - -[[package]] -name = "cmd_lib_macros" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae881960f7e2a409f91ef0b1c09558cf293031a1d6e8b45f908311f2a43f5fdf" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "cocoa" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" -dependencies = [ - "bitflags 1.3.2", - "block", - "cocoa-foundation", - "core-foundation", - "core-graphics 0.22.3", - "foreign-types 0.3.2", - "libc", - "objc", -] - -[[package]] -name = "cocoa-foundation" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" -dependencies = [ - "bitflags 1.3.2", - "block", - "core-foundation", - "core-graphics-types", - "libc", - "objc", -] - -[[package]] -name = "collab" -version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=67b31e4bcc64d81332c37efecb0a8618681f1db9#67b31e4bcc64d81332c37efecb0a8618681f1db9" -dependencies = [ - "anyhow", - "arc-swap", - "async-trait", - "bincode", - "bytes", - "chrono", - "js-sys", - "lazy_static", - "serde", - "serde_json", - "serde_repr", - "thiserror", - "tokio", - "tokio-stream", - "tracing", - "unicode-segmentation", - "web-sys", - "yrs", -] - -[[package]] -name = "collab-database" -version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=67b31e4bcc64d81332c37efecb0a8618681f1db9#67b31e4bcc64d81332c37efecb0a8618681f1db9" -dependencies = [ - "anyhow", - "async-trait", - "base64 0.22.1", - "chrono", - "chrono-tz 0.10.0", - "collab", - "collab-entity", - "csv", - "dashmap 5.5.3", - "fancy-regex 0.13.0", - "futures", - "getrandom 0.2.12", - "iana-time-zone", - "js-sys", - "lazy_static", - "nanoid", - "percent-encoding", - "rayon", - "rust_decimal", - "rusty-money", - "serde", - "serde_json", - "serde_repr", - "sha2", - "strum", - "strum_macros 0.25.3", - "thiserror", - "tokio", - "tokio-stream", - "tokio-util", - "tracing", - "uuid", - "yrs", -] - -[[package]] -name = "collab-document" -version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=67b31e4bcc64d81332c37efecb0a8618681f1db9#67b31e4bcc64d81332c37efecb0a8618681f1db9" -dependencies = [ - "anyhow", - "arc-swap", - "collab", - "collab-entity", - "getrandom 0.2.12", - "markdown", - "nanoid", - "serde", - "serde_json", - "thiserror", - "tokio", - "tokio-stream", - "tracing", - "uuid", -] - -[[package]] -name = "collab-entity" -version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=67b31e4bcc64d81332c37efecb0a8618681f1db9#67b31e4bcc64d81332c37efecb0a8618681f1db9" -dependencies = [ - "anyhow", - "bytes", - "collab", - "getrandom 0.2.12", - "prost 0.13.3", - "prost-build", - "protoc-bin-vendored", - "serde", - "serde_json", - "serde_repr", - "thiserror", - "uuid", - "walkdir", -] - -[[package]] -name = "collab-folder" -version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=67b31e4bcc64d81332c37efecb0a8618681f1db9#67b31e4bcc64d81332c37efecb0a8618681f1db9" -dependencies = [ - "anyhow", - "arc-swap", - "chrono", - "collab", - "collab-entity", - "dashmap 5.5.3", - "getrandom 0.2.12", - "serde", - "serde_json", - "serde_repr", - "thiserror", - "tokio", - "tokio-stream", - "tracing", - "uuid", -] - -[[package]] -name = "collab-importer" -version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=67b31e4bcc64d81332c37efecb0a8618681f1db9#67b31e4bcc64d81332c37efecb0a8618681f1db9" -dependencies = [ - "anyhow", - "async-recursion", - "async-trait", - "async_zip", - "base64 0.22.1", - "chrono", - "collab", - "collab-database", - "collab-document", - "collab-entity", - "collab-folder", - "csv", - "fancy-regex 0.13.0", - "futures", - "futures-lite", - "futures-util", - "fxhash", - "hex", - "markdown", - "percent-encoding", - "rayon", - "sanitize-filename", - "serde", - "serde_json", - "sha2", - "thiserror", - "tokio", - "tokio-util", - "tracing", - "uuid", - "walkdir", - "zip 0.6.6", -] - -[[package]] -name = "collab-integrate" -version = "0.1.0" -dependencies = [ - "anyhow", - "arc-swap", - "async-trait", - "collab", - "collab-database", - "collab-document", - "collab-entity", - "collab-folder", - "collab-plugins", - "collab-user", - "diesel", - "flowy-error", - "flowy-sqlite", - "futures", - "lib-infra", - "serde", - "serde_json", - "tokio", - "tracing", -] - -[[package]] -name = "collab-plugins" -version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=67b31e4bcc64d81332c37efecb0a8618681f1db9#67b31e4bcc64d81332c37efecb0a8618681f1db9" -dependencies = [ - "anyhow", - "async-stream", - "async-trait", - "bincode", - "bytes", - "chrono", - "collab", - "collab-entity", - "futures", - "futures-util", - "getrandom 0.2.12", - "indexed_db_futures", - "js-sys", - "lazy_static", - "rand 0.8.5", - "rocksdb", - "serde", - "serde_json", - "similar 2.4.0", - "smallvec", - "thiserror", - "tokio", - "tokio-retry", - "tokio-stream", - "tracing", - "tracing-wasm", - "uuid", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "yrs", -] - -[[package]] -name = "collab-rt-entity" -version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ea131f0baab67defe7591067357eced490072372#ea131f0baab67defe7591067357eced490072372" -dependencies = [ - "anyhow", - "bincode", - "bytes", - "chrono", - "client-websocket", - "collab", - "collab-entity", - "collab-rt-protocol", - "database-entity", - "prost 0.13.3", - "prost-build", - "protoc-bin-vendored", - "serde", - "serde_json", - "serde_repr", - "thiserror", - "tokio-tungstenite", - "yrs", -] - -[[package]] -name = "collab-rt-protocol" -version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ea131f0baab67defe7591067357eced490072372#ea131f0baab67defe7591067357eced490072372" -dependencies = [ - "anyhow", - "async-trait", - "bincode", - "collab", - "collab-entity", - "serde", - "thiserror", - "tokio", - "tracing", - "yrs", -] - -[[package]] -name = "collab-user" -version = "0.2.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=67b31e4bcc64d81332c37efecb0a8618681f1db9#67b31e4bcc64d81332c37efecb0a8618681f1db9" -dependencies = [ - "anyhow", - "collab", - "collab-entity", - "getrandom 0.2.12", - "serde", - "serde_json", - "tokio", - "tokio-stream", - "tracing", -] - -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - -[[package]] -name = "combine" -version = "4.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "concurrent-queue" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "console" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3993e6445baa160675931ec041a5e03ca84b9c6e32a056150d3aa2bdda0a1f45" -dependencies = [ - "encode_unicode", - "lazy_static", - "libc", - "regex", - "terminal_size", - "unicode-width", - "winapi", -] - -[[package]] -name = "constant_time_eq" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" - -[[package]] -name = "constant_time_eq" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" - -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - -[[package]] -name = "cookie" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" -dependencies = [ - "percent-encoding", - "time", - "version_check", -] - -[[package]] -name = "cookie_store" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "387461abbc748185c3a6e1673d826918b450b87ff22639429c694619a83b6cf6" -dependencies = [ - "cookie", - "idna 0.3.0", - "log", - "publicsuffix", - "serde", - "serde_derive", - "serde_json", - "time", - "url", -] - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" - -[[package]] -name = "core-graphics" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "core-graphics-types", - "foreign-types 0.3.2", - "libc", -] - -[[package]] -name = "core-graphics" -version = "0.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "970a29baf4110c26fedbc7f82107d42c23f7e88e404c4577ed73fe99ff85a212" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "core-graphics-types", - "foreign-types 0.5.0", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "libc", -] - -[[package]] -name = "cpufeatures" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" -dependencies = [ - "libc", -] - -[[package]] -name = "crc" -version = "3.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" -dependencies = [ - "crc-catalog", -] - -[[package]] -name = "crc-catalog" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" - -[[package]] -name = "crc32fast" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "rand_core 0.6.4", - "typenum", -] - -[[package]] -name = "cssparser" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" -dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa 0.4.8", - "matches", - "phf 0.8.0", - "proc-macro2", - "quote", - "smallvec", - "syn 1.0.109", -] - -[[package]] -name = "cssparser" -version = "0.31.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b3df4f93e5fbbe73ec01ec8d3f68bba73107993a5b1e7519273c32db9b0d5be" -dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa 1.0.10", - "phf 0.8.0", - "smallvec", -] - -[[package]] -name = "cssparser-macros" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" -dependencies = [ - "quote", - "syn 2.0.55", -] - -[[package]] -name = "csv" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" -dependencies = [ - "csv-core", - "itoa 1.0.10", - "ryu", - "serde", -] - -[[package]] -name = "csv-core" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" -dependencies = [ - "memchr", -] - -[[package]] -name = "ctor" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad291aa74992b9b7a7e88c38acbbf6ad7e107f1d90ee8775b7bc1fc3394f485c" -dependencies = [ - "quote", - "syn 2.0.55", -] - -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher", -] - -[[package]] -name = "darling" -version = "0.20.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim 0.10.0", - "syn 2.0.55", -] - -[[package]] -name = "darling_macro" -version = "0.20.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f" -dependencies = [ - "darling_core", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "dashmap" -version = "5.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" -dependencies = [ - "cfg-if", - "hashbrown 0.14.3", - "lock_api", - "once_cell", - "parking_lot_core 0.9.9", -] - -[[package]] -name = "dashmap" -version = "6.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804c8821570c3f8b70230c2ba75ffa5c0f9a4189b9a432b6656c536712acae28" -dependencies = [ - "cfg-if", - "crossbeam-utils", - "hashbrown 0.14.3", - "lock_api", - "once_cell", - "parking_lot_core 0.9.9", -] - -[[package]] -name = "data-encoding" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" - -[[package]] -name = "database-entity" -version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ea131f0baab67defe7591067357eced490072372#ea131f0baab67defe7591067357eced490072372" -dependencies = [ - "anyhow", - "app-error", - "appflowy-ai-client", - "bincode", - "bytes", - "chrono", - "collab-entity", - "infra", - "prost 0.13.3", - "serde", - "serde_json", - "serde_repr", - "thiserror", - "tracing", - "uuid", - "validator 0.19.0", -] - -[[package]] -name = "date_time_parser" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0521d96e513670773ac503e5f5239178c3aef16cffda1e77a3cdbdbe993fb5a" -dependencies = [ - "chrono", - "regex", -] - -[[package]] -name = "deflate64" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" - -[[package]] -name = "delegate-display" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98a85201f233142ac819bbf6226e36d0b5e129a47bd325084674261c82d4cd66" -dependencies = [ - "macroific", - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "deranged" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", - "serde", -] - -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "derive-new" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d150dea618e920167e5973d70ae6ece4385b7164e0d799fe7c122dd0a5d912ad" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "derive_arbitrary" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "derive_more" -version = "0.99.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn 1.0.109", -] - -[[package]] -name = "deunicode" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6e854126756c496b8c81dec88f9a706b15b875c5849d4097a3854476b9fdf94" - -[[package]] -name = "diesel" -version = "2.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03fc05c17098f21b89bc7d98fe1dd3cce2c11c2ad8e145f2a44fe08ed28eb559" -dependencies = [ - "chrono", - "diesel_derives", - "libsqlite3-sys", - "r2d2", - "serde_json", - "time", -] - -[[package]] -name = "diesel_derives" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d02eecb814ae714ffe61ddc2db2dd03e6c49a42e269b5001355500d431cce0c" -dependencies = [ - "diesel_table_macro_syntax", - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "diesel_migrations" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6036b3f0120c5961381b570ee20a02432d7e2d27ea60de9578799cf9156914ac" -dependencies = [ - "diesel", - "migrations_internals", - "migrations_macros", -] - -[[package]] -name = "diesel_table_macro_syntax" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5" -dependencies = [ - "syn 2.0.55", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", - "subtle", -] - -[[package]] -name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.48.0", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "dispatch" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "dlib" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" -dependencies = [ - "libloading", -] - -[[package]] -name = "dotenv" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" - -[[package]] -name = "downcast-rs" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" - -[[package]] -name = "dtoa" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" - -[[package]] -name = "dtoa-short" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbaceec3c6e4211c79e7b1800fb9680527106beb2f9c51904a3210c03a448c74" -dependencies = [ - "dtoa", -] - -[[package]] -name = "dunce" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" - -[[package]] -name = "dyn-clone" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" - -[[package]] -name = "ego-tree" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a68a4904193147e0a8dec3314640e6db742afd5f6e634f428a6af230d9b3591" - -[[package]] -name = "either" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" - -[[package]] -name = "embed-resource" -version = "2.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6985554d0688b687c5cb73898a34fbe3ad6c24c58c238a4d91d5e840670ee9d" -dependencies = [ - "cc", - "memchr", - "rustc_version", - "toml 0.8.12", - "vswhom", - "winreg 0.52.0", -] - -[[package]] -name = "embed_plist" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" - -[[package]] -name = "encode_unicode" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" - -[[package]] -name = "encoding_rs" -version = "0.8.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "env_logger" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" -dependencies = [ - "humantime", - "is-terminal", - "log", - "regex", - "termcolor", -] - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "errno" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "error-code" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b" - -[[package]] -name = "event-listener" -version = "5.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" -dependencies = [ - "concurrent-queue", - "parking", - "pin-project-lite", -] - -[[package]] -name = "event-listener-strategy" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" -dependencies = [ - "event-listener", - "pin-project-lite", -] - -[[package]] -name = "faccess" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ae66425802d6a903e268ae1a08b8c38ba143520f227a205edf4e9c7e3e26d5" -dependencies = [ - "bitflags 1.3.2", - "libc", - "winapi", -] - -[[package]] -name = "fancy-regex" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0678ab2d46fa5195aaf59ad034c083d351377d4af57f3e073c074d0da3e3c766" -dependencies = [ - "bit-set", - "regex", -] - -[[package]] -name = "fancy-regex" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b95f7c0680e4142284cf8b22c14a476e87d61b004a3a0861872b32ef7ead40a2" -dependencies = [ - "bit-set", - "regex", -] - -[[package]] -name = "fancy-regex" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531e46835a22af56d1e3b66f04844bed63158bc094a628bec1d321d9b4c44bf2" -dependencies = [ - "bit-set", - "regex-automata 0.4.6", - "regex-syntax 0.8.2", -] - -[[package]] -name = "fancy_constructor" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f71f317e4af73b2f8f608fac190c52eac4b1879d2145df1db2fe48881ca69435" -dependencies = [ - "macroific", - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "fastdivide" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59668941c55e5c186b8b58c391629af56774ec768f73c08bbcd56f09348eb00b" - -[[package]] -name = "fastrand" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" -dependencies = [ - "getrandom 0.2.12", -] - -[[package]] -name = "fdeflate" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" -dependencies = [ - "simd-adler32", -] - -[[package]] -name = "field-offset" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" -dependencies = [ - "memoffset", - "rustc_version", -] - -[[package]] -name = "filetime" -version = "0.2.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.4.1", - "windows-sys 0.52.0", -] - -[[package]] -name = "fixedbitset" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" - -[[package]] -name = "flate2" -version = "1.0.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "flowy-ai" -version = "0.1.0" -dependencies = [ - "allo-isolate", - "anyhow", - "appflowy-local-ai", - "appflowy-plugin", - "arc-swap", - "base64 0.21.7", - "bytes", - "collab-integrate", - "dashmap 6.0.1", - "flowy-ai-pub", - "flowy-codegen", - "flowy-derive", - "flowy-error", - "flowy-notification", - "flowy-sqlite", - "flowy-storage-pub", - "futures", - "futures-util", - "lib-dispatch", - "lib-infra", - "log", - "md5", - "notify", - "pin-project", - "protobuf", - "reqwest", - "serde", - "serde_json", - "sha2", - "strum_macros 0.21.1", - "tokio", - "tokio-stream", - "tokio-util", - "tracing", - "uuid", - "validator 0.18.1", - "zip 2.2.0", - "zip-extensions", -] - -[[package]] -name = "flowy-ai-pub" -version = "0.1.0" -dependencies = [ - "bytes", - "client-api", - "flowy-error", - "futures", - "lib-infra", - "serde_json", -] - -[[package]] -name = "flowy-ast" -version = "0.1.0" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "flowy-codegen" -version = "0.1.0" -dependencies = [ - "cmd_lib", - "console", - "fancy-regex 0.10.0", - "flowy-ast", - "itertools 0.10.5", - "lazy_static", - "log", - "phf 0.8.0", - "protoc-bin-vendored", - "protoc-rust", - "quote", - "serde", - "serde_json", - "similar 1.3.0", - "syn 1.0.109", - "tera", - "toml 0.5.11", - "walkdir", -] - -[[package]] -name = "flowy-config" -version = "0.1.0" -dependencies = [ - "bytes", - "flowy-codegen", - "flowy-derive", - "flowy-error", - "flowy-sqlite", - "lib-dispatch", - "protobuf", - "strum_macros 0.21.1", -] - -[[package]] -name = "flowy-core" -version = "0.1.0" -dependencies = [ - "anyhow", - "appflowy-local-ai", - "arc-swap", - "base64 0.21.7", - "bytes", - "client-api", - "collab", - "collab-entity", - "collab-folder", - "collab-integrate", - "collab-plugins", - "dashmap 6.0.1", - "diesel", - "flowy-ai", - "flowy-ai-pub", - "flowy-config", - "flowy-database-pub", - "flowy-database2", - "flowy-date", - "flowy-document", - "flowy-document-pub", - "flowy-error", - "flowy-folder", - "flowy-folder-pub", - "flowy-search", - "flowy-search-pub", - "flowy-server", - "flowy-server-pub", - "flowy-sqlite", - "flowy-storage", - "flowy-storage-pub", - "flowy-user", - "flowy-user-pub", - "futures", - "futures-core", - "lib-dispatch", - "lib-infra", - "lib-log", - "semver", - "serde", - "serde_json", - "serde_repr", - "sysinfo", - "tokio", - "tokio-stream", - "tracing", - "uuid", - "walkdir", -] - -[[package]] -name = "flowy-database-pub" -version = "0.1.0" -dependencies = [ - "anyhow", - "client-api", - "collab", - "collab-entity", - "flowy-error", - "lib-infra", -] - -[[package]] -name = "flowy-database2" -version = "0.1.0" -dependencies = [ - "anyhow", - "arc-swap", - "async-stream", - "async-trait", - "bytes", - "chrono", - "chrono-tz 0.8.6", - "collab", - "collab-database", - "collab-entity", - "collab-integrate", - "collab-plugins", - "csv", - "dashmap 6.0.1", - "fancy-regex 0.11.0", - "flowy-codegen", - "flowy-database-pub", - "flowy-derive", - "flowy-error", - "flowy-notification", - "futures", - "indexmap 2.2.6", - "lazy_static", - "lib-dispatch", - "lib-infra", - "moka", - "nanoid", - "protobuf", - "rayon", - "rust_decimal", - "rusty-money", - "serde", - "serde_json", - "serde_repr", - "strum", - "strum_macros 0.25.3", - "tokio", - "tokio-util", - "tracing", - "url", - "validator 0.18.1", -] - -[[package]] -name = "flowy-date" -version = "0.1.0" -dependencies = [ - "bytes", - "chrono", - "date_time_parser", - "fancy-regex 0.11.0", - "flowy-codegen", - "flowy-derive", - "flowy-error", - "lib-dispatch", - "protobuf", - "strum_macros 0.21.1", - "tracing", -] - -[[package]] -name = "flowy-derive" -version = "0.1.0" -dependencies = [ - "dashmap 6.0.1", - "flowy-ast", - "flowy-codegen", - "lazy_static", - "proc-macro2", - "quote", - "serde_json", - "syn 1.0.109", - "walkdir", -] - -[[package]] -name = "flowy-document" -version = "0.1.0" -dependencies = [ - "anyhow", - "bytes", - "collab", - "collab-document", - "collab-entity", - "collab-integrate", - "collab-plugins", - "dashmap 6.0.1", - "flowy-codegen", - "flowy-derive", - "flowy-document-pub", - "flowy-error", - "flowy-notification", - "flowy-storage-pub", - "futures", - "getrandom 0.2.12", - "indexmap 2.2.6", - "lib-dispatch", - "lib-infra", - "nanoid", - "protobuf", - "scraper 0.18.1", - "serde", - "serde_json", - "strum_macros 0.21.1", - "tokio", - "tokio-stream", - "tracing", - "uuid", - "validator 0.18.1", -] - -[[package]] -name = "flowy-document-pub" -version = "0.1.0" -dependencies = [ - "anyhow", - "collab", - "collab-document", - "flowy-error", - "lib-infra", -] - -[[package]] -name = "flowy-encrypt" -version = "0.1.0" -dependencies = [ - "aes-gcm", - "anyhow", - "base64 0.21.7", - "getrandom 0.2.12", - "hmac", - "pbkdf2 0.12.2", - "rand 0.8.5", - "sha2", -] - -[[package]] -name = "flowy-error" -version = "0.1.0" -dependencies = [ - "anyhow", - "bytes", - "client-api", - "collab", - "collab-database", - "collab-document", - "collab-folder", - "collab-plugins", - "fancy-regex 0.11.0", - "flowy-codegen", - "flowy-derive", - "flowy-sqlite", - "lib-dispatch", - "protobuf", - "r2d2", - "reqwest", - "serde", - "serde_json", - "serde_repr", - "tantivy", - "thiserror", - "tokio", - "url", - "validator 0.18.1", -] - -[[package]] -name = "flowy-folder" -version = "0.1.0" -dependencies = [ - "arc-swap", - "async-trait", - "bytes", - "chrono", - "client-api", - "collab", - "collab-document", - "collab-entity", - "collab-folder", - "collab-integrate", - "collab-plugins", - "dashmap 6.0.1", - "flowy-codegen", - "flowy-derive", - "flowy-error", - "flowy-folder-pub", - "flowy-notification", - "flowy-search-pub", - "flowy-sqlite", - "futures", - "lazy_static", - "lib-dispatch", - "lib-infra", - "nanoid", - "protobuf", - "regex", - "serde", - "serde_json", - "strum_macros 0.21.1", - "tokio", - "tokio-stream", - "tracing", - "unicode-segmentation", - "uuid", - "validator 0.18.1", -] - -[[package]] -name = "flowy-folder-pub" -version = "0.1.0" -dependencies = [ - "anyhow", - "client-api", - "collab", - "collab-entity", - "collab-folder", - "flowy-error", - "lib-infra", - "serde", - "serde_json", - "uuid", -] - -[[package]] -name = "flowy-notification" -version = "0.1.0" -dependencies = [ - "bytes", - "dashmap 6.0.1", - "flowy-codegen", - "flowy-derive", - "lazy_static", - "lib-dispatch", - "protobuf", - "serde", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "flowy-search" -version = "0.1.0" -dependencies = [ - "async-stream", - "bytes", - "collab", - "collab-folder", - "diesel", - "diesel_derives", - "diesel_migrations", - "flowy-codegen", - "flowy-derive", - "flowy-error", - "flowy-folder", - "flowy-notification", - "flowy-search-pub", - "flowy-sqlite", - "flowy-user", - "futures", - "lib-dispatch", - "lib-infra", - "protobuf", - "serde", - "serde_json", - "strsim 0.11.1", - "strum_macros 0.26.2", - "tantivy", - "tempfile", - "tokio", - "tracing", - "validator 0.18.1", -] - -[[package]] -name = "flowy-search-pub" -version = "0.1.0" -dependencies = [ - "client-api", - "collab", - "collab-folder", - "flowy-error", - "futures", - "lib-infra", -] - -[[package]] -name = "flowy-server" -version = "0.1.0" -dependencies = [ - "anyhow", - "arc-swap", - "bytes", - "chrono", - "client-api", - "collab", - "collab-database", - "collab-document", - "collab-entity", - "collab-folder", - "collab-plugins", - "collab-user", - "dashmap 6.0.1", - "flowy-ai-pub", - "flowy-database-pub", - "flowy-document-pub", - "flowy-encrypt", - "flowy-error", - "flowy-folder-pub", - "flowy-search-pub", - "flowy-server-pub", - "flowy-storage", - "flowy-storage-pub", - "flowy-user-pub", - "futures", - "futures-util", - "hex", - "hyper", - "lazy_static", - "lib-dispatch", - "lib-infra", - "mime_guess", - "postgrest", - "rand 0.8.5", - "reqwest", - "semver", - "serde", - "serde_json", - "thiserror", - "tokio", - "tokio-retry", - "tokio-stream", - "tokio-util", - "tracing", - "url", - "uuid", - "yrs", -] - -[[package]] -name = "flowy-server-pub" -version = "0.1.0" -dependencies = [ - "flowy-error", - "serde", - "serde_repr", -] - -[[package]] -name = "flowy-sqlite" -version = "0.1.0" -dependencies = [ - "anyhow", - "diesel", - "diesel_derives", - "diesel_migrations", - "libsqlite3-sys", - "r2d2", - "scheduled-thread-pool", - "serde", - "serde_json", - "thiserror", - "tracing", -] - -[[package]] -name = "flowy-storage" -version = "0.1.0" -dependencies = [ - "allo-isolate", - "anyhow", - "async-trait", - "bytes", - "chrono", - "collab-importer", - "dashmap 6.0.1", - "flowy-codegen", - "flowy-derive", - "flowy-error", - "flowy-notification", - "flowy-sqlite", - "flowy-storage-pub", - "futures-util", - "fxhash", - "lib-dispatch", - "lib-infra", - "mime_guess", - "protobuf", - "serde", - "serde_json", - "strum_macros 0.25.3", - "tokio", - "tracing", - "url", -] - -[[package]] -name = "flowy-storage-pub" -version = "0.1.0" -dependencies = [ - "anyhow", - "async-trait", - "bytes", - "client-api-entity", - "flowy-error", - "lib-infra", - "mime", - "mime_guess", - "serde", - "serde_json", - "tokio", - "tracing", -] - -[[package]] -name = "flowy-user" -version = "0.1.0" -dependencies = [ - "anyhow", - "arc-swap", - "base64 0.21.7", - "bytes", - "chrono", - "client-api", - "collab", - "collab-database", - "collab-document", - "collab-entity", - "collab-folder", - "collab-integrate", - "collab-plugins", - "collab-user", - "dashmap 6.0.1", - "diesel", - "diesel_derives", - "fancy-regex 0.11.0", - "flowy-codegen", - "flowy-derive", - "flowy-encrypt", - "flowy-error", - "flowy-folder-pub", - "flowy-notification", - "flowy-server-pub", - "flowy-sqlite", - "flowy-user-pub", - "lazy_static", - "lib-dispatch", - "lib-infra", - "once_cell", - "protobuf", - "rayon", - "semver", - "serde", - "serde_json", - "serde_repr", - "strum", - "strum_macros 0.25.3", - "tokio", - "tokio-stream", - "tracing", - "unicode-segmentation", - "uuid", - "validator 0.18.1", -] - -[[package]] -name = "flowy-user-pub" -version = "0.1.0" -dependencies = [ - "anyhow", - "base64 0.21.7", - "chrono", - "client-api", - "collab", - "collab-entity", - "collab-folder", - "flowy-error", - "flowy-folder-pub", - "lib-infra", - "serde", - "serde_json", - "serde_repr", - "tokio", - "tokio-stream", - "tracing", - "uuid", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared 0.1.1", -] - -[[package]] -name = "foreign-types" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" -dependencies = [ - "foreign-types-macros", - "foreign-types-shared 0.3.1", -] - -[[package]] -name = "foreign-types-macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "foreign-types-shared" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "fs4" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e180ac76c23b45e767bd7ae9579bc0bb458618c4bc71835926e098e61d15f8" -dependencies = [ - "rustix", - "windows-sys 0.52.0", -] - -[[package]] -name = "fsevent-sys" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" -dependencies = [ - "libc", -] - -[[package]] -name = "funty" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" - -[[package]] -name = "futf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" -dependencies = [ - "mac", - "new_debug_unreachable", -] - -[[package]] -name = "futures" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" - -[[package]] -name = "futures-executor" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" - -[[package]] -name = "futures-lite" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "parking", - "pin-project-lite", -] - -[[package]] -name = "futures-macro" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "futures-sink" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" - -[[package]] -name = "futures-task" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" - -[[package]] -name = "futures-util" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - -[[package]] -name = "gdk" -version = "0.15.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6e05c1f572ab0e1f15be94217f0dc29088c248b14f792a5ff0af0d84bcda9e8" -dependencies = [ - "bitflags 1.3.2", - "cairo-rs", - "gdk-pixbuf", - "gdk-sys", - "gio", - "glib", - "libc", - "pango", -] - -[[package]] -name = "gdk-pixbuf" -version = "0.15.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad38dd9cc8b099cceecdf41375bb6d481b1b5a7cd5cd603e10a69a9383f8619a" -dependencies = [ - "bitflags 1.3.2", - "gdk-pixbuf-sys", - "gio", - "glib", - "libc", -] - -[[package]] -name = "gdk-pixbuf-sys" -version = "0.15.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "140b2f5378256527150350a8346dbdb08fadc13453a7a2d73aecd5fab3c402a7" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps 6.2.2", -] - -[[package]] -name = "gdk-sys" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e7a08c1e8f06f4177fb7e51a777b8c1689f743a7bc11ea91d44d2226073a88" -dependencies = [ - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "pkg-config", - "system-deps 6.2.2", -] - -[[package]] -name = "gdkwayland-sys" -version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cca49a59ad8cfdf36ef7330fe7bdfbe1d34323220cc16a0de2679ee773aee2c2" -dependencies = [ - "gdk-sys", - "glib-sys", - "gobject-sys", - "libc", - "pkg-config", - "system-deps 6.2.2", -] - -[[package]] -name = "gdkx11-sys" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b7f8c7a84b407aa9b143877e267e848ff34106578b64d1e0a24bf550716178" -dependencies = [ - "gdk-sys", - "glib-sys", - "libc", - "system-deps 6.2.2", - "x11", -] - -[[package]] -name = "generator" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" -dependencies = [ - "cc", - "libc", - "log", - "rustversion", - "windows 0.48.0", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "gethostname" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "gethostname" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" -dependencies = [ - "libc", - "windows-targets 0.48.5", -] - -[[package]] -name = "getopts" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" -dependencies = [ - "unicode-width", -] - -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - -[[package]] -name = "getrandom" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "wasm-bindgen", -] - -[[package]] -name = "ghash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" -dependencies = [ - "opaque-debug", - "polyval", -] - -[[package]] -name = "gimli" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" - -[[package]] -name = "gio" -version = "0.15.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68fdbc90312d462781a395f7a16d96a2b379bb6ef8cd6310a2df272771c4283b" -dependencies = [ - "bitflags 1.3.2", - "futures-channel", - "futures-core", - "futures-io", - "gio-sys", - "glib", - "libc", - "once_cell", - "thiserror", -] - -[[package]] -name = "gio-sys" -version = "0.15.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32157a475271e2c4a023382e9cab31c4584ee30a97da41d3c4e9fdd605abcf8d" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps 6.2.2", - "winapi", -] - -[[package]] -name = "glib" -version = "0.15.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb0306fbad0ab5428b0ca674a23893db909a98582969c9b537be4ced78c505d" -dependencies = [ - "bitflags 1.3.2", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "once_cell", - "smallvec", - "thiserror", -] - -[[package]] -name = "glib-macros" -version = "0.15.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10c6ae9f6fa26f4fb2ac16b528d138d971ead56141de489f8111e259b9df3c4a" -dependencies = [ - "anyhow", - "heck 0.4.1", - "proc-macro-crate 1.3.1", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "glib-sys" -version = "0.15.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4b192f8e65e9cf76cbf4ea71fa8e3be4a0e18ffe3d68b8da6836974cc5bad4" -dependencies = [ - "libc", - "system-deps 6.2.2", -] - -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - -[[package]] -name = "globset" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" -dependencies = [ - "aho-corasick", - "bstr", - "log", - "regex-automata 0.4.6", - "regex-syntax 0.8.2", -] - -[[package]] -name = "globwalk" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" -dependencies = [ - "bitflags 1.3.2", - "ignore", - "walkdir", -] - -[[package]] -name = "gloo-utils" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e" -dependencies = [ - "js-sys", - "serde", - "serde_json", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "gobject-sys" -version = "0.15.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d57ce44246becd17153bd035ab4d32cfee096a657fc01f2231c9278378d1e0a" -dependencies = [ - "glib-sys", - "libc", - "system-deps 6.2.2", -] - -[[package]] -name = "gotrue" -version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ea131f0baab67defe7591067357eced490072372#ea131f0baab67defe7591067357eced490072372" -dependencies = [ - "anyhow", - "futures-util", - "getrandom 0.2.12", - "gotrue-entity", - "infra", - "reqwest", - "serde", - "serde_json", - "tokio", - "tracing", -] - -[[package]] -name = "gotrue-entity" -version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ea131f0baab67defe7591067357eced490072372#ea131f0baab67defe7591067357eced490072372" -dependencies = [ - "anyhow", - "app-error", - "chrono", - "jsonwebtoken", - "lazy_static", - "serde", - "serde_json", -] - -[[package]] -name = "gtk" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e3004a2d5d6d8b5057d2b57b3712c9529b62e82c77f25c1fecde1fd5c23bd0" -dependencies = [ - "atk", - "bitflags 1.3.2", - "cairo-rs", - "field-offset", - "futures-channel", - "gdk", - "gdk-pixbuf", - "gio", - "glib", - "gtk-sys", - "gtk3-macros", - "libc", - "once_cell", - "pango", - "pkg-config", -] - -[[package]] -name = "gtk-sys" -version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5bc2f0587cba247f60246a0ca11fe25fb733eabc3de12d1965fc07efab87c84" -dependencies = [ - "atk-sys", - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "pango-sys", - "system-deps 6.2.2", -] - -[[package]] -name = "gtk3-macros" -version = "0.15.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "684c0456c086e8e7e9af73ec5b84e35938df394712054550e81558d21c44ab0d" -dependencies = [ - "anyhow", - "proc-macro-crate 1.3.1", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "h2" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fbd2820c5e49886948654ab546d0688ff24530286bdcf8fca3cefb16d4618eb" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap 2.2.6", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash 0.7.8", -] - -[[package]] -name = "hashbrown" -version = "0.14.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" -dependencies = [ - "ahash 0.8.11", - "allocator-api2", -] - -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "home" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "html5ever" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" -dependencies = [ - "log", - "mac", - "markup5ever", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "htmlescape" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163" - -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa 1.0.10", -] - -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - -[[package]] -name = "http-range" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" - -[[package]] -name = "httparse" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "humansize" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" -dependencies = [ - "libm", -] - -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - -[[package]] -name = "hyper" -version = "0.14.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa 1.0.10", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" -dependencies = [ - "futures-util", - "http", - "hyper", - "rustls", - "tokio", - "tokio-rustls", -] - -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper", - "native-tls", - "tokio", - "tokio-native-tls", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "ico" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3804960be0bb5e4edb1e1ad67afd321a9ecfd875c3e65c099468fd2717d7cae" -dependencies = [ - "byteorder", - "png", -] - -[[package]] -name = "icu_collections" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locid" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - -[[package]] -name = "icu_normalizer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "utf16_iter", - "utf8_iter", - "write16", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" - -[[package]] -name = "icu_properties" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" -dependencies = [ - "displaydoc", - "icu_collections", - "icu_locid_transform", - "icu_properties_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" - -[[package]] -name = "icu_provider" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", - "writeable", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_provider_macros" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "idna" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "idna" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "ignore" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" -dependencies = [ - "crossbeam-deque", - "globset", - "log", - "memchr", - "regex-automata 0.4.6", - "same-file", - "walkdir", - "winapi-util", -] - -[[package]] -name = "image" -version = "0.24.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" -dependencies = [ - "bytemuck", - "byteorder", - "color_quant", - "num-traits", - "png", - "tiff", -] - -[[package]] -name = "indexed_db_futures" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0704b71f13f81b5933d791abf2de26b33c40935143985220299a357721166706" -dependencies = [ - "accessory", - "cfg-if", - "delegate-display", - "fancy_constructor", - "js-sys", - "uuid", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - -[[package]] -name = "indexmap" -version = "2.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" -dependencies = [ - "equivalent", - "hashbrown 0.14.3", - "serde", -] - -[[package]] -name = "infer" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f551f8c3a39f68f986517db0d1759de85881894fdc7db798bd2a9df9cb04b7fc" -dependencies = [ - "cfb", -] - -[[package]] -name = "infra" -version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ea131f0baab67defe7591067357eced490072372#ea131f0baab67defe7591067357eced490072372" -dependencies = [ - "anyhow", - "bytes", - "futures", - "pin-project", - "reqwest", - "serde", - "serde_json", - "tokio", - "tracing", - "validator 0.19.0", -] - -[[package]] -name = "inotify" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" -dependencies = [ - "bitflags 1.3.2", - "inotify-sys", - "libc", -] - -[[package]] -name = "inotify-sys" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" -dependencies = [ - "libc", -] - -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "generic-array", -] - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "interprocess" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81f2533f3be42fffe3b5e63b71aeca416c1c3bc33e4e27be018521e76b1f38fb" -dependencies = [ - "cfg-if", - "libc", - "rustc_version", - "to_method", - "winapi", -] - -[[package]] -name = "ipnet" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" - -[[package]] -name = "is-terminal" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - -[[package]] -name = "itoa" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" - -[[package]] -name = "javascriptcore-rs" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf053e7843f2812ff03ef5afe34bb9c06ffee120385caad4f6b9967fcd37d41c" -dependencies = [ - "bitflags 1.3.2", - "glib", - "javascriptcore-rs-sys", -] - -[[package]] -name = "javascriptcore-rs-sys" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "905fbb87419c5cde6e3269537e4ea7d46431f3008c5d057e915ef3f115e7793c" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps 5.0.0", -] - -[[package]] -name = "jni" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c" -dependencies = [ - "cesu8", - "combine", - "jni-sys", - "log", - "thiserror", - "walkdir", -] - -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - -[[package]] -name = "jobserver" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" -dependencies = [ - "libc", -] - -[[package]] -name = "jpeg-decoder" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" - -[[package]] -name = "js-sys" -version = "0.3.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "json-patch" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ff1e1486799e3f64129f8ccad108b38290df9cd7015cd31bed17239f0789d6" -dependencies = [ - "serde", - "serde_json", - "thiserror", - "treediff", -] - -[[package]] -name = "jsonwebtoken" -version = "8.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" -dependencies = [ - "base64 0.21.7", - "pem", - "ring 0.16.20", - "serde", - "serde_json", - "simple_asn1", -] - -[[package]] -name = "kqueue" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" -dependencies = [ - "kqueue-sys", - "libc", -] - -[[package]] -name = "kqueue-sys" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" -dependencies = [ - "bitflags 1.3.2", - "libc", -] - -[[package]] -name = "kuchikiki" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8" -dependencies = [ - "cssparser 0.27.2", - "html5ever", - "indexmap 1.9.3", - "matches", - "selectors 0.22.0", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - -[[package]] -name = "levenshtein_automata" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2cdeb66e45e9f36bfad5bbdb4d2384e70936afbee843c6f6543f0c551ebb25" - -[[package]] -name = "lib-dispatch" -version = "0.1.0" -dependencies = [ - "bincode", - "bytes", - "derivative", - "dyn-clone", - "futures", - "futures-channel", - "futures-core", - "futures-util", - "getrandom 0.2.12", - "nanoid", - "pin-project", - "protobuf", - "serde", - "serde_json", - "serde_repr", - "thread-id", - "tokio", - "tracing", - "validator 0.18.1", - "wasm-bindgen", - "wasm-bindgen-futures", -] - -[[package]] -name = "lib-infra" -version = "0.1.0" -dependencies = [ - "allo-isolate", - "anyhow", - "async-trait", - "atomic_refcell", - "bytes", - "cfg-if", - "chrono", - "futures", - "futures-core", - "futures-util", - "md5", - "pin-project", - "tempfile", - "tokio", - "tracing", - "validator 0.18.1", - "walkdir", - "zip 2.2.0", -] - -[[package]] -name = "lib-log" -version = "0.1.0" -dependencies = [ - "chrono", - "lazy_static", - "lib-infra", - "serde", - "serde_json", - "tracing", - "tracing-appender", - "tracing-bunyan-formatter", - "tracing-core", - "tracing-subscriber", -] - -[[package]] -name = "libc" -version = "0.2.153" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" - -[[package]] -name = "libloading" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" -dependencies = [ - "cfg-if", - "windows-targets 0.52.4", -] - -[[package]] -name = "libm" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" - -[[package]] -name = "libredox" -version = "0.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" -dependencies = [ - "bitflags 2.5.0", - "libc", - "redox_syscall 0.4.1", -] - -[[package]] -name = "librocksdb-sys" -version = "0.16.0+8.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce3d60bc059831dc1c83903fb45c103f75db65c5a7bf22272764d9cc683e348c" -dependencies = [ - "bindgen", - "bzip2-sys", - "cc", - "glob", - "libc", - "libz-sys", - "zstd-sys", -] - -[[package]] -name = "libsqlite3-sys" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "libz-sys" -version = "1.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e143b5e666b2695d28f6bca6497720813f699c9602dd7f5cac91008b8ada7f9" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "line-wrap" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd1bc4d24ad230d21fb898d1116b1801d7adfc449d42026475862ab48b11e70e" - -[[package]] -name = "linux-raw-sys" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" - -[[package]] -name = "litemap" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" - -[[package]] -name = "lock_api" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "lockfree-object-pool" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e" - -[[package]] -name = "log" -version = "0.4.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" - -[[package]] -name = "loom" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" -dependencies = [ - "cfg-if", - "generator", - "pin-utils", - "scoped-tls", - "serde", - "serde_json", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "lru" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3262e75e648fce39813cb56ac41f3c3e3f65217ebf3844d818d1f9398cfb0dc" -dependencies = [ - "hashbrown 0.14.3", -] - -[[package]] -name = "lz4_flex" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5" - -[[package]] -name = "lzma-rs" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" -dependencies = [ - "byteorder", - "crc", -] - -[[package]] -name = "lzma-sys" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" -dependencies = [ - "cc", - "libc", - "pkg-config", -] - -[[package]] -name = "mac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" - -[[package]] -name = "macroific" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f05c00ac596022625d01047c421a0d97d7f09a18e429187b341c201cb631b9dd" -dependencies = [ - "macroific_attr_parse", - "macroific_core", - "macroific_macro", -] - -[[package]] -name = "macroific_attr_parse" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd94d5da95b30ae6e10621ad02340909346ad91661f3f8c0f2b62345e46a2f67" -dependencies = [ - "cfg-if", - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "macroific_core" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13198c120864097a565ccb3ff947672d969932b7975ebd4085732c9f09435e55" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "macroific_macro" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c9853143cbed7f1e41dc39fee95f9b361bec65c8dc2a01bf609be01b61f5ae" -dependencies = [ - "macroific_attr_parse", - "macroific_core", - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - -[[package]] -name = "markdown" -version = "1.0.0-alpha.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6491e6c702bf7e3b24e769d800746d5f2c06a6c6a2db7992612e0f429029e81" -dependencies = [ - "unicode-id", -] - -[[package]] -name = "markup5ever" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" -dependencies = [ - "log", - "phf 0.10.1", - "phf_codegen 0.10.0", - "string_cache", - "string_cache_codegen", - "tendril", -] - -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata 0.1.10", -] - -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - -[[package]] -name = "md5" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" - -[[package]] -name = "measure_time" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56220900f1a0923789ecd6bf25fbae8af3b2f1ff3e9e297fc9b6b8674dd4d852" -dependencies = [ - "instant", - "log", -] - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "memmap2" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" -dependencies = [ - "libc", -] - -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] - -[[package]] -name = "migrations_internals" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f23f71580015254b020e856feac3df5878c2c7a8812297edd6c0a485ac9dada" -dependencies = [ - "serde", - "toml 0.7.8", -] - -[[package]] -name = "migrations_macros" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cce3325ac70e67bbab5bd837a31cae01f1a6db64e0e744a33cb03a543469ef08" -dependencies = [ - "migrations_internals", - "proc-macro2", - "quote", -] - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "mime_guess" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" -dependencies = [ - "mime", - "unicase", -] - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" -dependencies = [ - "adler", - "simd-adler32", -] - -[[package]] -name = "mio" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" -dependencies = [ - "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.48.0", -] - -[[package]] -name = "moka" -version = "0.12.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32cf62eb4dd975d2dde76432fb1075c49e3ee2331cf36f1f8fd4b66550d32b6f" -dependencies = [ - "async-lock", - "async-trait", - "crossbeam-channel", - "crossbeam-epoch", - "crossbeam-utils", - "event-listener", - "futures-util", - "once_cell", - "parking_lot 0.12.1", - "quanta", - "rustc_version", - "smallvec", - "tagptr", - "thiserror", - "triomphe", - "uuid", -] - -[[package]] -name = "multimap" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" - -[[package]] -name = "murmurhash32" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2195bf6aa996a481483b29d62a7663eed3fe39600c460e323f8ff41e90bdd89b" - -[[package]] -name = "nanoid" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" -dependencies = [ - "rand 0.8.5", -] - -[[package]] -name = "native-tls" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "ndk" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4" -dependencies = [ - "bitflags 1.3.2", - "jni-sys", - "ndk-sys", - "num_enum", - "thiserror", -] - -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - -[[package]] -name = "ndk-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97" -dependencies = [ - "jni-sys", -] - -[[package]] -name = "new_debug_unreachable" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" - -[[package]] -name = "nix" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" -dependencies = [ - "bitflags 2.5.0", - "cfg-if", - "cfg_aliases", - "libc", -] - -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "notify" -version = "6.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" -dependencies = [ - "bitflags 2.5.0", - "crossbeam-channel", - "filetime", - "fsevent-sys", - "inotify", - "kqueue", - "libc", - "log", - "mio", - "walkdir", - "windows-sys 0.48.0", -] - -[[package]] -name = "ntapi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" -dependencies = [ - "winapi", -] - -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - -[[package]] -name = "num-bigint" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "num_enum" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" -dependencies = [ - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", - "objc_exception", -] - -[[package]] -name = "objc-foundation" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" -dependencies = [ - "block", - "objc", - "objc_id", -] - -[[package]] -name = "objc-sys" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c71324e4180d0899963fc83d9d241ac39e699609fc1025a850aadac8257459" - -[[package]] -name = "objc2" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d" -dependencies = [ - "objc-sys", - "objc2-encode", -] - -[[package]] -name = "objc2-encode" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666" - -[[package]] -name = "objc_exception" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" -dependencies = [ - "cc", -] - -[[package]] -name = "objc_id" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" -dependencies = [ - "objc", -] - -[[package]] -name = "object" -version = "0.32.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "oneshot" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f6640c6bda7731b1fdbab747981a0f896dd1fedaf9f4a53fa237a04a84431f4" -dependencies = [ - "loom", -] - -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - -[[package]] -name = "open" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2078c0039e6a54a0c42c28faa984e115fb4c2d5bf2208f77d1961002df8576f8" -dependencies = [ - "pathdiff", - "windows-sys 0.42.0", -] - -[[package]] -name = "openssl" -version = "0.10.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" -dependencies = [ - "bitflags 2.5.0", - "cfg-if", - "foreign-types 0.3.2", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-src" -version = "300.2.3+3.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cff92b6f71555b61bb9315f7c64da3ca43d87531622120fea0195fc761b4843" -dependencies = [ - "cc", -] - -[[package]] -name = "openssl-sys" -version = "0.9.101" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" -dependencies = [ - "cc", - "libc", - "openssl-src", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "os_pipe" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57119c3b893986491ec9aa85056780d3a0f3cf4da7cc09dd3650dbd6c6738fb9" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - -[[package]] -name = "ownedbytes" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a059efb063b8f425b948e042e6b9bd85edfe60e913630ed727b23e2dfcc558" -dependencies = [ - "stable_deref_trait", -] - -[[package]] -name = "pango" -version = "0.15.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e4045548659aee5313bde6c582b0d83a627b7904dd20dc2d9ef0895d414e4f" -dependencies = [ - "bitflags 1.3.2", - "glib", - "libc", - "once_cell", - "pango-sys", -] - -[[package]] -name = "pango-sys" -version = "0.15.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2a00081cde4661982ed91d80ef437c20eacaf6aa1a5962c0279ae194662c3aa" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps 6.2.2", -] - -[[package]] -name = "parking" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" - -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.6", -] - -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core 0.9.9", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall 0.2.16", - "smallvec", - "winapi", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall 0.4.1", - "smallvec", - "windows-targets 0.48.5", -] - -[[package]] -name = "parse-zoneinfo" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" -dependencies = [ - "regex", -] - -[[package]] -name = "password-hash" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" -dependencies = [ - "base64ct", - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "pathdiff" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" - -[[package]] -name = "pbkdf2" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" -dependencies = [ - "digest", - "hmac", - "password-hash", - "sha2", -] - -[[package]] -name = "pbkdf2" -version = "0.12.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" -dependencies = [ - "digest", - "hmac", -] - -[[package]] -name = "pem" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" -dependencies = [ - "base64 0.13.1", -] - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "pest" -version = "2.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f8023d0fb78c8e03784ea1c7f3fa36e68a723138990b8d5a47d916b651e7a8" -dependencies = [ - "memchr", - "thiserror", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0d24f72393fd16ab6ac5738bc33cdb6a9aa73f8b902e8fe29cf4e67d7dd1026" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc17e2a6c7d0a492f0158d7a4bd66cc17280308bbaff78d5bef566dca35ab80" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "pest_meta" -version = "2.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "934cd7631c050f4674352a6e835d5f6711ffbfb9345c2fc0107155ac495ae293" -dependencies = [ - "once_cell", - "pest", - "sha2", -] - -[[package]] -name = "petgraph" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" -dependencies = [ - "fixedbitset", - "indexmap 2.2.6", -] - -[[package]] -name = "phf" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" -dependencies = [ - "phf_macros 0.8.0", - "phf_shared 0.8.0", - "proc-macro-hack", -] - -[[package]] -name = "phf" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" -dependencies = [ - "phf_shared 0.10.0", -] - -[[package]] -name = "phf" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" -dependencies = [ - "phf_macros 0.11.2", - "phf_shared 0.11.2", -] - -[[package]] -name = "phf_codegen" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" -dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", -] - -[[package]] -name = "phf_codegen" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" -dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", -] - -[[package]] -name = "phf_codegen" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" -dependencies = [ - "phf_generator 0.11.2", - "phf_shared 0.11.2", -] - -[[package]] -name = "phf_generator" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" -dependencies = [ - "phf_shared 0.8.0", - "rand 0.7.3", -] - -[[package]] -name = "phf_generator" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" -dependencies = [ - "phf_shared 0.10.0", - "rand 0.8.5", -] - -[[package]] -name = "phf_generator" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" -dependencies = [ - "phf_shared 0.11.2", - "rand 0.8.5", -] - -[[package]] -name = "phf_macros" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" -dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", - "proc-macro-hack", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "phf_macros" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" -dependencies = [ - "phf_generator 0.11.2", - "phf_shared 0.11.2", - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "phf_shared" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" -dependencies = [ - "siphasher", -] - -[[package]] -name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher", -] - -[[package]] -name = "phf_shared" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pin-project" -version = "1.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkg-config" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" - -[[package]] -name = "plist" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9d34169e64b3c7a80c8621a48adaf44e0cf62c78a9b25dd9dd35f1881a17cf9" -dependencies = [ - "base64 0.21.7", - "indexmap 2.2.6", - "line-wrap", - "quick-xml", - "serde", - "time", -] - -[[package]] -name = "png" -version = "0.17.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" -dependencies = [ - "bitflags 1.3.2", - "crc32fast", - "fdeflate", - "flate2", - "miniz_oxide", -] - -[[package]] -name = "polyval" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" -dependencies = [ - "cfg-if", - "cpufeatures", - "opaque-debug", - "universal-hash", -] - -[[package]] -name = "postgrest" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a966c650b47a064e7082170b4be74fca08c088d893244fc4b70123e3c1f3ee7" -dependencies = [ - "reqwest", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - -[[package]] -name = "precomputed-hash" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" - -[[package]] -name = "prettyplease" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d3928fb5db768cb86f891ff014f0144589297e3c6a1aba6ed7cecfdace270c7" -dependencies = [ - "proc-macro2", - "syn 2.0.55", -] - -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - -[[package]] -name = "proc-macro-crate" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" -dependencies = [ - "toml_edit 0.21.1", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" -dependencies = [ - "proc-macro2", - "quote", -] - -[[package]] -name = "proc-macro-error2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" -dependencies = [ - "proc-macro-error-attr2", - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - -[[package]] -name = "proc-macro2" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "prost" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "146c289cda302b98a28d40c8b3b90498d6e526dd24ac2ecea73e4e491685b94a" -dependencies = [ - "bytes", - "prost-derive 0.12.3", -] - -[[package]] -name = "prost" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f" -dependencies = [ - "bytes", - "prost-derive 0.13.3", -] - -[[package]] -name = "prost-build" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55e02e35260070b6f716a2423c2ff1c3bb1642ddca6f99e1f26d06268a0e2d2" -dependencies = [ - "bytes", - "heck 0.4.1", - "itertools 0.10.5", - "log", - "multimap", - "once_cell", - "petgraph", - "prettyplease", - "prost 0.12.3", - "prost-types", - "regex", - "syn 2.0.55", - "tempfile", - "which", -] - -[[package]] -name = "prost-derive" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efb6c9a1dd1def8e2124d17e83a20af56f1570d6c2d2bd9e266ccb768df3840e" -dependencies = [ - "anyhow", - "itertools 0.10.5", - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "prost-derive" -version = "0.13.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" -dependencies = [ - "anyhow", - "itertools 0.12.1", - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "prost-types" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193898f59edcf43c26227dcd4c8427f00d99d61e95dcde58dabd49fa291d470e" -dependencies = [ - "prost 0.12.3", -] - -[[package]] -name = "protobuf" -version = "2.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" - -[[package]] -name = "protobuf-codegen" -version = "2.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "033460afb75cf755fcfc16dfaed20b86468082a2ea24e05ac35ab4a099a017d6" -dependencies = [ - "protobuf", -] - -[[package]] -name = "protoc" -version = "2.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0218039c514f9e14a5060742ecd50427f8ac4f85a6dc58f2ddb806e318c55ee" -dependencies = [ - "log", - "which", -] - -[[package]] -name = "protoc-bin-vendored" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "005ca8623e5633e298ad1f917d8be0a44bcf406bf3cde3b80e63003e49a3f27d" -dependencies = [ - "protoc-bin-vendored-linux-aarch_64", - "protoc-bin-vendored-linux-ppcle_64", - "protoc-bin-vendored-linux-x86_32", - "protoc-bin-vendored-linux-x86_64", - "protoc-bin-vendored-macos-x86_64", - "protoc-bin-vendored-win32", -] - -[[package]] -name = "protoc-bin-vendored-linux-aarch_64" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb9fc9cce84c8694b6ea01cc6296617b288b703719b725b8c9c65f7c5874435" - -[[package]] -name = "protoc-bin-vendored-linux-ppcle_64" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d2a07dcf7173a04d49974930ccbfb7fd4d74df30ecfc8762cf2f895a094516" - -[[package]] -name = "protoc-bin-vendored-linux-x86_32" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54fef0b04fcacba64d1d80eed74a20356d96847da8497a59b0a0a436c9165b0" - -[[package]] -name = "protoc-bin-vendored-linux-x86_64" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8782f2ce7d43a9a5c74ea4936f001e9e8442205c244f7a3d4286bd4c37bc924" - -[[package]] -name = "protoc-bin-vendored-macos-x86_64" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5de656c7ee83f08e0ae5b81792ccfdc1d04e7876b1d9a38e6876a9e09e02537" - -[[package]] -name = "protoc-bin-vendored-win32" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9653c3ed92974e34c5a6e0a510864dab979760481714c172e0a34e437cb98804" - -[[package]] -name = "protoc-rust" -version = "2.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f8a182bb17c485f20bdc4274a8c39000a61024cfe461c799b50fec77267838" -dependencies = [ - "protobuf", - "protobuf-codegen", - "protoc", - "tempfile", -] - -[[package]] -name = "psl-types" -version = "2.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" - -[[package]] -name = "ptr_meta" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" -dependencies = [ - "ptr_meta_derive", -] - -[[package]] -name = "ptr_meta_derive" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "publicsuffix" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96a8c1bda5ae1af7f99a2962e49df150414a43d62404644d98dd5c3a93d07457" -dependencies = [ - "idna 0.3.0", - "psl-types", -] - -[[package]] -name = "quanta" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5167a477619228a0b284fac2674e3c388cba90631d7b7de620e6f1fcd08da5" -dependencies = [ - "crossbeam-utils", - "libc", - "once_cell", - "raw-cpuid", - "wasi 0.11.0+wasi-snapshot-preview1", - "web-sys", - "winapi", -] - -[[package]] -name = "quick-xml" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" -dependencies = [ - "memchr", -] - -[[package]] -name = "quote" -version = "1.0.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r2d2" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93" -dependencies = [ - "log", - "parking_lot 0.12.1", - "scheduled-thread-pool", -] - -[[package]] -name = "radium" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" - -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", - "rand_pcg", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.12", -] - -[[package]] -name = "rand_distr" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" -dependencies = [ - "num-traits", - "rand 0.8.5", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_pcg" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "raw-cpuid" -version = "11.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb9ee317cfe3fbd54b36a511efc1edd42e216903c9cd575e686dd68a2ba90d8d" -dependencies = [ - "bitflags 2.5.0", -] - -[[package]] -name = "raw-window-handle" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" - -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "redox_syscall" -version = "0.1.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" - -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_users" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" -dependencies = [ - "getrandom 0.2.12", - "libredox", - "thiserror", -] - -[[package]] -name = "regex" -version = "1.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata 0.4.6", - "regex-syntax 0.8.2", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", -] - -[[package]] -name = "regex-automata" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax 0.8.2", -] - -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - -[[package]] -name = "regex-syntax" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" - -[[package]] -name = "rend" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" -dependencies = [ - "bytecheck", -] - -[[package]] -name = "reqwest" -version = "0.11.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" -dependencies = [ - "base64 0.21.7", - "bytes", - "cookie", - "cookie_store", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-rustls", - "hyper-tls", - "ipnet", - "js-sys", - "log", - "mime", - "mime_guess", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls", - "rustls-pemfile", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "system-configuration", - "tokio", - "tokio-native-tls", - "tokio-rustls", - "tokio-util", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", - "webpki-roots", - "winreg 0.50.0", -] - -[[package]] -name = "rfd" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0149778bd99b6959285b0933288206090c50e2327f47a9c463bfdbf45c8823ea" -dependencies = [ - "block", - "dispatch", - "glib-sys", - "gobject-sys", - "gtk-sys", - "js-sys", - "lazy_static", - "log", - "objc", - "objc-foundation", - "objc_id", - "raw-window-handle", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "windows 0.37.0", -] - -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted 0.7.1", - "web-sys", - "winapi", -] - -[[package]] -name = "ring" -version = "0.17.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.12", - "libc", - "spin 0.9.8", - "untrusted 0.9.0", - "windows-sys 0.52.0", -] - -[[package]] -name = "rkyv" -version = "0.7.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cba464629b3394fc4dbc6f940ff8f5b4ff5c7aef40f29166fd4ad12acbc99c0" -dependencies = [ - "bitvec", - "bytecheck", - "bytes", - "hashbrown 0.12.3", - "ptr_meta", - "rend", - "rkyv_derive", - "seahash", - "tinyvec", - "uuid", -] - -[[package]] -name = "rkyv_derive" -version = "0.7.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7dddfff8de25e6f62b9d64e6e432bf1c6736c57d20323e15ee10435fbda7c65" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "rocksdb" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bd13e55d6d7b8cd0ea569161127567cd587676c99f4472f779a0279aa60a7a7" -dependencies = [ - "libc", - "librocksdb-sys", -] - -[[package]] -name = "rust-stemmers" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e46a2036019fdb888131db7a4c847a1063a7493f971ed94ea82c67eada63ca54" -dependencies = [ - "serde", - "serde_derive", -] - -[[package]] -name = "rust_decimal" -version = "1.36.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b082d80e3e3cc52b2ed634388d436fe1f4de6af5786cc2de9ba9737527bdf555" -dependencies = [ - "arrayvec", - "borsh", - "bytes", - "num-traits", - "rand 0.8.5", - "rkyv", - "serde", - "serde_json", -] - -[[package]] -name = "rust_decimal_macros" -version = "1.34.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e418701588729bef95e7a655f2b483ad64bb97c46e8e79fde83efd92aaab6d82" -dependencies = [ - "quote", - "rust_decimal", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" - -[[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - -[[package]] -name = "rustix" -version = "0.38.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" -dependencies = [ - "bitflags 2.5.0", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustls" -version = "0.21.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" -dependencies = [ - "log", - "ring 0.17.8", - "rustls-webpki", - "sct", -] - -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.7", -] - -[[package]] -name = "rustls-webpki" -version = "0.101.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" -dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", -] - -[[package]] -name = "rustversion" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" - -[[package]] -name = "rusty-money" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b28f881005eac7ad8d46b6f075da5f322bd7f4f83a38720fc069694ddadd683" -dependencies = [ - "rust_decimal", - "rust_decimal_macros", -] - -[[package]] -name = "ryu" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "sanitize-filename" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ed72fbaf78e6f2d41744923916966c4fbe3d7c74e3037a8ee482f1115572603" -dependencies = [ - "lazy_static", - "regex", -] - -[[package]] -name = "schannel" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "scheduled-thread-pool" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" -dependencies = [ - "parking_lot 0.12.1", -] - -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "scraper" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c95a930e03325234c18c7071fd2b60118307e025d6fff3e12745ffbf63a3d29c" -dependencies = [ - "ahash 0.8.11", - "cssparser 0.31.2", - "ego-tree", - "getopts", - "html5ever", - "once_cell", - "selectors 0.25.0", - "smallvec", - "tendril", -] - -[[package]] -name = "scraper" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585480e3719b311b78a573db1c9d9c4c1f8010c2dee4cc59c2efe58ea4dbc3e1" -dependencies = [ - "ahash 0.8.11", - "cssparser 0.31.2", - "ego-tree", - "getopts", - "html5ever", - "once_cell", - "selectors 0.25.0", - "tendril", -] - -[[package]] -name = "sct" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" -dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", -] - -[[package]] -name = "seahash" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" - -[[package]] -name = "security-framework" -version = "2.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "selectors" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" -dependencies = [ - "bitflags 1.3.2", - "cssparser 0.27.2", - "derive_more", - "fxhash", - "log", - "matches", - "phf 0.8.0", - "phf_codegen 0.8.0", - "precomputed-hash", - "servo_arc 0.1.1", - "smallvec", - "thin-slice", -] - -[[package]] -name = "selectors" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb30575f3638fc8f6815f448d50cb1a2e255b0897985c8c59f4d37b72a07b06" -dependencies = [ - "bitflags 2.5.0", - "cssparser 0.31.2", - "derive_more", - "fxhash", - "log", - "new_debug_unreachable", - "phf 0.10.1", - "phf_codegen 0.10.0", - "precomputed-hash", - "servo_arc 0.3.0", - "smallvec", -] - -[[package]] -name = "semver" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" -dependencies = [ - "serde", -] - -[[package]] -name = "serde" -version = "1.0.210" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.210" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "serde_derive_internals" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e578a843d40b4189a4d66bba51d7684f57da5bd7c304c64e14bd63efbef49509" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "serde_json" -version = "1.0.128" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" -dependencies = [ - "indexmap 2.2.6", - "itoa 1.0.10", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "serde_repr" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2e6b945e9d3df726b65d6ee24060aff8e3533d431f677a9695db04eff9dfdb" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "serde_spanned" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa 1.0.10", - "ryu", - "serde", -] - -[[package]] -name = "serde_with" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee80b0e361bbf88fd2f6e242ccd19cfda072cb0faa6ae694ecee08199938569a" -dependencies = [ - "base64 0.21.7", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.2.6", - "serde", - "serde_derive", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6561dc161a9224638a31d876ccdfefbc1df91d3f3a8342eddb35f055d48c7655" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "serialize-to-javascript" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9823f2d3b6a81d98228151fdeaf848206a7855a7a042bbf9bf870449a66cafb" -dependencies = [ - "serde", - "serde_json", - "serialize-to-javascript-impl", -] - -[[package]] -name = "serialize-to-javascript-impl" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "servo_arc" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" -dependencies = [ - "nodrop", - "stable_deref_trait", -] - -[[package]] -name = "servo_arc" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d036d71a959e00c77a63538b90a6c2390969f9772b096ea837205c6bd0491a44" -dependencies = [ - "stable_deref_trait", -] - -[[package]] -name = "sha1" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha1_smol" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" - -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shared-entity" -version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=ea131f0baab67defe7591067357eced490072372#ea131f0baab67defe7591067357eced490072372" -dependencies = [ - "anyhow", - "app-error", - "appflowy-ai-client", - "bytes", - "chrono", - "collab-entity", - "database-entity", - "futures", - "gotrue-entity", - "infra", - "log", - "pin-project", - "reqwest", - "serde", - "serde_json", - "serde_repr", - "thiserror", - "tracing", - "uuid", - "validator 0.19.0", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" -dependencies = [ - "libc", -] - -[[package]] -name = "simd-adler32" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" - -[[package]] -name = "simdutf8" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" - -[[package]] -name = "similar" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad1d488a557b235fc46dae55512ffbfc429d2482b08b4d9435ab07384ca8aec" - -[[package]] -name = "similar" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21" - -[[package]] -name = "simple_asn1" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" -dependencies = [ - "num-bigint", - "num-traits", - "thiserror", - "time", -] - -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - -[[package]] -name = "sketches-ddsketch" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85636c14b73d81f541e525f585c0a2109e6744e1565b5c1668e31c70c10ed65c" -dependencies = [ - "serde", -] - -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - -[[package]] -name = "slug" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bd94acec9c8da640005f8e135a39fc0372e74535e6b368b7a04b875f784c8c4" -dependencies = [ - "deunicode", - "wasm-bindgen", -] - -[[package]] -name = "smallstr" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63b1aefdf380735ff8ded0b15f31aab05daf1f70216c01c02a12926badd1df9d" -dependencies = [ - "smallvec", -] - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "socket2" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "soup2" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b4d76501d8ba387cf0fefbe055c3e0a59891d09f0f995ae4e4b16f6b60f3c0" -dependencies = [ - "bitflags 1.3.2", - "gio", - "glib", - "libc", - "once_cell", - "soup2-sys", -] - -[[package]] -name = "soup2-sys" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "009ef427103fcb17f802871647a7fa6c60cbb654b4c4e4c0ac60a31c5f6dc9cf" -dependencies = [ - "bitflags 1.3.2", - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps 5.0.0", -] - -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" - -[[package]] -name = "state" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b" -dependencies = [ - "loom", -] - -[[package]] -name = "string_cache" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" -dependencies = [ - "new_debug_unreachable", - "once_cell", - "parking_lot 0.12.1", - "phf_shared 0.10.0", - "precomputed-hash", - "serde", -] - -[[package]] -name = "string_cache_codegen" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" -dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", - "proc-macro2", - "quote", -] - -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "strum" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" - -[[package]] -name = "strum_macros" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec" -dependencies = [ - "heck 0.3.3", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "strum_macros" -version = "0.25.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.55", -] - -[[package]] -name = "strum_macros" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.55", -] - -[[package]] -name = "subtle" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn_derive" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - -[[package]] -name = "synstructure" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "sysinfo" -version = "0.30.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c385888ef380a852a16209afc8cfad22795dd8873d69c9a14d2e2088f118d18" -dependencies = [ - "cfg-if", - "core-foundation-sys", - "libc", - "ntapi", - "once_cell", - "rayon", - "windows 0.52.0", -] - -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "system-deps" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18db855554db7bd0e73e06cf7ba3df39f97812cb11d3f75e71c39bf45171797e" -dependencies = [ - "cfg-expr 0.9.1", - "heck 0.3.3", - "pkg-config", - "toml 0.5.11", - "version-compare 0.0.11", -] - -[[package]] -name = "system-deps" -version = "6.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" -dependencies = [ - "cfg-expr 0.15.7", - "heck 0.5.0", - "pkg-config", - "toml 0.8.12", - "version-compare 0.2.0", -] - -[[package]] -name = "tagptr" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" - -[[package]] -name = "tantivy" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8d0582f186c0a6d55655d24543f15e43607299425c5ad8352c242b914b31856" -dependencies = [ - "aho-corasick", - "arc-swap", - "base64 0.22.1", - "bitpacking", - "byteorder", - "census", - "crc32fast", - "crossbeam-channel", - "downcast-rs", - "fastdivide", - "fnv", - "fs4", - "htmlescape", - "itertools 0.12.1", - "levenshtein_automata", - "log", - "lru", - "lz4_flex", - "measure_time", - "memmap2", - "num_cpus", - "once_cell", - "oneshot", - "rayon", - "regex", - "rust-stemmers", - "rustc-hash", - "serde", - "serde_json", - "sketches-ddsketch", - "smallvec", - "tantivy-bitpacker", - "tantivy-columnar", - "tantivy-common", - "tantivy-fst", - "tantivy-query-grammar", - "tantivy-stacker", - "tantivy-tokenizer-api", - "tempfile", - "thiserror", - "time", - "uuid", - "winapi", -] - -[[package]] -name = "tantivy-bitpacker" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284899c2325d6832203ac6ff5891b297fc5239c3dc754c5bc1977855b23c10df" -dependencies = [ - "bitpacking", -] - -[[package]] -name = "tantivy-columnar" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12722224ffbe346c7fec3275c699e508fd0d4710e629e933d5736ec524a1f44e" -dependencies = [ - "downcast-rs", - "fastdivide", - "itertools 0.12.1", - "serde", - "tantivy-bitpacker", - "tantivy-common", - "tantivy-sstable", - "tantivy-stacker", -] - -[[package]] -name = "tantivy-common" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8019e3cabcfd20a1380b491e13ff42f57bb38bf97c3d5fa5c07e50816e0621f4" -dependencies = [ - "async-trait", - "byteorder", - "ownedbytes", - "serde", - "time", -] - -[[package]] -name = "tantivy-fst" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d60769b80ad7953d8a7b2c70cdfe722bbcdcac6bccc8ac934c40c034d866fc18" -dependencies = [ - "byteorder", - "regex-syntax 0.8.2", - "utf8-ranges", -] - -[[package]] -name = "tantivy-query-grammar" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "847434d4af57b32e309f4ab1b4f1707a6c566656264caa427ff4285c4d9d0b82" -dependencies = [ - "nom", -] - -[[package]] -name = "tantivy-sstable" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c69578242e8e9fc989119f522ba5b49a38ac20f576fc778035b96cc94f41f98e" -dependencies = [ - "tantivy-bitpacker", - "tantivy-common", - "tantivy-fst", - "zstd 0.13.2", -] - -[[package]] -name = "tantivy-stacker" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56d6ff5591fc332739b3ce7035b57995a3ce29a93ffd6012660e0949c956ea8" -dependencies = [ - "murmurhash32", - "rand_distr", - "tantivy-common", -] - -[[package]] -name = "tantivy-tokenizer-api" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0dcade25819a89cfe6f17d932c9cedff11989936bf6dd4f336d50392053b04" -dependencies = [ - "serde", -] - -[[package]] -name = "tao" -version = "0.16.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22205b267a679ca1c590b9f178488d50981fc3e48a1b91641ae31593db875ce" -dependencies = [ - "bitflags 1.3.2", - "cairo-rs", - "cc", - "cocoa", - "core-foundation", - "core-graphics 0.22.3", - "crossbeam-channel", - "dispatch", - "gdk", - "gdk-pixbuf", - "gdk-sys", - "gdkwayland-sys", - "gdkx11-sys", - "gio", - "glib", - "glib-sys", - "gtk", - "image", - "instant", - "jni", - "lazy_static", - "libc", - "log", - "ndk", - "ndk-context", - "ndk-sys", - "objc", - "once_cell", - "parking_lot 0.12.1", - "png", - "raw-window-handle", - "scopeguard", - "serde", - "tao-macros", - "unicode-segmentation", - "uuid", - "windows 0.39.0", - "windows-implement", - "x11-dl", -] - -[[package]] -name = "tao-macros" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec114582505d158b669b136e6851f85840c109819d77c42bb7c0709f727d18c2" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "tap" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" - -[[package]] -name = "tar" -version = "0.4.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" -dependencies = [ - "filetime", - "libc", - "xattr", -] - -[[package]] -name = "target-lexicon" -version = "0.12.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" - -[[package]] -name = "tauri" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f078117725e36d55d29fafcbb4b1e909073807ca328ae8deb8c0b3843aac0fed" -dependencies = [ - "anyhow", - "cocoa", - "dirs-next", - "dunce", - "embed_plist", - "encoding_rs", - "flate2", - "futures-util", - "glib", - "glob", - "gtk", - "heck 0.4.1", - "http", - "ignore", - "objc", - "once_cell", - "open", - "percent-encoding", - "rand 0.8.5", - "raw-window-handle", - "regex", - "rfd", - "semver", - "serde", - "serde_json", - "serde_repr", - "serialize-to-javascript", - "state", - "tar", - "tauri-macros", - "tauri-runtime", - "tauri-runtime-wry", - "tauri-utils", - "tempfile", - "thiserror", - "tokio", - "url", - "uuid", - "webkit2gtk", - "webview2-com", - "windows 0.39.0", -] - -[[package]] -name = "tauri-build" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9914a4715e0b75d9f387a285c7e26b5bbfeb1249ad9f842675a82481565c532" -dependencies = [ - "anyhow", - "cargo_toml", - "dirs-next", - "heck 0.4.1", - "json-patch", - "semver", - "serde", - "serde_json", - "tauri-utils", - "tauri-winres", - "walkdir", -] - -[[package]] -name = "tauri-codegen" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1554c5857f65dbc377cefb6b97c8ac77b1cb2a90d30d3448114d5d6b48a77fc" -dependencies = [ - "base64 0.21.7", - "brotli", - "ico", - "json-patch", - "plist", - "png", - "proc-macro2", - "quote", - "regex", - "semver", - "serde", - "serde_json", - "sha2", - "tauri-utils", - "thiserror", - "time", - "uuid", - "walkdir", -] - -[[package]] -name = "tauri-macros" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "277abf361a3a6993ec16bcbb179de0d6518009b851090a01adfea12ac89fa875" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "syn 1.0.109", - "tauri-codegen", - "tauri-utils", -] - -[[package]] -name = "tauri-plugin-deep-link" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4536f5f6602e8fdfaa7b3b185076c2a0704f8eb7015f4e58461eb483ec3ed1f8" -dependencies = [ - "dirs", - "interprocess", - "log", - "objc2", - "once_cell", - "tauri-utils", - "windows-sys 0.48.0", - "winreg 0.50.0", -] - -[[package]] -name = "tauri-runtime" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2d0652aa2891ff3e9caa2401405257ea29ab8372cce01f186a5825f1bd0e76" -dependencies = [ - "gtk", - "http", - "http-range", - "rand 0.8.5", - "raw-window-handle", - "serde", - "serde_json", - "tauri-utils", - "thiserror", - "url", - "uuid", - "webview2-com", - "windows 0.39.0", -] - -[[package]] -name = "tauri-runtime-wry" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "067c56fc153b3caf406d7cd6de4486c80d1d66c0f414f39e94cb2f5543f6445f" -dependencies = [ - "arboard", - "cocoa", - "gtk", - "percent-encoding", - "rand 0.8.5", - "raw-window-handle", - "tauri-runtime", - "tauri-utils", - "uuid", - "webkit2gtk", - "webview2-com", - "windows 0.39.0", - "wry", -] - -[[package]] -name = "tauri-utils" -version = "1.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75ad0bbb31fccd1f4c56275d0a5c3abdf1f59999f72cb4ef8b79b4ed42082a21" -dependencies = [ - "brotli", - "ctor", - "dunce", - "glob", - "heck 0.4.1", - "html5ever", - "infer", - "json-patch", - "kuchikiki", - "log", - "memchr", - "phf 0.11.2", - "proc-macro2", - "quote", - "semver", - "serde", - "serde_json", - "serde_with", - "thiserror", - "url", - "walkdir", - "windows-version", -] - -[[package]] -name = "tauri-winres" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5993dc129e544393574288923d1ec447c857f3f644187f4fbf7d9a875fbfc4fb" -dependencies = [ - "embed-resource", - "toml 0.7.8", -] - -[[package]] -name = "tempfile" -version = "3.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" -dependencies = [ - "cfg-if", - "fastrand", - "rustix", - "windows-sys 0.52.0", -] - -[[package]] -name = "tendril" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" -dependencies = [ - "futf", - "mac", - "utf-8", -] - -[[package]] -name = "tera" -version = "1.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "970dff17c11e884a4a09bc76e3a17ef71e01bb13447a11e85226e254fe6d10b8" -dependencies = [ - "chrono", - "chrono-tz 0.8.6", - "globwalk", - "humansize", - "lazy_static", - "percent-encoding", - "pest", - "pest_derive", - "rand 0.8.5", - "regex", - "serde", - "serde_json", - "slug", - "unic-segment", -] - -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "terminal_size" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "thin-slice" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" - -[[package]] -name = "thiserror" -version = "1.0.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "thread-id" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fbf4c9d56b320106cd64fd024dadfa0be7cb4706725fc44a7d7ce952d820c1" -dependencies = [ - "libc", - "redox_syscall 0.1.57", - "winapi", -] - -[[package]] -name = "thread_local" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" -dependencies = [ - "cfg-if", - "once_cell", -] - -[[package]] -name = "tiff" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e" -dependencies = [ - "flate2", - "jpeg-decoder", - "weezl", -] - -[[package]] -name = "time" -version = "0.3.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" -dependencies = [ - "deranged", - "itoa 1.0.10", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" - -[[package]] -name = "time-macros" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "tinystr" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "to_method" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c4ceeeca15c8384bbc3e011dbd8fccb7f068a440b752b7d9b32ceb0ca0e2e8" - -[[package]] -name = "tokio" -version = "1.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" -dependencies = [ - "backtrace", - "bytes", - "libc", - "mio", - "num_cpus", - "parking_lot 0.12.1", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "tracing", - "windows-sys 0.48.0", -] - -[[package]] -name = "tokio-macros" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-retry" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f57eb36ecbe0fc510036adff84824dd3c24bb781e21bfa67b69d556aa85214f" -dependencies = [ - "pin-project", - "rand 0.8.5", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-stream" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", - "tokio-util", -] - -[[package]] -name = "tokio-tungstenite" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" -dependencies = [ - "futures-util", - "log", - "native-tls", - "tokio", - "tokio-native-tls", - "tungstenite", -] - -[[package]] -name = "tokio-util" -version = "0.7.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" -dependencies = [ - "bytes", - "futures-core", - "futures-io", - "futures-sink", - "futures-util", - "hashbrown 0.14.3", - "pin-project-lite", - "slab", - "tokio", -] - -[[package]] -name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde", -] - -[[package]] -name = "toml" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.19.15", -] - -[[package]] -name = "toml" -version = "0.8.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.22.9", -] - -[[package]] -name = "toml_datetime" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap 2.2.6", - "serde", - "serde_spanned", - "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" -dependencies = [ - "indexmap 2.2.6", - "toml_datetime", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.22.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" -dependencies = [ - "indexmap 2.2.6", - "serde", - "serde_spanned", - "toml_datetime", - "winnow 0.6.5", -] - -[[package]] -name = "tower-service" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" - -[[package]] -name = "tracing" -version = "0.1.41" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" -dependencies = [ - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-appender" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf" -dependencies = [ - "crossbeam-channel", - "thiserror", - "time", - "tracing-subscriber", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "tracing-bunyan-formatter" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d637245a0d8774bd48df6482e086c59a8b5348a910c3b0579354045a9d82411" -dependencies = [ - "ahash 0.8.11", - "gethostname 0.2.3", - "log", - "serde", - "serde_json", - "time", - "tracing", - "tracing-core", - "tracing-log 0.1.4", - "tracing-subscriber", -] - -[[package]] -name = "tracing-core" -version = "0.1.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-serde" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" -dependencies = [ - "serde", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex", - "serde", - "serde_json", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log 0.2.0", - "tracing-serde", -] - -[[package]] -name = "tracing-wasm" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4575c663a174420fa2d78f4108ff68f65bf2fbb7dd89f33749b6e826b3626e07" -dependencies = [ - "tracing", - "tracing-subscriber", - "wasm-bindgen", -] - -[[package]] -name = "tree_magic_mini" -version = "3.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ee137597cdb361b55a4746983e4ac1b35ab6024396a419944ad473bb915265" -dependencies = [ - "fnv", - "home", - "memchr", - "nom", - "once_cell", - "petgraph", -] - -[[package]] -name = "treediff" -version = "4.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d127780145176e2b5d16611cc25a900150e86e9fd79d3bde6ff3a37359c9cb5" -dependencies = [ - "serde_json", -] - -[[package]] -name = "triomphe" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859eb650cfee7434994602c3a68b25d77ad9e68c8a6cd491616ef86661382eb3" - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "tsify" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b26cf145f2f3b9ff84e182c448eaf05468e247f148cf3d2a7d67d78ff023a0" -dependencies = [ - "gloo-utils", - "serde", - "serde_json", - "tsify-macros", - "wasm-bindgen", -] - -[[package]] -name = "tsify-macros" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a94b0f0954b3e59bfc2c246b4c8574390d94a4ad4ad246aaf2fb07d7dfd3b47" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn 2.0.55", -] - -[[package]] -name = "tungstenite" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" -dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http", - "httparse", - "log", - "native-tls", - "rand 0.8.5", - "sha1", - "thiserror", - "url", - "utf-8", -] - -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "ucd-trie" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" - -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-segment" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" -dependencies = [ - "unic-ucd-segment", -] - -[[package]] -name = "unic-ucd-segment" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] - -[[package]] -name = "unicase" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" - -[[package]] -name = "unicode-id" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1b6def86329695390197b82c1e244a54a131ceb66c996f2088a3876e2ae083f" - -[[package]] -name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "unicode-normalization" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-segmentation" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" - -[[package]] -name = "unicode-width" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" - -[[package]] -name = "universal-hash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common", - "subtle", -] - -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" -dependencies = [ - "form_urlencoded", - "idna 0.5.0", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf-8" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" - -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - -[[package]] -name = "utf8-ranges" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcfc827f90e53a02eaef5e535ee14266c1d569214c6aa70133a624d8a3164ba" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "uuid" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" -dependencies = [ - "getrandom 0.2.12", - "serde", - "sha1_smol", - "wasm-bindgen", -] - -[[package]] -name = "validator" -version = "0.18.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db79c75af171630a3148bd3e6d7c4f42b6a9a014c2945bc5ed0020cbb8d9478e" -dependencies = [ - "idna 0.5.0", - "once_cell", - "regex", - "serde", - "serde_derive", - "serde_json", - "url", - "validator_derive 0.18.2", -] - -[[package]] -name = "validator" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0b4a29d8709210980a09379f27ee31549b73292c87ab9899beee1c0d3be6303" -dependencies = [ - "idna 1.0.3", - "once_cell", - "regex", - "serde", - "serde_derive", - "serde_json", - "url", - "validator_derive 0.19.0", -] - -[[package]] -name = "validator_derive" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df0bcf92720c40105ac4b2dda2a4ea3aa717d4d6a862cc217da653a4bd5c6b10" -dependencies = [ - "darling", - "once_cell", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "validator_derive" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bac855a2ce6f843beb229757e6e570a42e837bcb15e5f449dd48d5747d41bf77" -dependencies = [ - "darling", - "once_cell", - "proc-macro-error2", - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version-compare" -version = "0.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c18c859eead79d8b95d09e4678566e8d70105c4e7b251f707a03df32442661b" - -[[package]] -name = "version-compare" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "vswhom" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" -dependencies = [ - "libc", - "vswhom-sys", -] - -[[package]] -name = "vswhom-sys" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3b17ae1f6c8a2b28506cd96d412eebf83b4a0ff2cbefeeb952f2f9dfa44ba18" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.55", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.55", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.92" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" - -[[package]] -name = "wasm-streams" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "wasm-timer" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f" -dependencies = [ - "futures", - "js-sys", - "parking_lot 0.11.2", - "pin-utils", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "wayland-backend" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d50fa61ce90d76474c87f5fc002828d81b32677340112b4ef08079a9d459a40" -dependencies = [ - "cc", - "downcast-rs", - "rustix", - "scoped-tls", - "smallvec", - "wayland-sys", -] - -[[package]] -name = "wayland-client" -version = "0.31.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82fb96ee935c2cea6668ccb470fb7771f6215d1691746c2d896b447a00ad3f1f" -dependencies = [ - "bitflags 2.5.0", - "rustix", - "wayland-backend", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols" -version = "0.31.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" -dependencies = [ - "bitflags 2.5.0", - "wayland-backend", - "wayland-client", - "wayland-scanner", -] - -[[package]] -name = "wayland-protocols-wlr" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" -dependencies = [ - "bitflags 2.5.0", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-scanner", -] - -[[package]] -name = "wayland-scanner" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63b3a62929287001986fb58c789dce9b67604a397c15c611ad9f747300b6c283" -dependencies = [ - "proc-macro2", - "quick-xml", - "quote", -] - -[[package]] -name = "wayland-sys" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15a0c8eaff5216d07f226cb7a549159267f3467b289d9a2e52fd3ef5aae2b7af" -dependencies = [ - "dlib", - "log", - "pkg-config", -] - -[[package]] -name = "web-sys" -version = "0.3.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webkit2gtk" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8f859735e4a452aeb28c6c56a852967a8a76c8eb1cc32dbf931ad28a13d6370" -dependencies = [ - "bitflags 1.3.2", - "cairo-rs", - "gdk", - "gdk-sys", - "gio", - "gio-sys", - "glib", - "glib-sys", - "gobject-sys", - "gtk", - "gtk-sys", - "javascriptcore-rs", - "libc", - "once_cell", - "soup2", - "webkit2gtk-sys", -] - -[[package]] -name = "webkit2gtk-sys" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d76ca6ecc47aeba01ec61e480139dda143796abcae6f83bcddf50d6b5b1dcf3" -dependencies = [ - "atk-sys", - "bitflags 1.3.2", - "cairo-sys-rs", - "gdk-pixbuf-sys", - "gdk-sys", - "gio-sys", - "glib-sys", - "gobject-sys", - "gtk-sys", - "javascriptcore-rs-sys", - "libc", - "pango-sys", - "pkg-config", - "soup2-sys", - "system-deps 6.2.2", -] - -[[package]] -name = "webpki-roots" -version = "0.25.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" - -[[package]] -name = "webview2-com" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4a769c9f1a64a8734bde70caafac2b96cada12cd4aefa49196b3a386b8b4178" -dependencies = [ - "webview2-com-macros", - "webview2-com-sys", - "windows 0.39.0", - "windows-implement", -] - -[[package]] -name = "webview2-com-macros" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaebe196c01691db62e9e4ca52c5ef1e4fd837dcae27dae3ada599b5a8fd05ac" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "webview2-com-sys" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aac48ef20ddf657755fdcda8dfed2a7b4fc7e4581acce6fe9b88c3d64f29dee7" -dependencies = [ - "regex", - "serde", - "serde_json", - "thiserror", - "windows 0.39.0", - "windows-bindgen", - "windows-metadata", -] - -[[package]] -name = "weezl" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" - -[[package]] -name = "which" -version = "4.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57b543186b344cc61c85b5aab0d2e3adf4e0f99bc076eff9aa5927bcc0b8a647" -dependencies = [ - "windows_aarch64_msvc 0.37.0", - "windows_i686_gnu 0.37.0", - "windows_i686_msvc 0.37.0", - "windows_x86_64_gnu 0.37.0", - "windows_x86_64_msvc 0.37.0", -] - -[[package]] -name = "windows" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1c4bd0a50ac6020f65184721f758dba47bb9fbc2133df715ec74a237b26794a" -dependencies = [ - "windows-implement", - "windows_aarch64_msvc 0.39.0", - "windows_i686_gnu 0.39.0", - "windows_i686_msvc 0.39.0", - "windows_x86_64_gnu 0.39.0", - "windows_x86_64_msvc 0.39.0", -] - -[[package]] -name = "windows" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" -dependencies = [ - "windows-core", - "windows-targets 0.52.4", -] - -[[package]] -name = "windows-bindgen" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68003dbd0e38abc0fb85b939240f4bce37c43a5981d3df37ccbaaa981b47cb41" -dependencies = [ - "windows-metadata", - "windows-tokens", -] - -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.4", -] - -[[package]] -name = "windows-implement" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba01f98f509cb5dc05f4e5fc95e535f78260f15fea8fe1a8abdd08f774f1cee7" -dependencies = [ - "syn 1.0.109", - "windows-tokens", -] - -[[package]] -name = "windows-metadata" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ee5e275231f07c6e240d14f34e1b635bf1faa1c76c57cfd59a5cdb9848e4278" - -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.4", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" -dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", -] - -[[package]] -name = "windows-tokens" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f838de2fe15fe6bac988e74b798f26499a8b21a9d97edec321e79b28d1d7f597" - -[[package]] -name = "windows-version" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75aa004c988e080ad34aff5739c39d0312f4684699d6d71fc8a198d057b8b9b4" -dependencies = [ - "windows-targets 0.52.4", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" - -[[package]] -name = "windows_i686_gnu" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1" - -[[package]] -name = "windows_i686_gnu" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" - -[[package]] -name = "windows_i686_msvc" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c" - -[[package]] -name = "windows_i686_msvc" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" - -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dffa400e67ed5a4dd237983829e66475f0a4a26938c4b04c21baede6262215b8" -dependencies = [ - "memchr", -] - -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - -[[package]] -name = "winreg" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - -[[package]] -name = "wl-clipboard-rs" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b41773911497b18ca8553c3daaf8ec9fe9819caf93d451d3055f69de028adb" -dependencies = [ - "derive-new", - "libc", - "log", - "nix", - "os_pipe", - "tempfile", - "thiserror", - "tree_magic_mini", - "wayland-backend", - "wayland-client", - "wayland-protocols", - "wayland-protocols-wlr", -] - -[[package]] -name = "write16" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" - -[[package]] -name = "writeable" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" - -[[package]] -name = "wry" -version = "0.24.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ad85d0e067359e409fcb88903c3eac817c392e5d638258abfb3da5ad8ba6fc4" -dependencies = [ - "base64 0.13.1", - "block", - "cocoa", - "core-graphics 0.22.3", - "crossbeam-channel", - "dunce", - "gdk", - "gio", - "glib", - "gtk", - "html5ever", - "http", - "kuchikiki", - "libc", - "log", - "objc", - "objc_id", - "once_cell", - "serde", - "serde_json", - "sha2", - "soup2", - "tao", - "thiserror", - "url", - "webkit2gtk", - "webkit2gtk-sys", - "webview2-com", - "windows 0.39.0", - "windows-implement", -] - -[[package]] -name = "wyz" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" -dependencies = [ - "tap", -] - -[[package]] -name = "x11" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" -dependencies = [ - "libc", - "pkg-config", -] - -[[package]] -name = "x11-dl" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" -dependencies = [ - "libc", - "once_cell", - "pkg-config", -] - -[[package]] -name = "x11rb" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8f25ead8c7e4cba123243a6367da5d3990e0d3affa708ea19dce96356bd9f1a" -dependencies = [ - "gethostname 0.4.3", - "rustix", - "x11rb-protocol", -] - -[[package]] -name = "x11rb-protocol" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e63e71c4b8bd9ffec2c963173a4dc4cbde9ee96961d4fcb4429db9929b606c34" - -[[package]] -name = "xattr" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" -dependencies = [ - "libc", - "linux-raw-sys", - "rustix", -] - -[[package]] -name = "xz2" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" -dependencies = [ - "lzma-sys", -] - -[[package]] -name = "yoke" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" -dependencies = [ - "serde", - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.55", - "synstructure", -] - -[[package]] -name = "yrs" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81de5913bca29f43a1d12ca92a7b39a2945e9420e01602a7563917c7bfc60f70" -dependencies = [ - "arc-swap", - "async-lock", - "async-trait", - "dashmap 6.0.1", - "fastrand", - "serde", - "serde_json", - "smallstr", - "smallvec", - "thiserror", -] - -[[package]] -name = "zerocopy" -version = "0.7.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "zerofrom" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.55", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "zerovec" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.55", -] - -[[package]] -name = "zip" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" -dependencies = [ - "aes", - "byteorder", - "bzip2", - "constant_time_eq 0.1.5", - "crc32fast", - "crossbeam-utils", - "flate2", - "hmac", - "pbkdf2 0.11.0", - "sha1", - "time", - "zstd 0.11.2+zstd.1.5.2", -] - -[[package]] -name = "zip" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc5e4288ea4057ae23afc69a4472434a87a2495cafce6632fd1c4ec9f5cf3494" -dependencies = [ - "aes", - "arbitrary", - "bzip2", - "constant_time_eq 0.3.0", - "crc32fast", - "crossbeam-utils", - "deflate64", - "displaydoc", - "flate2", - "hmac", - "indexmap 2.2.6", - "lzma-rs", - "memchr", - "pbkdf2 0.12.2", - "rand 0.8.5", - "sha1", - "thiserror", - "time", - "zeroize", - "zopfli", - "zstd 0.13.2", -] - -[[package]] -name = "zip-extensions" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb0a99499b3497d765525c5d05e3ade9ca4a731c184365c19472c3fd6ba86341" -dependencies = [ - "zip 2.2.0", -] - -[[package]] -name = "zopfli" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5019f391bac5cf252e93bbcc53d039ffd62c7bfb7c150414d61369afe57e946" -dependencies = [ - "bumpalo", - "crc32fast", - "lockfree-object-pool", - "log", - "once_cell", - "simd-adler32", -] - -[[package]] -name = "zstd" -version = "0.11.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" -dependencies = [ - "zstd-safe 5.0.2+zstd.1.5.2", -] - -[[package]] -name = "zstd" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9" -dependencies = [ - "zstd-safe 7.2.0", -] - -[[package]] -name = "zstd-safe" -version = "5.0.2+zstd.1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" -dependencies = [ - "libc", - "zstd-sys", -] - -[[package]] -name = "zstd-safe" -version = "7.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa556e971e7b568dc775c136fc9de8c779b1c2fc3a63defaafadffdbd3181afa" -dependencies = [ - "zstd-sys", -] - -[[package]] -name = "zstd-sys" -version = "2.0.12+zstd.1.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a4e40c320c3cb459d9a9ff6de98cff88f4751ee9275d140e2be94a2b74e4c13" -dependencies = [ - "cc", - "pkg-config", -] diff --git a/frontend/appflowy_web_app/src-tauri/Cargo.toml b/frontend/appflowy_web_app/src-tauri/Cargo.toml deleted file mode 100644 index 56fa4bc799..0000000000 --- a/frontend/appflowy_web_app/src-tauri/Cargo.toml +++ /dev/null @@ -1,136 +0,0 @@ -[package] -name = "appflowy_tauri" -version = "0.0.0" -description = "A Tauri App" -authors = ["you"] -license = "" -repository = "" -edition = "2021" -rust-version = "1.57" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[build-dependencies] -tauri-build = { version = "1.5", features = [] } - -[workspace.dependencies] -anyhow = "1.0" -tracing = "0.1.40" -bytes = "1.5.0" -serde = "1.0" -serde_json = "1.0.108" -protobuf = { version = "2.28.0" } -diesel = { version = "2.1.0", features = [ - "sqlite", - "chrono", - "r2d2", - "serde_json", -] } -uuid = { version = "1.5.0", features = ["serde", "v4"] } -serde_repr = "0.1" -parking_lot = "0.12" -futures = "0.3.29" -tokio = "1.34.0" -tokio-stream = "0.1.14" -async-trait = "0.1.74" -chrono = { version = "0.4.31", default-features = false, features = ["clock"] } -yrs = "0.19.1" -# Please use the following script to update collab. -# Working directory: frontend -# -# To update the commit ID, run: -# scripts/tool/update_collab_rev.sh new_rev_id -# -# To switch to the local path, run: -# scripts/tool/update_collab_source.sh -# ⚠️⚠️⚠️️ -collab = { version = "0.2" } -collab-entity = { version = "0.2" } -collab-folder = { version = "0.2" } -collab-document = { version = "0.2" } -collab-database = { version = "0.2" } -collab-plugins = { version = "0.2" } -collab-user = { version = "0.2" } -collab-importer = { version = "0.1" } - -# Please using the following command to update the revision id -# Current directory: frontend -# Run the script: -# scripts/tool/update_client_api_rev.sh new_rev_id -# ⚠️⚠️⚠️️ -client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "ea131f0baab67defe7591067357eced490072372" } - -[dependencies] -serde_json.workspace = true -serde.workspace = true -tauri = { version = "1.5", features = [ - "dialog-all", - "clipboard-all", - "fs-all", - "shell-open", -] } -tauri-utils = "1.5.2" -bytes.workspace = true -tracing.workspace = true -lib-dispatch = { path = "../../rust-lib/lib-dispatch", features = [ - "use_serde", -] } -flowy-core = { path = "../../rust-lib/flowy-core", features = ["ts"] } -flowy-user = { path = "../../rust-lib/flowy-user", features = ["tauri_ts"] } -flowy-config = { path = "../../rust-lib/flowy-config", features = ["tauri_ts"] } -flowy-date = { path = "../../rust-lib/flowy-date", features = ["tauri_ts"] } -flowy-error = { path = "../../rust-lib/flowy-error", features = [ - "impl_from_sqlite", - "impl_from_dispatch_error", - "impl_from_appflowy_cloud", - "impl_from_reqwest", - "impl_from_serde", - "tauri_ts", -] } -flowy-document = { path = "../../rust-lib/flowy-document", features = [ - "tauri_ts", -] } -flowy-notification = { path = "../../rust-lib/flowy-notification", features = [ - "tauri_ts", -] } -flowy-ai = { path = "../../rust-lib/flowy-ai", features = ["tauri_ts"] } - -uuid = "1.5.0" -tauri-plugin-deep-link = "0.1.2" -dotenv = "0.15.0" -semver = "1.0.23" - -[features] -# by default Tauri runs in production mode -# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL -default = ["custom-protocol"] -# this feature is used used for production builds where `devPath` points to the filesystem -# DO NOT remove this -custom-protocol = ["tauri/custom-protocol"] - -[patch.crates-io] -# Please use the following script to update collab. -# Working directory: frontend -# -# To update the commit ID, run: -# scripts/tool/update_collab_rev.sh new_rev_id -# -# To switch to the local path, run: -# scripts/tool/update_collab_source.sh -# ⚠️⚠️⚠️️ -collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "67b31e4bcc64d81332c37efecb0a8618681f1db9" } -collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "67b31e4bcc64d81332c37efecb0a8618681f1db9" } -collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "67b31e4bcc64d81332c37efecb0a8618681f1db9" } -collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "67b31e4bcc64d81332c37efecb0a8618681f1db9" } -collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "67b31e4bcc64d81332c37efecb0a8618681f1db9" } -collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "67b31e4bcc64d81332c37efecb0a8618681f1db9" } -collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "67b31e4bcc64d81332c37efecb0a8618681f1db9" } -collab-importer = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "67b31e4bcc64d81332c37efecb0a8618681f1db9" } - - -# Working directory: frontend -# To update the commit ID, run: -# scripts/tool/update_local_ai_rev.sh new_rev_id -# ⚠️⚠️⚠️️ -appflowy-local-ai = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "6f064efe232268f8d396edbb4b84d57fbb640f13" } -appflowy-plugin = { version = "0.1", git = "https://github.com/AppFlowy-IO/AppFlowy-LocalAI", rev = "6f064efe232268f8d396edbb4b84d57fbb640f13" } diff --git a/frontend/appflowy_web_app/src-tauri/Info.plist b/frontend/appflowy_web_app/src-tauri/Info.plist deleted file mode 100644 index 25b430c049..0000000000 --- a/frontend/appflowy_web_app/src-tauri/Info.plist +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - CFBundleURLTypes - - - CFBundleURLName - - appflowy-flutter - CFBundleURLSchemes - - appflowy-flutter - - - - - \ No newline at end of file diff --git a/frontend/appflowy_web_app/src-tauri/build.rs b/frontend/appflowy_web_app/src-tauri/build.rs deleted file mode 100644 index 795b9b7c83..0000000000 --- a/frontend/appflowy_web_app/src-tauri/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - tauri_build::build() -} diff --git a/frontend/appflowy_web_app/src-tauri/env.development b/frontend/appflowy_web_app/src-tauri/env.development deleted file mode 100644 index 188835e3d0..0000000000 --- a/frontend/appflowy_web_app/src-tauri/env.development +++ /dev/null @@ -1,4 +0,0 @@ -APPFLOWY_CLOUD_ENV_APPFLOWY_CLOUD_BASE_URL=https://test.appflowy.cloud -APPFLOWY_CLOUD_ENV_APPFLOWY_CLOUD_WS_BASE_URL=wss://test.appflowy.cloud/ws/v1 -APPFLOWY_CLOUD_ENV_APPFLOWY_CLOUD_GOTRUE_URL=https://test.appflowy.cloud/gotrue -APPFLOWY_CLOUD_ENV_CLOUD_TYPE=2 diff --git a/frontend/appflowy_web_app/src-tauri/env.production b/frontend/appflowy_web_app/src-tauri/env.production deleted file mode 100644 index b03c328b84..0000000000 --- a/frontend/appflowy_web_app/src-tauri/env.production +++ /dev/null @@ -1,4 +0,0 @@ -APPFLOWY_CLOUD_ENV_APPFLOWY_CLOUD_BASE_URL=https://beta.appflowy.cloud -APPFLOWY_CLOUD_ENV_APPFLOWY_CLOUD_WS_BASE_URL=wss://beta.appflowy.cloud/ws/v1 -APPFLOWY_CLOUD_ENV_APPFLOWY_CLOUD_GOTRUE_URL=https://beta.appflowy.cloud/gotrue -APPFLOWY_CLOUD_ENV_CLOUD_TYPE=2 diff --git a/frontend/appflowy_web_app/src-tauri/icons/128x128.png b/frontend/appflowy_web_app/src-tauri/icons/128x128.png deleted file mode 100644 index 3a51041313..0000000000 Binary files a/frontend/appflowy_web_app/src-tauri/icons/128x128.png and /dev/null differ diff --git a/frontend/appflowy_web_app/src-tauri/icons/128x128@2x.png b/frontend/appflowy_web_app/src-tauri/icons/128x128@2x.png deleted file mode 100644 index 9076de3a4b..0000000000 Binary files a/frontend/appflowy_web_app/src-tauri/icons/128x128@2x.png and /dev/null differ diff --git a/frontend/appflowy_web_app/src-tauri/icons/32x32.png b/frontend/appflowy_web_app/src-tauri/icons/32x32.png deleted file mode 100644 index 6ae6683fef..0000000000 Binary files a/frontend/appflowy_web_app/src-tauri/icons/32x32.png and /dev/null differ diff --git a/frontend/appflowy_web_app/src-tauri/icons/Square107x107Logo.png b/frontend/appflowy_web_app/src-tauri/icons/Square107x107Logo.png deleted file mode 100644 index b08dcf7d21..0000000000 Binary files a/frontend/appflowy_web_app/src-tauri/icons/Square107x107Logo.png and /dev/null differ diff --git a/frontend/appflowy_web_app/src-tauri/icons/Square142x142Logo.png b/frontend/appflowy_web_app/src-tauri/icons/Square142x142Logo.png deleted file mode 100644 index f3e437b76e..0000000000 Binary files a/frontend/appflowy_web_app/src-tauri/icons/Square142x142Logo.png and /dev/null differ diff --git a/frontend/appflowy_web_app/src-tauri/icons/Square150x150Logo.png b/frontend/appflowy_web_app/src-tauri/icons/Square150x150Logo.png deleted file mode 100644 index 6a1dc04864..0000000000 Binary files a/frontend/appflowy_web_app/src-tauri/icons/Square150x150Logo.png and /dev/null differ diff --git a/frontend/appflowy_web_app/src-tauri/icons/Square284x284Logo.png b/frontend/appflowy_web_app/src-tauri/icons/Square284x284Logo.png deleted file mode 100644 index 2f2d9d6fe6..0000000000 Binary files a/frontend/appflowy_web_app/src-tauri/icons/Square284x284Logo.png and /dev/null differ diff --git a/frontend/appflowy_web_app/src-tauri/icons/Square30x30Logo.png b/frontend/appflowy_web_app/src-tauri/icons/Square30x30Logo.png deleted file mode 100644 index 46e3802c0b..0000000000 Binary files a/frontend/appflowy_web_app/src-tauri/icons/Square30x30Logo.png and /dev/null differ diff --git a/frontend/appflowy_web_app/src-tauri/icons/Square310x310Logo.png b/frontend/appflowy_web_app/src-tauri/icons/Square310x310Logo.png deleted file mode 100644 index 230b1abe58..0000000000 Binary files a/frontend/appflowy_web_app/src-tauri/icons/Square310x310Logo.png and /dev/null differ diff --git a/frontend/appflowy_web_app/src-tauri/icons/Square44x44Logo.png b/frontend/appflowy_web_app/src-tauri/icons/Square44x44Logo.png deleted file mode 100644 index ad188037a3..0000000000 Binary files a/frontend/appflowy_web_app/src-tauri/icons/Square44x44Logo.png and /dev/null differ diff --git a/frontend/appflowy_web_app/src-tauri/icons/Square71x71Logo.png b/frontend/appflowy_web_app/src-tauri/icons/Square71x71Logo.png deleted file mode 100644 index ceae9ad1bb..0000000000 Binary files a/frontend/appflowy_web_app/src-tauri/icons/Square71x71Logo.png and /dev/null differ diff --git a/frontend/appflowy_web_app/src-tauri/icons/Square89x89Logo.png b/frontend/appflowy_web_app/src-tauri/icons/Square89x89Logo.png deleted file mode 100644 index 123dcea650..0000000000 Binary files a/frontend/appflowy_web_app/src-tauri/icons/Square89x89Logo.png and /dev/null differ diff --git a/frontend/appflowy_web_app/src-tauri/icons/StoreLogo.png b/frontend/appflowy_web_app/src-tauri/icons/StoreLogo.png deleted file mode 100644 index d7906c3c03..0000000000 Binary files a/frontend/appflowy_web_app/src-tauri/icons/StoreLogo.png and /dev/null differ diff --git a/frontend/appflowy_web_app/src-tauri/icons/icon.icns b/frontend/appflowy_web_app/src-tauri/icons/icon.icns deleted file mode 100644 index 74b585f25d..0000000000 Binary files a/frontend/appflowy_web_app/src-tauri/icons/icon.icns and /dev/null differ diff --git a/frontend/appflowy_web_app/src-tauri/icons/icon.ico b/frontend/appflowy_web_app/src-tauri/icons/icon.ico deleted file mode 100644 index cd9ad402d1..0000000000 Binary files a/frontend/appflowy_web_app/src-tauri/icons/icon.ico and /dev/null differ diff --git a/frontend/appflowy_web_app/src-tauri/icons/icon.png b/frontend/appflowy_web_app/src-tauri/icons/icon.png deleted file mode 100644 index 7cc3853d67..0000000000 Binary files a/frontend/appflowy_web_app/src-tauri/icons/icon.png and /dev/null differ diff --git a/frontend/appflowy_web_app/src-tauri/rust-toolchain.toml b/frontend/appflowy_web_app/src-tauri/rust-toolchain.toml deleted file mode 100644 index 6f14058b2e..0000000000 --- a/frontend/appflowy_web_app/src-tauri/rust-toolchain.toml +++ /dev/null @@ -1,2 +0,0 @@ -[toolchain] -channel = "1.77.2" diff --git a/frontend/appflowy_web_app/src-tauri/rustfmt.toml b/frontend/appflowy_web_app/src-tauri/rustfmt.toml deleted file mode 100644 index 5cb0d67ee5..0000000000 --- a/frontend/appflowy_web_app/src-tauri/rustfmt.toml +++ /dev/null @@ -1,12 +0,0 @@ -# https://rust-lang.github.io/rustfmt/?version=master&search= -max_width = 100 -tab_spaces = 2 -newline_style = "Auto" -match_block_trailing_comma = true -use_field_init_shorthand = true -use_try_shorthand = true -reorder_imports = true -reorder_modules = true -remove_nested_parens = true -merge_derives = true -edition = "2021" \ No newline at end of file diff --git a/frontend/appflowy_web_app/src-tauri/src/init.rs b/frontend/appflowy_web_app/src-tauri/src/init.rs deleted file mode 100644 index 7af31af362..0000000000 --- a/frontend/appflowy_web_app/src-tauri/src/init.rs +++ /dev/null @@ -1,79 +0,0 @@ -use dotenv::dotenv; -use flowy_core::config::AppFlowyCoreConfig; -use flowy_core::{AppFlowyCore, DEFAULT_NAME}; -use lib_dispatch::runtime::AFPluginRuntime; -use std::rc::Rc; -use std::sync::{Arc, Mutex}; - -pub fn read_env() { - dotenv().ok(); - - let env = if cfg!(debug_assertions) { - include_str!("../env.development") - } else { - include_str!("../env.production") - }; - - for line in env.lines() { - if let Some((key, value)) = line.split_once('=') { - // Check if the environment variable is not already set in the system - let current_value = std::env::var(key).unwrap_or_default(); - if current_value.is_empty() { - std::env::set_var(key, value); - } - } - } -} - -pub fn init_appflowy_core() -> MutexAppFlowyCore { - let config_json = include_str!("../tauri.conf.json"); - let config: tauri_utils::config::Config = serde_json::from_str(config_json).unwrap(); - - let app_version = config - .package - .version - .clone() - .map(|v| v.to_string()) - .unwrap_or_else(|| "0.5.8".to_string()); - let app_version = - semver::Version::parse(&app_version).unwrap_or_else(|_| semver::Version::new(0, 5, 8)); - let mut data_path = tauri::api::path::app_local_data_dir(&config).unwrap(); - if cfg!(debug_assertions) { - data_path.push("data_dev"); - } else { - data_path.push("data"); - } - - let custom_application_path = data_path.to_str().unwrap().to_string(); - let application_path = data_path.to_str().unwrap().to_string(); - let device_id = uuid::Uuid::new_v4().to_string(); - - read_env(); - std::env::set_var("RUST_LOG", "trace"); - - let config = AppFlowyCoreConfig::new( - app_version, - custom_application_path, - application_path, - device_id, - "tauri".to_string(), - DEFAULT_NAME.to_string(), - ) - .log_filter("trace", vec!["appflowy_tauri".to_string()]); - - let runtime = Arc::new(AFPluginRuntime::new().unwrap()); - let cloned_runtime = runtime.clone(); - runtime.block_on(async move { - MutexAppFlowyCore::new(AppFlowyCore::new(config, cloned_runtime, None).await) - }) -} - -pub struct MutexAppFlowyCore(pub Arc>); - -impl MutexAppFlowyCore { - pub(crate) fn new(appflowy_core: AppFlowyCore) -> Self { - Self(Arc::new(Mutex::new(appflowy_core))) - } -} -unsafe impl Sync for MutexAppFlowyCore {} -unsafe impl Send for MutexAppFlowyCore {} diff --git a/frontend/appflowy_web_app/src-tauri/src/main.rs b/frontend/appflowy_web_app/src-tauri/src/main.rs deleted file mode 100644 index 781ce55098..0000000000 --- a/frontend/appflowy_web_app/src-tauri/src/main.rs +++ /dev/null @@ -1,71 +0,0 @@ -#![cfg_attr( - all(not(debug_assertions), target_os = "windows"), - windows_subsystem = "windows" -)] - -#[allow(dead_code)] -pub const DEEP_LINK_SCHEME: &str = "appflowy-flutter"; -pub const OPEN_DEEP_LINK: &str = "open_deep_link"; - -mod init; -mod notification; -mod request; - -use flowy_notification::{register_notification_sender, unregister_all_notification_sender}; -use init::*; -use notification::*; -use request::*; -use tauri::Manager; -extern crate dotenv; - -fn main() { - tauri_plugin_deep_link::prepare(DEEP_LINK_SCHEME); - - let flowy_core = init_appflowy_core(); - tauri::Builder::default() - .invoke_handler(tauri::generate_handler![invoke_request]) - .manage(flowy_core) - .on_window_event(|_window_event| {}) - .on_menu_event(|_menu| {}) - .on_page_load(|window, _payload| { - let app_handler = window.app_handle(); - // Make sure hot reload won't register the notification sender twice - unregister_all_notification_sender(); - register_notification_sender(TSNotificationSender::new(app_handler.clone())); - // tauri::async_runtime::spawn(async move {}); - - window.listen_global(AF_EVENT, move |event| { - on_event(app_handler.clone(), event); - }); - }) - .setup(|_app| { - let splashscreen_window = _app.get_window("splashscreen").unwrap(); - let window = _app.get_window("main").unwrap(); - let handle = _app.handle(); - - // we perform the initialization code on a new task so the app doesn't freeze - tauri::async_runtime::spawn(async move { - // initialize your app here instead of sleeping :) - std::thread::sleep(std::time::Duration::from_secs(2)); - - // After it's done, close the splashscreen and display the main window - splashscreen_window.close().unwrap(); - window.show().unwrap(); - // If you need macOS support this must be called in .setup() ! - // Otherwise this could be called right after prepare() but then you don't have access to tauri APIs - // On macOS You still have to install a .app bundle you got from tauri build --debug for this to work! - tauri_plugin_deep_link::register( - DEEP_LINK_SCHEME, - move |request| { - dbg!(&request); - handle.emit_all(OPEN_DEEP_LINK, request).unwrap(); - }, - ) - .unwrap(/* If listening to the scheme is optional for your app, you don't want to unwrap here. */); - }); - - Ok(()) - }) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); -} diff --git a/frontend/appflowy_web_app/src-tauri/src/notification.rs b/frontend/appflowy_web_app/src-tauri/src/notification.rs deleted file mode 100644 index b42541edec..0000000000 --- a/frontend/appflowy_web_app/src-tauri/src/notification.rs +++ /dev/null @@ -1,35 +0,0 @@ -use flowy_notification::entities::SubscribeObject; -use flowy_notification::NotificationSender; -use serde::Serialize; -use tauri::{AppHandle, Event, Manager, Wry}; - -#[allow(dead_code)] -pub const AF_EVENT: &str = "af-event"; -pub const AF_NOTIFICATION: &str = "af-notification"; - -#[tracing::instrument(level = "trace")] -pub fn on_event(app_handler: AppHandle, event: Event) {} - -#[allow(dead_code)] -pub fn send_notification(app_handler: AppHandle, payload: P) { - app_handler.emit_all(AF_NOTIFICATION, payload).unwrap(); -} - -pub struct TSNotificationSender { - handler: AppHandle, -} - -impl TSNotificationSender { - pub fn new(handler: AppHandle) -> Self { - Self { handler } - } -} - -impl NotificationSender for TSNotificationSender { - fn send_subject(&self, subject: SubscribeObject) -> Result<(), String> { - self - .handler - .emit_all(AF_NOTIFICATION, subject) - .map_err(|e| format!("{:?}", e)) - } -} diff --git a/frontend/appflowy_web_app/src-tauri/src/request.rs b/frontend/appflowy_web_app/src-tauri/src/request.rs deleted file mode 100644 index ff69a438c9..0000000000 --- a/frontend/appflowy_web_app/src-tauri/src/request.rs +++ /dev/null @@ -1,45 +0,0 @@ -use crate::init::MutexAppFlowyCore; -use lib_dispatch::prelude::{ - AFPluginDispatcher, AFPluginEventResponse, AFPluginRequest, StatusCode, -}; -use tauri::{AppHandle, Manager, State, Wry}; - -#[derive(Clone, Debug, serde::Deserialize)] -pub struct AFTauriRequest { - ty: String, - payload: Vec, -} - -impl std::convert::From for AFPluginRequest { - fn from(event: AFTauriRequest) -> Self { - AFPluginRequest::new(event.ty).payload(event.payload) - } -} - -#[derive(Clone, serde::Serialize)] -pub struct AFTauriResponse { - code: StatusCode, - payload: Vec, -} - -impl std::convert::From for AFTauriResponse { - fn from(response: AFPluginEventResponse) -> Self { - Self { - code: response.status_code, - payload: response.payload.to_vec(), - } - } -} - -// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command -#[tauri::command] -pub async fn invoke_request( - request: AFTauriRequest, - app_handler: AppHandle, -) -> AFTauriResponse { - let request: AFPluginRequest = request.into(); - let state: State = app_handler.state(); - let dispatcher = state.0.lock().unwrap().dispatcher(); - let response = AFPluginDispatcher::sync_send(dispatcher, request); - response.into() -} diff --git a/frontend/appflowy_web_app/src-tauri/tauri.conf.json b/frontend/appflowy_web_app/src-tauri/tauri.conf.json deleted file mode 100644 index ea11f47def..0000000000 --- a/frontend/appflowy_web_app/src-tauri/tauri.conf.json +++ /dev/null @@ -1,113 +0,0 @@ -{ - "build": { - "beforeBuildCommand": "npm run build:tauri", - "beforeDevCommand": "npm run dev:tauri", - "devPath": "http://localhost:5173", - "distDir": "../dist", - "withGlobalTauri": false - }, - "package": { - "productName": "AppFlowy", - "version": "0.0.1" - }, - "tauri": { - "allowlist": { - "all": false, - "shell": { - "all": false, - "open": true - }, - "fs": { - "all": true, - "scope": [ - "$APPLOCALDATA/**" - ], - "readFile": true, - "writeFile": true, - "readDir": true, - "copyFile": true, - "createDir": true, - "removeDir": true, - "removeFile": true, - "renameFile": true, - "exists": true - }, - "clipboard": { - "all": true, - "writeText": true, - "readText": true - }, - "dialog": { - "all": true, - "ask": true, - "confirm": true, - "message": true, - "open": true, - "save": true - } - }, - "bundle": { - "active": true, - "category": "DeveloperTool", - "copyright": "", - "deb": { - "depends": [] - }, - "icon": [ - "icons/32x32.png", - "icons/128x128.png", - "icons/128x128@2x.png", - "icons/icon.icns", - "icons/icon.ico" - ], - "externalBin": [], - "identifier": "com.appflowy.tauri", - "longDescription": "", - "macOS": { - "entitlements": null, - "exceptionDomain": "", - "frameworks": [], - "providerShortName": null, - "signingIdentity": null, - "minimumSystemVersion": "10.15.0" - }, - "resources": [], - "shortDescription": "", - "targets": "all", - "windows": { - "certificateThumbprint": null, - "digestAlgorithm": "sha256", - "timestampUrl": "" - } - }, - "security": { - "csp": null - }, - "updater": { - "active": false - }, - "windows": [ - { - "fileDropEnabled": false, - "fullscreen": false, - "height": 800, - "resizable": true, - "title": "AppFlowy", - "width": 1200, - "minWidth": 800, - "minHeight": 600, - "visible": false, - "label": "main" - }, - { - "height": 300, - "width": 549, - "decorations": false, - "url": "launch_splash.jpg", - "label": "splashscreen", - "center": true, - "visible": true - } - ] - } -} diff --git a/frontend/appflowy_web_app/src/@types/i18next.d.ts b/frontend/appflowy_web_app/src/@types/i18next.d.ts deleted file mode 100644 index 6adbb4a512..0000000000 --- a/frontend/appflowy_web_app/src/@types/i18next.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -import resources from './resources'; - -declare module 'i18next' { - interface CustomTypeOptions { - defaultNS: 'translation'; - resources: typeof resources; - } -} diff --git a/frontend/appflowy_web_app/src/@types/resources.ts b/frontend/appflowy_web_app/src/@types/resources.ts deleted file mode 100644 index 6bd90364e0..0000000000 --- a/frontend/appflowy_web_app/src/@types/resources.ts +++ /dev/null @@ -1,7 +0,0 @@ -import translation from './translations/en.json'; - -const resources = { - translation, -} as const; - -export default resources; diff --git a/frontend/appflowy_web_app/src/application/comment.type.ts b/frontend/appflowy_web_app/src/application/comment.type.ts deleted file mode 100644 index 0d5bdecf53..0000000000 --- a/frontend/appflowy_web_app/src/application/comment.type.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { AFWebUser } from '@/application/types'; - -export interface GlobalComment { - commentId: string; - user: AFWebUser | null; - content: string; - createdAt: string; - lastUpdatedAt: string; - replyCommentId: string | null; - isDeleted: boolean; - canDeleted: boolean; -} - -export interface Reaction { - reactionType: string; - reactUsers: AFWebUser[]; - commentId: string; -} diff --git a/frontend/appflowy_web_app/src/application/constants.ts b/frontend/appflowy_web_app/src/application/constants.ts deleted file mode 100644 index 5880ff6326..0000000000 --- a/frontend/appflowy_web_app/src/application/constants.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const databasePrefix = 'af_database'; - -export const HEADER_HEIGHT = 48; \ No newline at end of file diff --git a/frontend/appflowy_web_app/src/application/database-yjs/__tests__/filter.test.ts b/frontend/appflowy_web_app/src/application/database-yjs/__tests__/filter.test.ts deleted file mode 100644 index b07dc9beac..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/__tests__/filter.test.ts +++ /dev/null @@ -1,672 +0,0 @@ -import { - NumberFilterCondition, - TextFilterCondition, - CheckboxFilterCondition, - ChecklistFilterCondition, - SelectOptionFilterCondition, - Row, -} from '@/application/database-yjs'; -import { withTestingData } from '@/application/database-yjs/__tests__/withTestingData'; -import { - withCheckboxFilter, - withChecklistFilter, - withDateTimeFilter, - withMultiSelectOptionFilter, - withNumberFilter, - withRichTextFilter, - withSingleSelectOptionFilter, - withUrlFilter, -} from '@/application/database-yjs/__tests__/withTestingFilters'; -import { withTestingRows } from '@/application/database-yjs/__tests__/withTestingRows'; -import { RowId, YDoc } from '@/application/types'; -import { - textFilterCheck, - numberFilterCheck, - checkboxFilterCheck, - checklistFilterCheck, - selectOptionFilterCheck, - filterBy, -} from '../filter'; -import { expect } from '@jest/globals'; -import * as Y from 'yjs'; - -describe('Text filter check', () => { - const text = 'Hello, world!'; - it('should return true for TextIs condition', () => { - const condition = TextFilterCondition.TextIs; - const content = 'Hello, world!'; - - const result = textFilterCheck(text, content, condition); - - expect(result).toBe(true); - }); - - it('should return false for TextIs condition', () => { - const condition = TextFilterCondition.TextIs; - const content = 'Hello, world'; - - const result = textFilterCheck(text, content, condition); - - expect(result).toBe(false); - }); - - it('should return true for TextIsNot condition', () => { - const condition = TextFilterCondition.TextIsNot; - const content = 'Hello, world'; - - const result = textFilterCheck(text, content, condition); - - expect(result).toBe(true); - }); - - it('should return false for TextIsNot condition', () => { - const condition = TextFilterCondition.TextIsNot; - const content = 'Hello, world!'; - - const result = textFilterCheck(text, content, condition); - - expect(result).toBe(false); - }); - - it('should return true for TextContains condition', () => { - const condition = TextFilterCondition.TextContains; - const content = 'world'; - - const result = textFilterCheck(text, content, condition); - - expect(result).toBe(true); - }); - - it('should return false for TextContains condition', () => { - const condition = TextFilterCondition.TextContains; - const content = 'planet'; - - const result = textFilterCheck(text, content, condition); - - expect(result).toBe(false); - }); - - it('should return true for TextDoesNotContain condition', () => { - const condition = TextFilterCondition.TextDoesNotContain; - const content = 'planet'; - - const result = textFilterCheck(text, content, condition); - - expect(result).toBe(true); - }); - - it('should return false for TextDoesNotContain condition', () => { - const condition = TextFilterCondition.TextDoesNotContain; - const content = 'world'; - - const result = textFilterCheck(text, content, condition); - - expect(result).toBe(false); - }); - - it('should return true for TextIsEmpty condition', () => { - const condition = TextFilterCondition.TextIsEmpty; - const text = ''; - - const result = textFilterCheck(text, '', condition); - - expect(result).toBe(true); - }); - - it('should return false for TextIsEmpty condition', () => { - const condition = TextFilterCondition.TextIsEmpty; - const text = 'Hello, world!'; - - const result = textFilterCheck(text, '', condition); - - expect(result).toBe(false); - }); - - it('should return true for TextIsNotEmpty condition', () => { - const condition = TextFilterCondition.TextIsNotEmpty; - const text = 'Hello, world!'; - - const result = textFilterCheck(text, '', condition); - - expect(result).toBe(true); - }); - - it('should return false for TextIsNotEmpty condition', () => { - const condition = TextFilterCondition.TextIsNotEmpty; - const text = ''; - - const result = textFilterCheck(text, '', condition); - - expect(result).toBe(false); - }); - - it('should return false for unknown condition', () => { - const condition = 42; - const content = 'Hello, world!'; - - const result = textFilterCheck(text, content, condition); - - expect(result).toBe(false); - }); -}); - -describe('Number filter check', () => { - const num = '42'; - it('should return true for Equal condition', () => { - const condition = NumberFilterCondition.Equal; - const content = '42'; - - const result = numberFilterCheck(num, content, condition); - - expect(result).toBe(true); - }); - - it('should return false for Equal condition', () => { - const condition = NumberFilterCondition.Equal; - const content = '43'; - - const result = numberFilterCheck(num, content, condition); - - expect(result).toBe(false); - }); - - it('should return true for NotEqual condition', () => { - const condition = NumberFilterCondition.NotEqual; - const content = '43'; - - const result = numberFilterCheck(num, content, condition); - - expect(result).toBe(true); - }); - - it('should return false for NotEqual condition', () => { - const condition = NumberFilterCondition.NotEqual; - const content = '42'; - - const result = numberFilterCheck(num, content, condition); - - expect(result).toBe(false); - }); - - it('should return true for GreaterThan condition', () => { - const condition = NumberFilterCondition.GreaterThan; - const content = '41'; - - const result = numberFilterCheck(num, content, condition); - - expect(result).toBe(true); - }); - - it('should return false for GreaterThan condition', () => { - const condition = NumberFilterCondition.GreaterThan; - const content = '42'; - - const result = numberFilterCheck(num, content, condition); - - expect(result).toBe(false); - }); - - it('should return true for GreaterThanOrEqualTo condition', () => { - const condition = NumberFilterCondition.GreaterThanOrEqualTo; - const content = '42'; - - const result = numberFilterCheck(num, content, condition); - - expect(result).toBe(true); - }); - - it('should return false for GreaterThanOrEqualTo condition', () => { - const condition = NumberFilterCondition.GreaterThanOrEqualTo; - const content = '43'; - - const result = numberFilterCheck(num, content, condition); - - expect(result).toBe(false); - }); - - it('should return true for LessThan condition', () => { - const condition = NumberFilterCondition.LessThan; - const content = '43'; - - const result = numberFilterCheck(num, content, condition); - - expect(result).toBe(true); - }); - - it('should return false for LessThan condition', () => { - const condition = NumberFilterCondition.LessThan; - const content = '42'; - - const result = numberFilterCheck(num, content, condition); - - expect(result).toBe(false); - }); - - it('should return true for LessThanOrEqualTo condition', () => { - const condition = NumberFilterCondition.LessThanOrEqualTo; - const content = '42'; - - const result = numberFilterCheck(num, content, condition); - - expect(result).toBe(true); - }); - - it('should return false for LessThanOrEqualTo condition', () => { - const condition = NumberFilterCondition.LessThanOrEqualTo; - const content = '41'; - - const result = numberFilterCheck(num, content, condition); - - expect(result).toBe(false); - }); - - it('should return true for NumberIsEmpty condition', () => { - const condition = NumberFilterCondition.NumberIsEmpty; - - const result = numberFilterCheck('', '', condition); - - expect(result).toBe(true); - }); - - it('should return false for NumberIsEmpty condition', () => { - const condition = NumberFilterCondition.NumberIsEmpty; - const num = '42'; - - const result = numberFilterCheck(num, '', condition); - - expect(result).toBe(false); - }); - - it('should return true for NumberIsNotEmpty condition', () => { - const condition = NumberFilterCondition.NumberIsNotEmpty; - const num = '42'; - - const result = numberFilterCheck(num, '', condition); - - expect(result).toBe(true); - }); - - it('should return false for NumberIsNotEmpty condition', () => { - const condition = NumberFilterCondition.NumberIsNotEmpty; - const num = ''; - - const result = numberFilterCheck(num, '', condition); - - expect(result).toBe(false); - }); - - it('should return false for unknown condition', () => { - const condition = 42; - const content = '42'; - - const result = numberFilterCheck(num, content, condition); - - expect(result).toBe(false); - }); -}); - -describe('Checkbox filter check', () => { - it('should return true for IsChecked condition', () => { - const condition = CheckboxFilterCondition.IsChecked; - const data = 'Yes'; - - const result = checkboxFilterCheck(data, condition); - - expect(result).toBe(true); - }); - - it('should return false for IsChecked condition', () => { - const condition = CheckboxFilterCondition.IsChecked; - const data = 'No'; - - const result = checkboxFilterCheck(data, condition); - - expect(result).toBe(false); - }); - - it('should return true for IsUnChecked condition', () => { - const condition = CheckboxFilterCondition.IsUnChecked; - const data = 'No'; - - const result = checkboxFilterCheck(data, condition); - - expect(result).toBe(true); - }); - - it('should return false for IsUnChecked condition', () => { - const condition = CheckboxFilterCondition.IsUnChecked; - const data = 'Yes'; - - const result = checkboxFilterCheck(data, condition); - - expect(result).toBe(false); - }); - - it('should return false for unknown condition', () => { - const condition = 42; - const data = 'Yes'; - - const result = checkboxFilterCheck(data, condition); - - expect(result).toBe(false); - }); -}); - -describe('Checklist filter check', () => { - it('should return true for IsComplete condition', () => { - const condition = ChecklistFilterCondition.IsComplete; - const data = JSON.stringify({ - options: [ - { id: '1', name: 'Option 1' }, - { id: '2', name: 'Option 2' }, - ], - selected_option_ids: ['1', '2'], - }); - - const result = checklistFilterCheck(data, '', condition); - - expect(result).toBe(true); - }); - - it('should return false for IsComplete condition', () => { - const condition = ChecklistFilterCondition.IsComplete; - const data = JSON.stringify({ - options: [ - { id: '1', name: 'Option 1' }, - { id: '2', name: 'Option 2' }, - ], - selected_option_ids: ['1'], - }); - - const result = checklistFilterCheck(data, '', condition); - - expect(result).toBe(false); - }); - - it('should return false for unknown condition', () => { - const condition = 42; - const data = JSON.stringify({ - options: [ - { id: '1', name: 'Option 1' }, - { id: '2', name: 'Option 2' }, - ], - selected_option_ids: ['1', '2'], - }); - - const result = checklistFilterCheck(data, '', condition); - - expect(result).toBe(false); - }); -}); - -describe('SelectOption filter check', () => { - it('should return true for OptionIs condition', () => { - const condition = SelectOptionFilterCondition.OptionIs; - const content = '1'; - const data = '1,2'; - - const result = selectOptionFilterCheck(data, content, condition); - - expect(result).toBe(true); - }); - - it('should return false for OptionIs condition', () => { - const condition = SelectOptionFilterCondition.OptionIs; - const content = '3'; - const data = '1,2'; - - const result = selectOptionFilterCheck(data, content, condition); - - expect(result).toBe(false); - }); - - it('should return true for OptionIsNot condition', () => { - const condition = SelectOptionFilterCondition.OptionIsNot; - const content = '3'; - const data = '1,2'; - - const result = selectOptionFilterCheck(data, content, condition); - - expect(result).toBe(true); - }); - - it('should return false for OptionIsNot condition', () => { - const condition = SelectOptionFilterCondition.OptionIsNot; - const content = '1'; - const data = '1,2'; - - const result = selectOptionFilterCheck(data, content, condition); - - expect(result).toBe(false); - }); - - it('should return true for OptionContains condition', () => { - const condition = SelectOptionFilterCondition.OptionContains; - const content = '1,3'; - const data = '1,2,3'; - - const result = selectOptionFilterCheck(data, content, condition); - - expect(result).toBe(true); - }); - - it('should return false for OptionContains condition', () => { - const condition = SelectOptionFilterCondition.OptionContains; - const content = '4'; - const data = '1,2,3'; - - const result = selectOptionFilterCheck(data, content, condition); - - expect(result).toBe(false); - }); - - it('should return true for OptionDoesNotContain condition', () => { - const condition = SelectOptionFilterCondition.OptionDoesNotContain; - const content = '4,5'; - const data = '1,2,3'; - - const result = selectOptionFilterCheck(data, content, condition); - - expect(result).toBe(true); - }); - - it('should return false for OptionDoesNotContain condition', () => { - const condition = SelectOptionFilterCondition.OptionDoesNotContain; - const content = '1,3'; - const data = '1,2,3'; - - const result = selectOptionFilterCheck(data, content, condition); - - expect(result).toBe(false); - }); - - it('should return true for OptionIsEmpty condition', () => { - const condition = SelectOptionFilterCondition.OptionIsEmpty; - const data = ''; - - const result = selectOptionFilterCheck(data, '', condition); - - expect(result).toBe(true); - }); - - it('should return false for OptionIsEmpty condition', () => { - const condition = SelectOptionFilterCondition.OptionIsEmpty; - const data = '1,2'; - - const result = selectOptionFilterCheck(data, '', condition); - - expect(result).toBe(false); - }); - - it('should return true for OptionIsNotEmpty condition', () => { - const condition = SelectOptionFilterCondition.OptionIsNotEmpty; - const data = '1,2'; - - const result = selectOptionFilterCheck(data, '', condition); - - expect(result).toBe(true); - }); - - it('should return false for OptionIsNotEmpty condition', () => { - const condition = SelectOptionFilterCondition.OptionIsNotEmpty; - const data = ''; - - const result = selectOptionFilterCheck(data, '', condition); - - expect(result).toBe(false); - }); - - it('should return false for unknown condition', () => { - const condition = 42; - const content = '1'; - const data = '1,2'; - - const result = selectOptionFilterCheck(data, content, condition); - - expect(result).toBe(false); - }); -}); - -describe('Database filterBy', () => { - let rows: Row[]; - - beforeEach(() => { - rows = withTestingRows(); - }); - - it('should return all rows for empty filter', () => { - const { filters, fields, rowMap } = withTestingData(); - const result = filterBy(rows, filters, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(result).toBe('1,2,3,4,5,6,7,8,9,10'); - }); - - it('should return all rows for empty rowMap', () => { - const { filters, fields } = withTestingData(); - const rowMap: Record = {}; - const result = filterBy(rows, filters, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(result).toBe('1,2,3,4,5,6,7,8,9,10'); - }); - - it('should return rows that match text filter', () => { - const { filters, fields, rowMap } = withTestingData(); - const filter = withRichTextFilter(); - filters.push([filter]); - const result = filterBy(rows, filters, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(result).toBe('1,5'); - }); - - it('should return rows that match number filter', () => { - const { filters, fields, rowMap } = withTestingData(); - const filter = withNumberFilter(); - filters.push([filter]); - const result = filterBy(rows, filters, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(result).toBe('4,5,6,7,8,9,10'); - }); - - it('should return rows that match checkbox filter', () => { - const { filters, fields, rowMap } = withTestingData(); - const filter = withCheckboxFilter(); - filters.push([filter]); - const result = filterBy(rows, filters, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(result).toBe('2,4,6,8,10'); - }); - - it('should return rows that match checklist filter', () => { - const { filters, fields, rowMap } = withTestingData(); - const filter = withChecklistFilter(); - filters.push([filter]); - const result = filterBy(rows, filters, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(result).toBe('1,2,4,5,6,7,8,10'); - }); - - it('should return rows that match multiple filters', () => { - const { filters, fields, rowMap } = withTestingData(); - const filter1 = withRichTextFilter(); - const filter2 = withNumberFilter(); - filters.push([filter1, filter2]); - const result = filterBy(rows, filters, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(result).toBe('5'); - }); - - it('should return rows that match url filter', () => { - const { filters, fields, rowMap } = withTestingData(); - const filter = withUrlFilter(); - filters.push([filter]); - const result = filterBy(rows, filters, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(result).toBe('4'); - }); - - it('should return rows that match date filter', () => { - const { filters, fields, rowMap } = withTestingData(); - const filter = withDateTimeFilter(); - filters.push([filter]); - const result = filterBy(rows, filters, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(result).toBe('1,2,3,4,5,6,7,8,9,10'); - }); - - it('should return rows that match select option filter', () => { - const { filters, fields, rowMap } = withTestingData(); - const filter = withSingleSelectOptionFilter(); - filters.push([filter]); - const result = filterBy(rows, filters, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(result).toBe('2,5,8'); - }); - - it('should return rows that match multi select option filter', () => { - const { filters, fields, rowMap } = withTestingData(); - const filter = withMultiSelectOptionFilter(); - filters.push([filter]); - const result = filterBy(rows, filters, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(result).toBe('1,2,3,5,6,7,8,9'); - }); - - it('should return rows that match multiple filters', () => { - const { filters, fields, rowMap } = withTestingData(); - const filter1 = withNumberFilter(); - const filter2 = withChecklistFilter(); - filters.push([filter1, filter2]); - const result = filterBy(rows, filters, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(result).toBe('4,5,6,7,8,10'); - }); - - it('should return empty array for all filters', () => { - const { filters, fields, rowMap } = withTestingData(); - const filter1 = withNumberFilter(); - const filter2 = withChecklistFilter(); - const filter3 = withRichTextFilter(); - const filter4 = withCheckboxFilter(); - const filter5 = withSingleSelectOptionFilter(); - const filter6 = withMultiSelectOptionFilter(); - const filter7 = withUrlFilter(); - const filter8 = withDateTimeFilter(); - filters.push([filter1, filter2, filter3, filter4, filter5, filter6, filter7, filter8]); - const result = filterBy(rows, filters, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(result).toBe(''); - }); -}); diff --git a/frontend/appflowy_web_app/src/application/database-yjs/__tests__/fixtures/filters.json b/frontend/appflowy_web_app/src/application/database-yjs/__tests__/fixtures/filters.json deleted file mode 100644 index eb0688a5de..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/__tests__/fixtures/filters.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "filter_text_field": { - "field_id": "text_field", - "condition": 2, - "content": "w" - }, - "filter_number_field": { - "field_id": "number_field", - "condition": 2, - "content": 1000 - }, - "filter_date_field": { - "field_id": "date_field", - "condition": 1, - "content": 1685798400000 - }, - "filter_checkbox_field": { - "field_id": "checkbox_field", - "condition": 1 - }, - "filter_checklist_field": { - "field_id": "checklist_field", - "condition": 1 - }, - "filter_url_field": { - "field_id": "url_field", - "condition": 0, - "content": "https://example.com/4" - }, - "filter_single_select_field": { - "field_id": "single_select_field", - "condition": 0, - "content": "2" - }, - "filter_multi_select_field": { - "field_id": "multi_select_field", - "condition": 2, - "content": "1,3" - } -} \ No newline at end of file diff --git a/frontend/appflowy_web_app/src/application/database-yjs/__tests__/fixtures/rows.json b/frontend/appflowy_web_app/src/application/database-yjs/__tests__/fixtures/rows.json deleted file mode 100644 index 989a335527..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/__tests__/fixtures/rows.json +++ /dev/null @@ -1,412 +0,0 @@ -[ - { - "id": "1", - "cells": { - "text_field": { - "id": "text_field", - "data": "Hello world" - }, - "number_field": { - "id": "number_field", - "data": 123 - }, - "checkbox_field": { - "id": "checkbox_field", - "data": "Yes" - }, - "date_field": { - "id": "date_field", - "data": 1685539200000, - "end_timestamp": 1685625600000, - "include_time": true, - "is_range": false, - "reminder_id": "rem1" - }, - "url_field": { - "id": "url_field", - "data": "https://example.com/1" - }, - "single_select_field": { - "id": "single_select_field", - "data": "1" - }, - "multi_select_field": { - "id": "multi_select_field", - "data": "1,2" - }, - "checklist_field": { - "id": "checklist_field", - "data": "{\"options\":[{\"id\":\"1\",\"name\":\"Task 1\"},{\"id\":\"2\",\"name\":\"Task 2\"}],\"selected_option_ids\":[\"1\"]}" - } - } - }, - { - "id": "2", - "cells": { - "text_field": { - "id": "text_field", - "data": "Good morning" - }, - "number_field": { - "id": "number_field", - "data": 456 - }, - "checkbox_field": { - "id": "checkbox_field", - "data": "No" - }, - "date_field": { - "id": "date_field", - "data": 1685625600000, - "end_timestamp": 1685712000000, - "include_time": false, - "is_range": true, - "reminder_id": "rem2" - }, - "url_field": { - "id": "url_field", - "data": "https://example.com/2" - }, - "single_select_field": { - "id": "single_select_field", - "data": "2" - }, - "multi_select_field": { - "id": "multi_select_field", - "data": "2,3" - }, - "checklist_field": { - "id": "checklist_field", - "data": "{\"options\":[{\"id\":\"1\",\"name\":\"Task 1\"},{\"id\":\"2\",\"name\":\"Task 2\"}],\"selected_option_ids\":[\"2\"]}" - } - } - }, - { - "id": "3", - "cells": { - "text_field": { - "id": "text_field", - "data": "Good night" - }, - "number_field": { - "id": "number_field", - "data": 789 - }, - "checkbox_field": { - "id": "checkbox_field", - "data": "Yes" - }, - "date_field": { - "id": "date_field", - "data": 1685712000000, - "end_timestamp": 1685798400000, - "include_time": true, - "is_range": false, - "reminder_id": "rem3" - }, - "url_field": { - "id": "url_field", - "data": "https://example.com/3" - }, - "single_select_field": { - "id": "single_select_field", - "data": "3" - }, - "multi_select_field": { - "id": "multi_select_field", - "data": "1,3" - }, - "checklist_field": { - "id": "checklist_field", - "data": "{\"options\":[{\"id\":\"1\",\"name\":\"Task 1\"},{\"id\":\"2\",\"name\":\"Task 2\"}],\"selected_option_ids\":[\"1\",\"2\"]}" - } - } - }, - { - "id": "4", - "cells": { - "text_field": { - "id": "text_field", - "data": "Happy day" - }, - "number_field": { - "id": "number_field", - "data": 1011 - }, - "checkbox_field": { - "id": "checkbox_field", - "data": "No" - }, - "date_field": { - "id": "date_field", - "data": 1685798400000, - "end_timestamp": 1685884800000, - "include_time": false, - "is_range": true, - "reminder_id": "rem4" - }, - "url_field": { - "id": "url_field", - "data": "https://example.com/4" - }, - "single_select_field": { - "id": "single_select_field", - "data": "1" - }, - "multi_select_field": { - "id": "multi_select_field", - "data": "2" - }, - "checklist_field": { - "id": "checklist_field", - "data": "{\"options\":[{\"id\":\"1\",\"name\":\"Task 1\"},{\"id\":\"2\",\"name\":\"Task 2\"}],\"selected_option_ids\":[]}" - } - } - }, - { - "id": "5", - "cells": { - "text_field": { - "id": "text_field", - "data": "Sunny weather" - }, - "number_field": { - "id": "number_field", - "data": 1213 - }, - "checkbox_field": { - "id": "checkbox_field", - "data": "Yes" - }, - "date_field": { - "id": "date_field", - "data": 1685884800000, - "end_timestamp": 1685971200000, - "include_time": true, - "is_range": false, - "reminder_id": "rem5" - }, - "url_field": { - "id": "url_field", - "data": "https://example.com/5" - }, - "single_select_field": { - "id": "single_select_field", - "data": "2" - }, - "multi_select_field": { - "id": "multi_select_field", - "data": "1,2,3" - }, - "checklist_field": { - "id": "checklist_field", - "data": "{\"options\":[{\"id\":\"1\",\"name\":\"Task 1\"},{\"id\":\"2\",\"name\":\"Task 2\"}],\"selected_option_ids\":[\"1\"]}" - } - } - }, - { - "id": "6", - "cells": { - "text_field": { - "id": "text_field", - "data": "Rainy day" - }, - "number_field": { - "id": "number_field", - "data": 1415 - }, - "checkbox_field": { - "id": "checkbox_field", - "data": "No" - }, - "date_field": { - "id": "date_field", - "data": 1685971200000, - "end_timestamp": 1686057600000, - "include_time": false, - "is_range": true, - "reminder_id": "rem6" - }, - "url_field": { - "id": "url_field", - "data": "https://example.com/6" - }, - "single_select_field": { - "id": "single_select_field", - "data": "3" - }, - "multi_select_field": { - "id": "multi_select_field", - "data": "1,3" - }, - "checklist_field": { - "id": "checklist_field", - "data": "{\"options\":[{\"id\":\"1\",\"name\":\"Task 1\"},{\"id\":\"2\",\"name\":\"Task 2\"}],\"selected_option_ids\":[\"2\"]}" - } - } - }, - { - "id": "7", - "cells": { - "text_field": { - "id": "text_field", - "data": "Winter is coming" - }, - "number_field": { - "id": "number_field", - "data": 1617 - }, - "checkbox_field": { - "id": "checkbox_field", - "data": "Yes" - }, - "date_field": { - "id": "date_field", - "data": 1686057600000, - "end_timestamp": 1686144000000, - "include_time": true, - "is_range": false, - "reminder_id": "rem7" - }, - "url_field": { - "id": "url_field", - "data": "https://example.com/7" - }, - "single_select_field": { - "id": "single_select_field", - "data": "1" - }, - "multi_select_field": { - "id": "multi_select_field", - "data": "1,2" - }, - "checklist_field": { - "id": "checklist_field", - "data": "{\"options\":[{\"id\":\"1\",\"name\":\"Task 1\"},{\"id\":\"2\",\"name\":\"Task 2\"}],\"selected_option_ids\":[\"1\"]}" - } - } - }, - { - "id": "8", - "cells": { - "text_field": { - "id": "text_field", - "data": "Summer vibes" - }, - "number_field": { - "id": "number_field", - "data": 1819 - }, - "checkbox_field": { - "id": "checkbox_field", - "data": "No" - }, - "date_field": { - "id": "date_field", - "data": 1686144000000, - "end_timestamp": 1686230400000, - "include_time": false, - "is_range": true, - "reminder_id": "rem8" - }, - "url_field": { - "id": "url_field", - "data": "https://example.com/8" - }, - "single_select_field": { - "id": "single_select_field", - "data": "2" - }, - "multi_select_field": { - "id": "multi_select_field", - "data": "2,3" - }, - "checklist_field": { - "id": "checklist_field", - "data": "{\"options\":[{\"id\":\"1\",\"name\":\"Task 1\"},{\"id\":\"2\",\"name\":\"Task 2\"}],\"selected_option_ids\":[\"2\"]}" - } - } - }, - { - "id": "9", - "cells": { - "text_field": { - "id": "text_field", - "data": "Autumn leaves" - }, - "number_field": { - "id": "number_field", - "data": 2021 - }, - "checkbox_field": { - "id": "checkbox_field", - "data": "Yes" - }, - "date_field": { - "id": "date_field", - "data": 1686230400000, - "end_timestamp": 1686316800000, - "include_time": true, - "is_range": false, - "reminder_id": "rem9" - }, - "url_field": { - "id": "url_field", - "data": "https://example.com/9" - }, - "single_select_field": { - "id": "single_select_field", - "data": "3" - }, - "multi_select_field": { - "id": "multi_select_field", - "data": "1,3" - }, - "checklist_field": { - "id": "checklist_field", - "data": "{\"options\":[{\"id\":\"1\",\"name\":\"Task 1\"},{\"id\":\"2\",\"name\":\"Task 2\"}],\"selected_option_ids\":[\"1\",\"2\"]}" - } - } - }, - { - "id": "10", - "cells": { - "text_field": { - "id": "text_field", - "data": "Spring blossoms" - }, - "number_field": { - "id": "number_field", - "data": 2223 - }, - "checkbox_field": { - "id": "checkbox_field", - "data": "No" - }, - "date_field": { - "id": "date_field", - "data": 1686316800000, - "end_timestamp": 1686403200000, - "include_time": false, - "is_range": true, - "reminder_id": "rem10" - }, - "url_field": { - "id": "url_field", - "data": "https://example.com/10" - }, - "single_select_field": { - "id": "single_select_field", - "data": "1" - }, - "multi_select_field": { - "id": "multi_select_field", - "data": "2" - }, - "checklist_field": { - "id": "checklist_field", - "data": "{\"options\":[{\"id\":\"1\",\"name\":\"Task 1\"},{\"id\":\"2\",\"name\":\"Task 2\"}],\"selected_option_ids\":[]}" - } - } - } -] diff --git a/frontend/appflowy_web_app/src/application/database-yjs/__tests__/fixtures/sorts.json b/frontend/appflowy_web_app/src/application/database-yjs/__tests__/fixtures/sorts.json deleted file mode 100644 index 11ae36cf60..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/__tests__/fixtures/sorts.json +++ /dev/null @@ -1,102 +0,0 @@ -{ - "sort_asc_text_field": { - "id": "sort_asc_text_field", - "field_id": "text_field", - "condition": "asc" - }, - "sort_desc_text_field": { - "field_id": "text_field", - "condition": "desc", - "id": "sort_desc_text_field" - }, - "sort_asc_number_field": { - "field_id": "number_field", - "condition": "asc", - "id": "sort_asc_number_field" - }, - "sort_desc_number_field": { - "field_id": "number_field", - "condition": "desc", - "id": "sort_desc_number_field" - }, - "sort_asc_date_field": { - "field_id": "date_field", - "condition": "asc", - "id": "sort_asc_date_field" - }, - "sort_desc_date_field": { - "field_id": "date_field", - "condition": "desc", - "id": "sort_desc_date_field" - }, - "sort_asc_checkbox_field": { - "field_id": "checkbox_field", - "condition": "asc", - "id": "sort_asc_checkbox_field" - }, - "sort_desc_checkbox_field": { - "field_id": "checkbox_field", - "condition": "desc", - "id": "sort_desc_checkbox_field" - }, - "sort_asc_checklist_field": { - "field_id": "checklist_field", - "condition": "asc", - "id": "sort_asc_checklist_field" - }, - "sort_desc_checklist_field": { - "field_id": "checklist_field", - "condition": "desc", - "id": "sort_desc_checklist_field" - }, - "sort_asc_single_select_field": { - "field_id": "single_select_field", - "condition": "asc", - "id": "sort_asc_single_select_field" - }, - "sort_desc_single_select_field": { - "field_id": "single_select_field", - "condition": "desc", - "id": "sort_desc_single_select_field" - }, - "sort_asc_multi_select_field": { - "field_id": "multi_select_field", - "condition": "asc", - "id": "sort_asc_multi_select_field" - }, - "sort_desc_multi_select_field": { - "field_id": "multi_select_field", - "condition": "desc", - "id": "sort_desc_multi_select_field" - }, - "sort_asc_url_field": { - "field_id": "url_field", - "condition": "asc", - "id": "sort_asc_url_field" - }, - "sort_desc_url_field": { - "field_id": "url_field", - "condition": "desc", - "id": "sort_desc_url_field" - }, - "sort_asc_created_at": { - "field_id": "created_at_field", - "condition": "asc", - "id": "sort_asc_created_at" - }, - "sort_desc_created_at": { - "field_id": "created_at_field", - "condition": "desc", - "id": "sort_desc_created_at" - }, - "sort_asc_updated_at": { - "field_id": "last_modified_field", - "condition": "asc", - "id": "sort_asc_updated_at" - }, - "sort_desc_updated_at": { - "field_id": "last_modified_field", - "condition": "desc", - "id": "sort_desc_updated_at" - } -} \ No newline at end of file diff --git a/frontend/appflowy_web_app/src/application/database-yjs/__tests__/group.test.ts b/frontend/appflowy_web_app/src/application/database-yjs/__tests__/group.test.ts deleted file mode 100644 index a38ed139d6..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/__tests__/group.test.ts +++ /dev/null @@ -1,198 +0,0 @@ -import { FieldType, Row } from '@/application/database-yjs'; -import { withTestingData } from '@/application/database-yjs/__tests__/withTestingData'; -import { withTestingRows } from '@/application/database-yjs/__tests__/withTestingRows'; -import { expect } from '@jest/globals'; -import { groupByField } from '../group'; -import * as Y from 'yjs'; -import { - YDatabaseField, - YDatabaseFieldTypeOption, - YjsDatabaseKey, - YjsEditorKey, - YMapFieldTypeOption, -} from '@/application/types'; -import { YjsEditor } from '@/application/slate-yjs'; - -describe('Database group', () => { - let rows: Row[]; - - beforeEach(() => { - rows = withTestingRows(); - }); - - it('should return undefined if field is not select option', () => { - const { fields, rowMap } = withTestingData(); - expect(groupByField(rows, rowMap, fields.get('text_field'))).toBeUndefined(); - expect(groupByField(rows, rowMap, fields.get('number_field'))).toBeUndefined(); - expect(groupByField(rows, rowMap, fields.get('checklist_field'))).toBeUndefined(); - }); - - it('should gourp by checkbox field', () => { - const { fields, rowMap } = withTestingData(); - const field = fields.get('checkbox_field'); - const result = groupByField(rows, rowMap, field); - const expectRes = new Map([ - [ - 'Yes', - [ - { id: '1', height: 37 }, - { id: '3', height: 37 }, - { id: '5', height: 37 }, - { id: '7', height: 37 }, - { id: '9', height: 37 }, - ], - ], - [ - 'No', - [ - { id: '2', height: 37 }, - { id: '4', height: 37 }, - { id: '6', height: 37 }, - { id: '8', height: 37 }, - { id: '10', height: 37 }, - ], - ], - ]); - expect(result).toEqual(expectRes); - }); - it('should group by select option field', () => { - const { fields, rowMap } = withTestingData(); - const field = fields.get('single_select_field'); - const result = groupByField(rows, rowMap, field); - const expectRes = new Map([ - [ - '1', - [ - { id: '1', height: 37 }, - { id: '4', height: 37 }, - { id: '7', height: 37 }, - { id: '10', height: 37 }, - ], - ], - [ - '2', - [ - { id: '2', height: 37 }, - { id: '5', height: 37 }, - { id: '8', height: 37 }, - ], - ], - [ - '3', - [ - { id: '3', height: 37 }, - { id: '6', height: 37 }, - { id: '9', height: 37 }, - ], - ], - ]); - expect(result).toEqual(expectRes); - }); - - it('should group by multi select option field', () => { - const { fields, rowMap } = withTestingData(); - const field = fields.get('multi_select_field'); - const result = groupByField(rows, rowMap, field); - const expectRes = new Map([ - [ - '1', - [ - { id: '1', height: 37 }, - { id: '3', height: 37 }, - { id: '5', height: 37 }, - { id: '6', height: 37 }, - { id: '7', height: 37 }, - { id: '9', height: 37 }, - ], - ], - [ - '2', - [ - { id: '1', height: 37 }, - { id: '2', height: 37 }, - { id: '4', height: 37 }, - { id: '5', height: 37 }, - { id: '7', height: 37 }, - { id: '8', height: 37 }, - { id: '10', height: 37 }, - ], - ], - [ - '3', - [ - { id: '2', height: 37 }, - { id: '3', height: 37 }, - { id: '5', height: 37 }, - { id: '6', height: 37 }, - { id: '8', height: 37 }, - { id: '9', height: 37 }, - ], - ], - ]); - expect(result).toEqual(expectRes); - }); - - it('should not group if no options', () => { - const { fields, rowMap } = withTestingData(); - const field = new Y.Map() as YDatabaseField; - const typeOption = new Y.Map() as YDatabaseFieldTypeOption; - const now = Date.now().toString(); - - field.set(YjsDatabaseKey.name, 'Single Select Field'); - field.set(YjsDatabaseKey.id, 'another_single_select_field'); - field.set(YjsDatabaseKey.type, String(FieldType.SingleSelect)); - field.set(YjsDatabaseKey.last_modified, now.valueOf()); - field.set(YjsDatabaseKey.type_option, typeOption); - fields.set('another_single_select_field', field); - expect(groupByField(rows, rowMap, field)).toBeUndefined(); - - const selectTypeOption = new Y.Map() as YMapFieldTypeOption; - - typeOption.set(String(FieldType.SingleSelect), selectTypeOption); - selectTypeOption.set(YjsDatabaseKey.content, JSON.stringify({ disable_color: false, options: [] })); - const expectRes = new Map([['another_single_select_field', rows]]); - expect(groupByField(rows, rowMap, field)).toEqual(expectRes); - }); - - it('should handle empty selected ids', () => { - const { fields, rowMap } = withTestingData(); - const cell = rowMap['1'] - ?.getMap(YjsEditorKey.data_section) - ?.get(YjsEditorKey.database_row) - ?.get(YjsDatabaseKey.cells) - ?.get('single_select_field'); - cell?.set(YjsDatabaseKey.data, null); - - const field = fields.get('single_select_field'); - const result = groupByField(rows, rowMap, field); - expect(result).toEqual( - new Map([ - ['single_select_field', [{ id: '1', height: 37 }]], - [ - '2', - [ - { id: '2', height: 37 }, - { id: '5', height: 37 }, - { id: '8', height: 37 }, - ], - ], - [ - '3', - [ - { id: '3', height: 37 }, - { id: '6', height: 37 }, - { id: '9', height: 37 }, - ], - ], - [ - '1', - [ - { id: '4', height: 37 }, - { id: '7', height: 37 }, - { id: '10', height: 37 }, - ], - ], - ]), - ); - }); -}); diff --git a/frontend/appflowy_web_app/src/application/database-yjs/__tests__/parse.test.ts b/frontend/appflowy_web_app/src/application/database-yjs/__tests__/parse.test.ts deleted file mode 100644 index 52e6df60e7..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/__tests__/parse.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { parseYDatabaseCellToCell } from '@/application/database-yjs/cell.parse'; -import { expect } from '@jest/globals'; -import { withTestingCheckboxCell, withTestingDateCell } from '@/application/database-yjs/__tests__/withTestingCell'; -import * as Y from 'yjs'; -import { - FieldType, - parseSelectOptionTypeOptions, - parseRelationTypeOption, - parseNumberTypeOptions, -} from '@/application/database-yjs'; -import { YDatabaseField, YDatabaseFieldTypeOption, YjsDatabaseKey } from '@/application/types'; -import { - withNumberTestingField, - withRelationTestingField, -} from '@/application/database-yjs/__tests__/withTestingField'; - -describe('parseYDatabaseCellToCell', () => { - it('should parse a DateTime cell', () => { - const doc = new Y.Doc(); - const cell = withTestingDateCell(); - doc.getMap('cells').set('date_field', cell); - const parsedCell = parseYDatabaseCellToCell(cell); - expect(parsedCell.data).not.toBe(undefined); - expect(parsedCell.createdAt).not.toBe(undefined); - expect(parsedCell.lastModified).not.toBe(undefined); - expect(parsedCell.fieldType).toBe(Number(FieldType.DateTime)); - }); - it('should parse a Checkbox cell', () => { - const doc = new Y.Doc(); - const cell = withTestingCheckboxCell(); - doc.getMap('cells').set('checkbox_field', cell); - const parsedCell = parseYDatabaseCellToCell(cell); - expect(parsedCell.data).toBe(true); - expect(parsedCell.createdAt).not.toBe(undefined); - expect(parsedCell.lastModified).not.toBe(undefined); - expect(parsedCell.fieldType).toBe(Number(FieldType.Checkbox)); - }); -}); - -describe('Select option field parse', () => { - it('should parse select option type options', () => { - const doc = new Y.Doc(); - const field = new Y.Map() as YDatabaseField; - const typeOption = new Y.Map() as YDatabaseFieldTypeOption; - const now = Date.now().toString(); - - field.set(YjsDatabaseKey.name, 'Single Select Field'); - field.set(YjsDatabaseKey.id, 'single_select_field'); - field.set(YjsDatabaseKey.type, String(FieldType.SingleSelect)); - field.set(YjsDatabaseKey.last_modified, now.valueOf()); - field.set(YjsDatabaseKey.type_option, typeOption); - doc.getMap('fields').set('single_select_field', field); - expect(parseSelectOptionTypeOptions(field)).toEqual(null); - }); -}); - -describe('number field parse', () => { - it('should parse number field', () => { - const doc = new Y.Doc(); - const field = withNumberTestingField(); - doc.getMap('fields').set('number_field', field); - expect(parseNumberTypeOptions(field)).toEqual({ - format: 0, - }); - }); -}); - -describe('relation field parse', () => { - it('should parse relation field', () => { - const doc = new Y.Doc(); - const field = withRelationTestingField(); - doc.getMap('fields').set('relation_field', field); - expect(parseRelationTypeOption(field)).toEqual(undefined); - }); -}); diff --git a/frontend/appflowy_web_app/src/application/database-yjs/__tests__/selector.test.tsx b/frontend/appflowy_web_app/src/application/database-yjs/__tests__/selector.test.tsx deleted file mode 100644 index 1cdfd3dbfd..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/__tests__/selector.test.tsx +++ /dev/null @@ -1,272 +0,0 @@ -import { renderHook } from '@testing-library/react'; -import { - useCellSelector, - useFieldSelector, - useFieldsSelector, - useFilterSelector, - useFiltersSelector, - useGroup, - useGroupsSelector, - usePrimaryFieldId, - useRowDataSelector, - useRowMetaSelector, - useRowOrdersSelector, - useRowsByGroup, - useSortSelector, - useSortsSelector, -} from '../selector'; -import { useDatabaseViewId } from '../context'; -import { DatabaseContextProvider } from '@/components/database/DatabaseContext'; -import { withTestingDatabase } from '@/application/database-yjs/__tests__/withTestingData'; -import { expect } from '@jest/globals'; -import { YDoc, YjsDatabaseKey, YjsEditorKey, YSharedRoot } from '@/application/types'; -import * as Y from 'yjs'; -import { withNumberTestingField, withTestingFields } from '@/application/database-yjs/__tests__/withTestingField'; -import { withTestingRows } from '@/application/database-yjs/__tests__/withTestingRows'; - -const wrapperCreator = - (viewId: string, doc: YDoc, rowDocMap: Record) => - ({ children }: { children: React.ReactNode }) => { - return ( - - {children} - - ); - }; - -describe('Database selector', () => { - let wrapper: ({ children }: { children: React.ReactNode }) => JSX.Element; - let rowDocMap: Record; - let doc: YDoc; - - beforeEach(() => { - const data = withTestingDatabase('1'); - - doc = data.doc; - rowDocMap = data.rowDocMap; - wrapper = wrapperCreator('1', doc, rowDocMap); - }); - - it('should select a field', () => { - const { result } = renderHook(() => useFieldSelector('number_field'), { wrapper }); - - const tempDoc = new Y.Doc(); - const field = withNumberTestingField(); - - tempDoc.getMap().set('number_field', field); - - expect(result.current.field?.toJSON()).toEqual(field.toJSON()); - }); - - it('should select all fields', () => { - const { result } = renderHook(() => useFieldsSelector(), { wrapper }); - - expect(result.current.map((item) => item.fieldId)).toEqual(Array.from(withTestingFields().keys())); - }); - - it('should select all filters', () => { - const { result } = renderHook(() => useFiltersSelector(), { wrapper }); - - expect(result.current).toEqual(['filter_multi_select_field']); - }); - - it('should select a filter', () => { - const { result } = renderHook(() => useFilterSelector('filter_multi_select_field'), { wrapper }); - - expect(result.current).toEqual({ - content: '1,3', - condition: 2, - fieldId: 'multi_select_field', - id: 'filter_multi_select_field', - filterType: NaN, - optionIds: ['1', '3'], - }); - }); - - it('should select all sorts', () => { - const { result } = renderHook(() => useSortsSelector(), { wrapper }); - - expect(result.current).toEqual(['sort_asc_text_field']); - }); - - it('should select a sort', () => { - const { result } = renderHook(() => useSortSelector('sort_asc_text_field'), { wrapper }); - - expect(result.current).toEqual({ - fieldId: 'text_field', - id: 'sort_asc_text_field', - condition: 0, - }); - }); - - it('should select all groups', () => { - const { result } = renderHook(() => useGroupsSelector(), { wrapper }); - - expect(result.current).toEqual(['g:single_select_field']); - }); - - it('should select a group', () => { - const { result } = renderHook(() => useGroup('g:single_select_field'), { wrapper }); - - expect(result.current).toEqual({ - fieldId: 'single_select_field', - columns: [ - { - id: '1', - visible: true, - }, - { - id: 'single_select_field', - visible: true, - }, - ], - }); - }); - - it('should select rows by group', () => { - const { result } = renderHook(() => useRowsByGroup('g:single_select_field'), { wrapper }); - - const { fieldId, columns, notFound, groupResult } = result.current; - - expect(fieldId).toEqual('single_select_field'); - expect(columns).toEqual([ - { - id: '1', - visible: true, - }, - { - id: 'single_select_field', - visible: true, - }, - ]); - expect(notFound).toBeFalsy(); - - expect(groupResult).toEqual( - new Map([ - [ - '1', - [ - { id: '1', height: 37 }, - { id: '7', height: 37 }, - ], - ], - [ - '2', - [ - { id: '2', height: 37 }, - { id: '8', height: 37 }, - { id: '5', height: 37 }, - ], - ], - [ - '3', - [ - { id: '9', height: 37 }, - { id: '3', height: 37 }, - { id: '6', height: 37 }, - ], - ], - ]), - ); - }); - - it('should select all row orders', () => { - const { result } = renderHook(() => useRowOrdersSelector(), { wrapper }); - - expect(result.current?.map((item) => item.id).join(',')).toEqual('9,2,3,1,6,8,5,7'); - }); - - it('should select a row data', () => { - const rows = withTestingRows(); - const { result } = renderHook(() => useRowDataSelector(rows[0].id), { wrapper }); - - expect(result.current.row?.toJSON()).toEqual( - rowDocMap[rows[0].id]?.getMap(YjsEditorKey.data_section)?.get(YjsEditorKey.database_row)?.toJSON(), - ); - }); - - it('should select a cell', () => { - const rows = withTestingRows(); - const { result } = renderHook( - () => - useCellSelector({ - rowId: rows[0].id, - fieldId: 'number_field', - }), - { wrapper }, - ); - - expect(result.current).toEqual({ - createdAt: NaN, - data: 123, - fieldType: 1, - lastModified: NaN, - }); - }); - - it('should select a primary field id', () => { - const { result } = renderHook(() => usePrimaryFieldId(), { wrapper }); - - expect(result.current).toEqual('text_field'); - }); - - it('should select a row meta', () => { - const rows = withTestingRows(); - const { result } = renderHook(() => useRowMetaSelector(rows[0].id), { wrapper }); - - expect(result.current?.documentId).not.toBeNull(); - }); - - it('should select view id', () => { - const { result } = renderHook(() => useDatabaseViewId(), { wrapper }); - - expect(result.current).toEqual('1'); - }); - - it('should select all rows if filter is not found', () => { - const view = (doc.get(YjsEditorKey.data_section) as YSharedRoot) - .get(YjsEditorKey.database) - .get(YjsDatabaseKey.views) - .get('1'); - - view.set(YjsDatabaseKey.filters, new Y.Array()); - - const { result } = renderHook(() => useRowOrdersSelector(), { wrapper }); - - expect(result.current?.map((item) => item.id).join(',')).toEqual('9,2,3,4,1,6,10,8,5,7'); - }); - - it('should select original row orders if sorts is not found', () => { - const view = (doc.get(YjsEditorKey.data_section) as YSharedRoot) - .get(YjsEditorKey.database) - .get(YjsDatabaseKey.views) - .get('1'); - - view.set(YjsDatabaseKey.sorts, new Y.Array()); - - const { result } = renderHook(() => useRowOrdersSelector(), { wrapper }); - - expect(result.current?.map((item) => item.id).join(',')).toEqual('1,2,3,5,6,7,8,9'); - }); - - it('should select all rows if filters and sorts are not found', () => { - const view = (doc.get(YjsEditorKey.data_section) as YSharedRoot) - .get(YjsEditorKey.database) - .get(YjsDatabaseKey.views) - .get('1'); - - view.set(YjsDatabaseKey.filters, new Y.Array()); - view.set(YjsDatabaseKey.sorts, new Y.Array()); - - const { result } = renderHook(() => useRowOrdersSelector(), { wrapper }); - - expect(result.current?.map((item) => item.id).join(',')).toEqual('1,2,3,4,5,6,7,8,9,10'); - }); -}); diff --git a/frontend/appflowy_web_app/src/application/database-yjs/__tests__/sort.test.ts b/frontend/appflowy_web_app/src/application/database-yjs/__tests__/sort.test.ts deleted file mode 100644 index ce41777e26..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/__tests__/sort.test.ts +++ /dev/null @@ -1,397 +0,0 @@ -import { Row } from '@/application/database-yjs'; -import { withTestingData } from '@/application/database-yjs/__tests__/withTestingData'; -import { withTestingRows } from '@/application/database-yjs/__tests__/withTestingRows'; -import { - withCheckboxSort, - withChecklistSort, - withCreatedAtSort, - withDateTimeSort, - withLastModifiedSort, - withMultiSelectOptionSort, - withNumberSort, - withRichTextSort, - withSingleSelectOptionSort, - withUrlSort, -} from '@/application/database-yjs/__tests__/withTestingSorts'; -import { - withCheckboxTestingField, - withDateTimeTestingField, - withNumberTestingField, - withRichTextTestingField, - withSelectOptionTestingField, - withURLTestingField, - withChecklistTestingField, - withRelationTestingField, -} from './withTestingField'; -import { sortBy, parseCellDataForSort } from '../sort'; -import * as Y from 'yjs'; -import { expect } from '@jest/globals'; -import { RowId, YDoc, YjsDatabaseKey, YjsEditorKey } from '@/application/types'; - -describe('parseCellDataForSort', () => { - it('should parse data correctly based on field type', () => { - const doc = new Y.Doc(); - const field = withNumberTestingField(); - doc.getMap().set('field', field); - const data = 42; - - const result = parseCellDataForSort(field, data); - - expect(result).toEqual(data); - }); - - it('should return default value for empty rich text', () => { - const doc = new Y.Doc(); - const field = withRichTextTestingField(); - doc.getMap().set('field', field); - const data = ''; - - const result = parseCellDataForSort(field, data); - - expect(result).toEqual('\uFFFF'); - }); - - it('should return default value for empty URL', () => { - const doc = new Y.Doc(); - const field = withURLTestingField(); - doc.getMap().set('field', field); - const data = ''; - - const result = parseCellDataForSort(field, data); - - expect(result).toBe('\uFFFF'); - }); - - it('should return data for non-empty rich text', () => { - const doc = new Y.Doc(); - const field = withRichTextTestingField(); - doc.getMap().set('field', field); - const data = 'Hello, world!'; - - const result = parseCellDataForSort(field, data); - - expect(result).toBe(data); - }); - - it('should parse checkbox data correctly', () => { - const doc = new Y.Doc(); - const field = withCheckboxTestingField(); - doc.getMap().set('field', field); - const data = 'Yes'; - - const result = parseCellDataForSort(field, data); - - expect(result).toBe(true); - - const noData = 'No'; - const noResult = parseCellDataForSort(field, noData); - expect(noResult).toBe(false); - }); - - it('should parse DateTime data correctly', () => { - const doc = new Y.Doc(); - const field = withDateTimeTestingField(); - doc.getMap().set('field', field); - const data = '1633046400000'; - - const result = parseCellDataForSort(field, data); - - expect(result).toBe(Number(data)); - }); - - it('should parse SingleSelect data correctly', () => { - const doc = new Y.Doc(); - const field = withSelectOptionTestingField(); - doc.getMap().set('field', field); - const data = '1'; - - const result = parseCellDataForSort(field, data); - - expect(result).toBe('Option 1'); - }); - - it('should parse MultiSelect data correctly', () => { - const doc = new Y.Doc(); - const field = withSelectOptionTestingField(); - doc.getMap().set('field', field); - const data = '1,2'; - - const result = parseCellDataForSort(field, data); - - expect(result).toBe('Option 1, Option 2'); - }); - - it('should parse Checklist data correctly', () => { - const doc = new Y.Doc(); - const field = withChecklistTestingField(); - doc.getMap().set('field', field); - const data = '[]'; - - const result = parseCellDataForSort(field, data); - - expect(result).toBe(0); - }); - - it('should return empty string for Relation field', () => { - const doc = new Y.Doc(); - const field = withRelationTestingField(); - doc.getMap().set('field', field); - const data = ''; - - const result = parseCellDataForSort(field, data); - - expect(result).toBe(''); - }); -}); - -describe('Database sortBy', () => { - let rows: Row[]; - - beforeEach(() => { - rows = withTestingRows(); - }); - - it('should not sort rows if no sort is provided', () => { - const { sorts, fields, rowMap } = withTestingData(); - - const sortedRows = sortBy(rows, sorts, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(sortedRows).toBe('1,2,3,4,5,6,7,8,9,10'); - }); - - it('should not sort rows if no rows are provided', () => { - const { sorts, fields } = withTestingData(); - const rowMap: Record = {}; - const sortedRows = sortBy(rows, sorts, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(sortedRows).toBe('1,2,3,4,5,6,7,8,9,10'); - }); - - it('should return default data if rowMeta is not found', () => { - const { sorts, fields, rowMap } = withTestingData(); - const sort = withNumberSort(); - sorts.push([sort]); - delete rowMap['1']; - - const sortedRows = sortBy(rows, sorts, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(sortedRows).toBe('1,2,3,4,5,6,7,8,9,10'); - }); - - it('should return default data if cell is not found', () => { - const { sorts, fields, rowMap } = withTestingData(); - const sort = withNumberSort(); - sorts.push([sort]); - const rowDoc = rowMap['1']; - rowDoc - ?.getMap(YjsEditorKey.data_section) - .get(YjsEditorKey.database_row) - ?.get(YjsDatabaseKey.cells) - .delete('number_field'); - - const sortedRows = sortBy(rows, sorts, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(sortedRows).toBe('1,2,3,4,5,6,7,8,9,10'); - }); - - it('should sort by number field in ascending order', () => { - const { sorts, fields, rowMap } = withTestingData(); - const sort = withNumberSort(); - sorts.push([sort]); - - const sortedRows = sortBy(rows, sorts, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(sortedRows).toBe('1,2,3,4,5,6,7,8,9,10'); - }); - - it('should sort by number field in descending order', () => { - const { sorts, fields, rowMap } = withTestingData(); - const sort = withNumberSort(false); - sorts.push([sort]); - - const sortedRows = sortBy(rows, sorts, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(sortedRows).toBe('10,9,8,7,6,5,4,3,2,1'); - }); - - it('should sort by rich text field in ascending order', () => { - const { sorts, fields, rowMap } = withTestingData(); - const sort = withRichTextSort(); - sorts.push([sort]); - - const sortedRows = sortBy(rows, sorts, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(sortedRows).toBe('9,2,3,4,1,6,10,8,5,7'); - }); - - it('should sort by rich text field in descending order', () => { - const { sorts, fields, rowMap } = withTestingData(); - const sort = withRichTextSort(false); - sorts.push([sort]); - - const sortedRows = sortBy(rows, sorts, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(sortedRows).toBe('7,5,8,10,6,1,4,3,2,9'); - }); - - it('should sort by url field in ascending order', () => { - const { sorts, fields, rowMap } = withTestingData(); - const sort = withUrlSort(); - sorts.push([sort]); - - const sortedRows = sortBy(rows, sorts, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(sortedRows).toBe('1,10,2,3,4,5,6,7,8,9'); - }); - - it('should sort by url field in descending order', () => { - const { sorts, fields, rowMap } = withTestingData(); - const sort = withUrlSort(false); - sorts.push([sort]); - - const sortedRows = sortBy(rows, sorts, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(sortedRows).toBe('9,8,7,6,5,4,3,2,10,1'); - }); - - it('should sort by checkbox field in ascending order', () => { - const { sorts, fields, rowMap } = withTestingData(); - const sort = withCheckboxSort(); - sorts.push([sort]); - - const sortedRows = sortBy(rows, sorts, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(sortedRows).toBe('2,4,6,8,10,1,3,5,7,9'); - }); - - it('should sort by checkbox field in descending order', () => { - const { sorts, fields, rowMap } = withTestingData(); - const sort = withCheckboxSort(false); - sorts.push([sort]); - - const sortedRows = sortBy(rows, sorts, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(sortedRows).toBe('1,3,5,7,9,2,4,6,8,10'); - }); - - it('should sort by DateTime field in ascending order', () => { - const { sorts, fields, rowMap } = withTestingData(); - const sort = withDateTimeSort(); - sorts.push([sort]); - - const sortedRows = sortBy(rows, sorts, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(sortedRows).toBe('1,2,3,4,5,6,7,8,9,10'); - }); - - it('should sort by DateTime field in descending order', () => { - const { sorts, fields, rowMap } = withTestingData(); - const sort = withDateTimeSort(false); - sorts.push([sort]); - - const sortedRows = sortBy(rows, sorts, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(sortedRows).toBe('10,9,8,7,6,5,4,3,2,1'); - }); - - it('should sort by SingleSelect field in ascending order', () => { - const { sorts, fields, rowMap } = withTestingData(); - const sort = withSingleSelectOptionSort(); - sorts.push([sort]); - - const sortedRows = sortBy(rows, sorts, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(sortedRows).toBe('1,4,7,10,2,5,8,3,6,9'); - }); - - it('should sort by SingleSelect field in descending order', () => { - const { sorts, fields, rowMap } = withTestingData(); - const sort = withSingleSelectOptionSort(false); - sorts.push([sort]); - - const sortedRows = sortBy(rows, sorts, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(sortedRows).toBe('3,6,9,2,5,8,1,4,7,10'); - }); - - it('should sort by MultiSelect field in ascending order', () => { - const { sorts, fields, rowMap } = withTestingData(); - const sort = withMultiSelectOptionSort(); - sorts.push([sort]); - - const sortedRows = sortBy(rows, sorts, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(sortedRows).toBe('1,7,5,3,6,9,4,10,2,8'); - }); - - it('should sort by MultiSelect field in descending order', () => { - const { sorts, fields, rowMap } = withTestingData(); - const sort = withMultiSelectOptionSort(false); - sorts.push([sort]); - - const sortedRows = sortBy(rows, sorts, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(sortedRows).toBe('2,8,4,10,3,6,9,5,1,7'); - }); - - it('should sort by Checklist field in ascending order', () => { - const { sorts, fields, rowMap } = withTestingData(); - const sort = withChecklistSort(); - sorts.push([sort]); - - const sortedRows = sortBy(rows, sorts, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(sortedRows).toBe('4,10,1,2,5,6,7,8,3,9'); - }); - - it('should sort by Checklist field in descending order', () => { - const { sorts, fields, rowMap } = withTestingData(); - const sort = withChecklistSort(false); - sorts.push([sort]); - - const sortedRows = sortBy(rows, sorts, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(sortedRows).toBe('3,9,1,2,5,6,7,8,4,10'); - }); - - it('should sort by CreatedAt field in ascending order', () => { - const { sorts, fields, rowMap } = withTestingData(); - const sort = withCreatedAtSort(); - sorts.push([sort]); - - const sortedRows = sortBy(rows, sorts, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(sortedRows).toBe('1,2,3,4,5,6,7,8,9,10'); - }); - - it('should sort by LastEditedTime field', () => { - const { sorts, fields, rowMap } = withTestingData(); - const sort = withLastModifiedSort(); - sorts.push([sort]); - const sortedRows = sortBy(rows, sorts, fields, rowMap) - .map((row) => row.id) - .join(','); - expect(sortedRows).toBe('1,2,3,4,5,6,7,8,9,10'); - }); -}); diff --git a/frontend/appflowy_web_app/src/application/database-yjs/__tests__/withTestingCell.ts b/frontend/appflowy_web_app/src/application/database-yjs/__tests__/withTestingCell.ts deleted file mode 100644 index de0f7d68e3..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/__tests__/withTestingCell.ts +++ /dev/null @@ -1,43 +0,0 @@ -import * as Y from 'yjs'; -import { YDatabaseCell, YjsDatabaseKey } from '@/application/types'; -import { FieldType } from '@/application/database-yjs'; - -export function withTestingDateCell() { - const cell = new Y.Map() as YDatabaseCell; - - cell.set(YjsDatabaseKey.id, 'date_field'); - cell.set(YjsDatabaseKey.data, Date.now()); - cell.set(YjsDatabaseKey.field_type, Number(FieldType.DateTime)); - cell.set(YjsDatabaseKey.created_at, Date.now()); - cell.set(YjsDatabaseKey.last_modified, Date.now()); - cell.set(YjsDatabaseKey.end_timestamp, Date.now() + 1000); - cell.set(YjsDatabaseKey.include_time, true); - cell.set(YjsDatabaseKey.is_range, true); - cell.set(YjsDatabaseKey.reminder_id, 'reminderId'); - - return cell; -} - -export function withTestingCheckboxCell() { - const cell = new Y.Map() as YDatabaseCell; - - cell.set(YjsDatabaseKey.id, 'checkbox_field'); - cell.set(YjsDatabaseKey.data, 'Yes'); - cell.set(YjsDatabaseKey.field_type, Number(FieldType.Checkbox)); - cell.set(YjsDatabaseKey.created_at, Date.now()); - cell.set(YjsDatabaseKey.last_modified, Date.now()); - - return cell; -} - -export function withTestingSingleOptionCell() { - const cell = new Y.Map() as YDatabaseCell; - - cell.set(YjsDatabaseKey.id, 'single_select_field'); - cell.set(YjsDatabaseKey.data, 'optionId'); - cell.set(YjsDatabaseKey.field_type, Number(FieldType.SingleSelect)); - cell.set(YjsDatabaseKey.created_at, Date.now()); - cell.set(YjsDatabaseKey.last_modified, Date.now()); - - return cell; -} diff --git a/frontend/appflowy_web_app/src/application/database-yjs/__tests__/withTestingData.ts b/frontend/appflowy_web_app/src/application/database-yjs/__tests__/withTestingData.ts deleted file mode 100644 index 282590eb35..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/__tests__/withTestingData.ts +++ /dev/null @@ -1,181 +0,0 @@ -import { - RowId, - YDatabase, - YDatabaseFields, - YDatabaseFilters, - YDatabaseGroup, - YDatabaseGroupColumn, - YDatabaseGroupColumns, - YDatabaseLayoutSettings, - YDatabaseSorts, - YDatabaseView, - YDatabaseViews, - YDoc, - YjsDatabaseKey, - YjsEditorKey, -} from '@/application/types'; -import { withTestingFields } from '@/application/database-yjs/__tests__/withTestingField'; -import { - withTestingRowData, - withTestingRowDataMap, - withTestingRows, -} from '@/application/database-yjs/__tests__/withTestingRows'; -import * as Y from 'yjs'; -import { withMultiSelectOptionFilter } from '@/application/database-yjs/__tests__/withTestingFilters'; -import { withRichTextSort } from '@/application/database-yjs/__tests__/withTestingSorts'; -import { metaIdFromRowId, RowMetaKey } from '@/application/database-yjs'; - -export function withTestingData () { - const doc = new Y.Doc(); - const sharedRoot = doc.getMap(); - const fields = withTestingFields() as YDatabaseFields; - - sharedRoot.set('fields', fields); - - const rowMap = withTestingRowDataMap(); - - sharedRoot.set('rows', rowMap); - - const sorts = new Y.Array() as YDatabaseSorts; - - sharedRoot.set('sorts', sorts); - - const filters = new Y.Array() as YDatabaseFilters; - - sharedRoot.set('filters', filters); - - return { - fields, - rowMap, - sorts, - filters, - doc, - }; -} - -export function withTestingDatabase (viewId: string) { - const doc = new Y.Doc(); - const sharedRoot = doc.getMap(YjsEditorKey.data_section); - const database = new Y.Map() as YDatabase; - - sharedRoot.set(YjsEditorKey.database, database); - - const fields = withTestingFields() as YDatabaseFields; - - database.set(YjsDatabaseKey.fields, fields); - database.set(YjsDatabaseKey.id, viewId); - - const metas = new Y.Map(); - - database.set(YjsDatabaseKey.metas, metas); - metas.set(YjsDatabaseKey.iid, viewId); - - const views = new Y.Map() as YDatabaseViews; - - database.set(YjsDatabaseKey.views, views); - - const view = new Y.Map() as YDatabaseView; - - views.set('1', view); - view.set(YjsDatabaseKey.id, viewId); - view.set(YjsDatabaseKey.layout, 0); - view.set(YjsDatabaseKey.name, 'View 1'); - view.set(YjsDatabaseKey.database_id, viewId); - - const layoutSetting = new Y.Map() as YDatabaseLayoutSettings; - - const calendarSetting = new Y.Map(); - - calendarSetting.set(YjsDatabaseKey.field_id, 'date_field'); - layoutSetting.set('2', calendarSetting); - - view.set(YjsDatabaseKey.layout_settings, layoutSetting); - - const filters = new Y.Array() as YDatabaseFilters; - const filter = withMultiSelectOptionFilter(); - - filters.push([filter]); - - const sorts = new Y.Array() as YDatabaseSorts; - const sort = withRichTextSort(); - - sorts.push([sort]); - - const groups = new Y.Array(); - const group = new Y.Map() as YDatabaseGroup; - - groups.push([group]); - group.set(YjsDatabaseKey.id, 'g:single_select_field'); - group.set(YjsDatabaseKey.field_id, 'single_select_field'); - group.set(YjsDatabaseKey.type, '3'); - group.set(YjsDatabaseKey.content, ''); - - const groupColumns = new Y.Array() as YDatabaseGroupColumns; - - group.set(YjsDatabaseKey.groups, groupColumns); - - const column1 = new Y.Map() as YDatabaseGroupColumn; - const column2 = new Y.Map() as YDatabaseGroupColumn; - - column1.set(YjsDatabaseKey.id, '1'); - column1.set(YjsDatabaseKey.visible, true); - column2.set(YjsDatabaseKey.id, 'single_select_field'); - column2.set(YjsDatabaseKey.visible, true); - - groupColumns.push([column1]); - groupColumns.push([column2]); - - view.set(YjsDatabaseKey.filters, filters); - view.set(YjsDatabaseKey.sorts, sorts); - view.set(YjsDatabaseKey.groups, groups); - - const fieldSettings = new Y.Map(); - const fieldOrder = new Y.Array(); - const rowOrders = new Y.Array(); - - fields.forEach((field) => { - const setting = new Y.Map(); - - const fieldId = field.get(YjsDatabaseKey.id); - - if (fieldId === 'text_field') { - field.set(YjsDatabaseKey.is_primary, true); - } - - fieldOrder.push([fieldId]); - fieldSettings.set(fieldId, setting); - setting.set(YjsDatabaseKey.visibility, 0); - }); - const rows = withTestingRows(); - - rows.forEach(({ id, height }) => { - const row = new Y.Map(); - - row.set(YjsDatabaseKey.id, id); - row.set(YjsDatabaseKey.height, height); - rowOrders.push([row]); - }); - - view.set(YjsDatabaseKey.field_settings, fieldSettings); - view.set(YjsDatabaseKey.field_orders, fieldOrder); - view.set(YjsDatabaseKey.row_orders, rowOrders); - - const rowMap: Record = {}; - - rows.forEach((row, index) => { - const rowDoc = new Y.Doc(); - const rowData = withTestingRowData(row.id, index); - const rowMeta = new Y.Map(); - const parser = metaIdFromRowId('281e76fb-712e-59e2-8370-678bf0788355'); - - rowMeta.set(parser(RowMetaKey.IconId), '😊'); - rowDoc.getMap(YjsEditorKey.data_section).set(YjsEditorKey.meta, rowMeta); - rowDoc.getMap(YjsEditorKey.data_section).set(YjsEditorKey.database_row, rowData); - rowMap[row.id] = rowDoc; - }); - - return { - rowDocMap: rowMap, - doc: doc as YDoc, - }; -} diff --git a/frontend/appflowy_web_app/src/application/database-yjs/__tests__/withTestingField.ts b/frontend/appflowy_web_app/src/application/database-yjs/__tests__/withTestingField.ts deleted file mode 100644 index e9329f341d..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/__tests__/withTestingField.ts +++ /dev/null @@ -1,204 +0,0 @@ -import { - YDatabaseField, - YDatabaseFieldTypeOption, - YjsDatabaseKey, - YMapFieldTypeOption, -} from '@/application/types'; -import { FieldType } from '@/application/database-yjs'; -import { SelectOptionColor } from '@/application/database-yjs/fields/select-option'; -import * as Y from 'yjs'; - -export function withTestingFields() { - const fields = new Y.Map(); - const textField = withRichTextTestingField(); - - fields.set('text_field', textField); - const numberField = withNumberTestingField(); - - fields.set('number_field', numberField); - - const checkboxField = withCheckboxTestingField(); - - fields.set('checkbox_field', checkboxField); - - const dateTimeField = withDateTimeTestingField(); - - fields.set('date_field', dateTimeField); - - const singleSelectField = withSelectOptionTestingField(); - - fields.set('single_select_field', singleSelectField); - const multipleSelectField = withSelectOptionTestingField(true); - - fields.set('multi_select_field', multipleSelectField); - - const urlField = withURLTestingField(); - - fields.set('url_field', urlField); - - const checklistField = withChecklistTestingField(); - - fields.set('checklist_field', checklistField); - - const createdAtField = withCreatedAtTestingField(); - - fields.set('created_at_field', createdAtField); - - const lastModifiedField = withLastModifiedTestingField(); - - fields.set('last_modified_field', lastModifiedField); - - return fields; -} - -export function withRichTextTestingField() { - const field = new Y.Map() as YDatabaseField; - const now = Date.now().toString(); - - field.set(YjsDatabaseKey.name, 'Rich Text Field'); - field.set(YjsDatabaseKey.id, 'text_field'); - field.set(YjsDatabaseKey.type, String(FieldType.RichText)); - field.set(YjsDatabaseKey.last_modified, now.valueOf()); - - return field; -} - -export function withNumberTestingField() { - const field = new Y.Map() as YDatabaseField; - - field.set(YjsDatabaseKey.name, 'Number Field'); - field.set(YjsDatabaseKey.id, 'number_field'); - field.set(YjsDatabaseKey.type, String(FieldType.Number)); - const typeOption = new Y.Map() as YDatabaseFieldTypeOption; - - const numberTypeOption = new Y.Map() as YMapFieldTypeOption; - - typeOption.set(String(FieldType.Number), numberTypeOption); - numberTypeOption.set(YjsDatabaseKey.format, '0'); - field.set(YjsDatabaseKey.type_option, typeOption); - - return field; -} - -export function withRelationTestingField() { - const field = new Y.Map() as YDatabaseField; - const typeOption = new Y.Map() as YDatabaseFieldTypeOption; - const now = Date.now().toString(); - - field.set(YjsDatabaseKey.name, 'Relation Field'); - field.set(YjsDatabaseKey.id, 'relation_field'); - field.set(YjsDatabaseKey.type, String(FieldType.Relation)); - field.set(YjsDatabaseKey.last_modified, now.valueOf()); - field.set(YjsDatabaseKey.type_option, typeOption); - - return field; -} - -export function withCheckboxTestingField() { - const field = new Y.Map() as YDatabaseField; - const now = Date.now().toString(); - - field.set(YjsDatabaseKey.name, 'Checkbox Field'); - field.set(YjsDatabaseKey.id, 'checkbox_field'); - field.set(YjsDatabaseKey.type, String(FieldType.Checkbox)); - field.set(YjsDatabaseKey.last_modified, now.valueOf()); - - return field; -} - -export function withDateTimeTestingField() { - const field = new Y.Map() as YDatabaseField; - const typeOption = new Y.Map() as YDatabaseFieldTypeOption; - const now = Date.now().toString(); - - field.set(YjsDatabaseKey.name, 'DateTime Field'); - field.set(YjsDatabaseKey.id, 'date_field'); - field.set(YjsDatabaseKey.type, String(FieldType.DateTime)); - field.set(YjsDatabaseKey.last_modified, now.valueOf()); - field.set(YjsDatabaseKey.type_option, typeOption); - - const dateTypeOption = new Y.Map() as YMapFieldTypeOption; - - typeOption.set(String(FieldType.DateTime), dateTypeOption); - - dateTypeOption.set(YjsDatabaseKey.time_format, '0'); - dateTypeOption.set(YjsDatabaseKey.date_format, '0'); - return field; -} - -export function withURLTestingField() { - const field = new Y.Map() as YDatabaseField; - const now = Date.now().toString(); - - field.set(YjsDatabaseKey.name, 'URL Field'); - field.set(YjsDatabaseKey.id, 'url_field'); - field.set(YjsDatabaseKey.type, String(FieldType.URL)); - field.set(YjsDatabaseKey.last_modified, now.valueOf()); - - return field; -} - -export function withSelectOptionTestingField(isMultiple = false) { - const field = new Y.Map() as YDatabaseField; - const typeOption = new Y.Map() as YDatabaseFieldTypeOption; - const now = Date.now().toString(); - - field.set(YjsDatabaseKey.name, 'Single Select Field'); - field.set(YjsDatabaseKey.id, isMultiple ? 'multi_select_field' : 'single_select_field'); - field.set(YjsDatabaseKey.type, String(FieldType.SingleSelect)); - field.set(YjsDatabaseKey.last_modified, now.valueOf()); - field.set(YjsDatabaseKey.type_option, typeOption); - - const selectTypeOption = new Y.Map() as YMapFieldTypeOption; - - typeOption.set(String(FieldType.SingleSelect), selectTypeOption); - - selectTypeOption.set( - YjsDatabaseKey.content, - JSON.stringify({ - disable_color: false, - options: [ - { id: '1', name: 'Option 1', color: SelectOptionColor.Purple }, - { id: '2', name: 'Option 2', color: SelectOptionColor.Pink }, - { id: '3', name: 'Option 3', color: SelectOptionColor.LightPink }, - ], - }) - ); - return field; -} - -export function withChecklistTestingField() { - const field = new Y.Map() as YDatabaseField; - const now = Date.now().toString(); - - field.set(YjsDatabaseKey.name, 'Checklist Field'); - field.set(YjsDatabaseKey.id, 'checklist_field'); - field.set(YjsDatabaseKey.type, String(FieldType.Checklist)); - field.set(YjsDatabaseKey.last_modified, now.valueOf()); - - return field; -} - -export function withCreatedAtTestingField() { - const field = new Y.Map() as YDatabaseField; - const now = Date.now().toString(); - - field.set(YjsDatabaseKey.name, 'Created At Field'); - field.set(YjsDatabaseKey.id, 'created_at_field'); - field.set(YjsDatabaseKey.type, String(FieldType.CreatedTime)); - field.set(YjsDatabaseKey.last_modified, now.valueOf()); - - return field; -} - -export function withLastModifiedTestingField() { - const field = new Y.Map() as YDatabaseField; - const now = Date.now().toString(); - - field.set(YjsDatabaseKey.name, 'Last Modified Field'); - field.set(YjsDatabaseKey.id, 'last_modified_field'); - field.set(YjsDatabaseKey.type, String(FieldType.LastEditedTime)); - field.set(YjsDatabaseKey.last_modified, now.valueOf()); - - return field; -} diff --git a/frontend/appflowy_web_app/src/application/database-yjs/__tests__/withTestingFilters.ts b/frontend/appflowy_web_app/src/application/database-yjs/__tests__/withTestingFilters.ts deleted file mode 100644 index 57a64402f8..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/__tests__/withTestingFilters.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { YDatabaseFilter, YjsDatabaseKey } from '@/application/types'; -import * as Y from 'yjs'; -import * as filtersJson from './fixtures/filters.json'; - -export function withRichTextFilter() { - const filter = new Y.Map() as YDatabaseFilter; - - filter.set(YjsDatabaseKey.id, 'filter_text_field'); - filter.set(YjsDatabaseKey.field_id, filtersJson.filter_text_field.field_id); - filter.set(YjsDatabaseKey.condition, filtersJson.filter_text_field.condition); - filter.set(YjsDatabaseKey.content, filtersJson.filter_text_field.content); - return filter; -} - -export function withUrlFilter() { - const filter = new Y.Map() as YDatabaseFilter; - - filter.set(YjsDatabaseKey.id, 'filter_url_field'); - filter.set(YjsDatabaseKey.field_id, filtersJson.filter_url_field.field_id); - filter.set(YjsDatabaseKey.condition, filtersJson.filter_url_field.condition); - filter.set(YjsDatabaseKey.content, filtersJson.filter_url_field.content); - return filter; -} - -export function withNumberFilter() { - const filter = new Y.Map() as YDatabaseFilter; - - filter.set(YjsDatabaseKey.id, 'filter_number_field'); - filter.set(YjsDatabaseKey.field_id, filtersJson.filter_number_field.field_id); - filter.set(YjsDatabaseKey.condition, filtersJson.filter_number_field.condition); - filter.set(YjsDatabaseKey.content, filtersJson.filter_number_field.content); - return filter; -} - -export function withCheckboxFilter() { - const filter = new Y.Map() as YDatabaseFilter; - - filter.set(YjsDatabaseKey.id, 'filter_checkbox_field'); - filter.set(YjsDatabaseKey.field_id, filtersJson.filter_checkbox_field.field_id); - filter.set(YjsDatabaseKey.condition, filtersJson.filter_checkbox_field.condition); - filter.set(YjsDatabaseKey.content, ''); - return filter; -} - -export function withChecklistFilter() { - const filter = new Y.Map() as YDatabaseFilter; - - filter.set(YjsDatabaseKey.id, 'filter_checklist_field'); - filter.set(YjsDatabaseKey.field_id, filtersJson.filter_checklist_field.field_id); - filter.set(YjsDatabaseKey.condition, filtersJson.filter_checklist_field.condition); - filter.set(YjsDatabaseKey.content, ''); - return filter; -} - -export function withSingleSelectOptionFilter() { - const filter = new Y.Map() as YDatabaseFilter; - - filter.set(YjsDatabaseKey.id, 'filter_single_select_field'); - filter.set(YjsDatabaseKey.field_id, filtersJson.filter_single_select_field.field_id); - filter.set(YjsDatabaseKey.condition, filtersJson.filter_single_select_field.condition); - filter.set(YjsDatabaseKey.content, filtersJson.filter_single_select_field.content); - return filter; -} - -export function withMultiSelectOptionFilter() { - const filter = new Y.Map() as YDatabaseFilter; - - filter.set(YjsDatabaseKey.id, 'filter_multi_select_field'); - filter.set(YjsDatabaseKey.field_id, filtersJson.filter_multi_select_field.field_id); - filter.set(YjsDatabaseKey.condition, filtersJson.filter_multi_select_field.condition); - filter.set(YjsDatabaseKey.content, filtersJson.filter_multi_select_field.content); - return filter; -} - -export function withDateTimeFilter() { - const filter = new Y.Map() as YDatabaseFilter; - - filter.set(YjsDatabaseKey.id, 'filter_date_field'); - filter.set(YjsDatabaseKey.field_id, filtersJson.filter_date_field.field_id); - filter.set(YjsDatabaseKey.condition, filtersJson.filter_date_field.condition); - filter.set(YjsDatabaseKey.content, filtersJson.filter_date_field.content); - return filter; -} diff --git a/frontend/appflowy_web_app/src/application/database-yjs/__tests__/withTestingRows.ts b/frontend/appflowy_web_app/src/application/database-yjs/__tests__/withTestingRows.ts deleted file mode 100644 index bffaccf28a..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/__tests__/withTestingRows.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { - RowId, - YDatabaseCell, - YDatabaseCells, - YDatabaseRow, - YDoc, - YjsDatabaseKey, - YjsEditorKey, -} from '@/application/types'; -import { FieldType, Row } from '@/application/database-yjs'; -import * as Y from 'yjs'; -import * as rowsJson from './fixtures/rows.json'; - -export function withTestingRows (): Row[] { - return rowsJson.map((row) => { - return { - id: row.id, - height: 37, - }; - }); -} - -export function withTestingRowDataMap (): Record { - const folder: Record = {}; - const rows = withTestingRows(); - - rows.forEach((row, index) => { - const rowDoc = new Y.Doc(); - const rowData = withTestingRowData(row.id, index); - - rowDoc.getMap(YjsEditorKey.data_section).set(YjsEditorKey.database_row, rowData); - folder[row.id] = rowDoc; - }); - - return folder; -} - -export function withTestingRowData (id: string, index: number) { - const rowData = new Y.Map() as YDatabaseRow; - - rowData.set(YjsDatabaseKey.id, id); - rowData.set(YjsDatabaseKey.height, 37); - rowData.set(YjsDatabaseKey.last_modified, Date.now() + index * 1000); - rowData.set(YjsDatabaseKey.created_at, Date.now() + index * 1000); - - const cells = new Y.Map() as YDatabaseCells; - - const textFieldCell = withTestingCell(rowsJson[index].cells.text_field.data); - - textFieldCell.set(YjsDatabaseKey.field_type, Number(FieldType.RichText)); - cells.set('text_field', textFieldCell); - - const numberFieldCell = withTestingCell(rowsJson[index].cells.number_field.data); - - numberFieldCell.set(YjsDatabaseKey.field_type, Number(FieldType.Number)); - cells.set('number_field', numberFieldCell); - - const checkboxFieldCell = withTestingCell(rowsJson[index].cells.checkbox_field.data); - - checkboxFieldCell.set(YjsDatabaseKey.field_type, Number(FieldType.Checkbox)); - cells.set('checkbox_field', checkboxFieldCell); - - const dateTimeFieldCell = withTestingCell(rowsJson[index].cells.date_field.data); - - dateTimeFieldCell.set(YjsDatabaseKey.field_type, Number(FieldType.DateTime)); - cells.set('date_field', dateTimeFieldCell); - - const urlFieldCell = withTestingCell(rowsJson[index].cells.url_field.data); - - urlFieldCell.set(YjsDatabaseKey.field_type, Number(FieldType.URL)); - cells.set('url_field', urlFieldCell); - - const singleSelectFieldCell = withTestingCell(rowsJson[index].cells.single_select_field.data); - - singleSelectFieldCell.set(YjsDatabaseKey.field_type, Number(FieldType.SingleSelect)); - cells.set('single_select_field', singleSelectFieldCell); - - const multiSelectFieldCell = withTestingCell(rowsJson[index].cells.multi_select_field.data); - - multiSelectFieldCell.set(YjsDatabaseKey.field_type, Number(FieldType.MultiSelect)); - cells.set('multi_select_field', multiSelectFieldCell); - - const checlistFieldCell = withTestingCell(rowsJson[index].cells.checklist_field.data); - - checlistFieldCell.set(YjsDatabaseKey.field_type, Number(FieldType.Checklist)); - cells.set('checklist_field', checlistFieldCell); - - rowData.set(YjsDatabaseKey.cells, cells); - return rowData; -} - -export function withTestingCell (cellData: string | number) { - const cell = new Y.Map() as YDatabaseCell; - - cell.set(YjsDatabaseKey.data, cellData); - return cell; -} diff --git a/frontend/appflowy_web_app/src/application/database-yjs/__tests__/withTestingSorts.ts b/frontend/appflowy_web_app/src/application/database-yjs/__tests__/withTestingSorts.ts deleted file mode 100644 index d9421d5e7c..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/__tests__/withTestingSorts.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { YDatabaseSort, YjsDatabaseKey } from '@/application/types'; -import * as Y from 'yjs'; -import * as sortsJson from './fixtures/sorts.json'; - -export function withRichTextSort(isAscending: boolean = true) { - const sort = new Y.Map() as YDatabaseSort; - const sortJSON = isAscending ? sortsJson.sort_asc_text_field : sortsJson.sort_desc_text_field; - - sort.set(YjsDatabaseKey.id, sortJSON.id); - sort.set(YjsDatabaseKey.field_id, sortJSON.field_id); - sort.set(YjsDatabaseKey.condition, sortJSON.condition === 'asc' ? '0' : '1'); - - return sort; -} - -export function withUrlSort(isAscending: boolean = true) { - const sort = new Y.Map() as YDatabaseSort; - const sortJSON = isAscending ? sortsJson.sort_asc_url_field : sortsJson.sort_desc_url_field; - - sort.set(YjsDatabaseKey.id, sortJSON.id); - sort.set(YjsDatabaseKey.field_id, sortJSON.field_id); - sort.set(YjsDatabaseKey.condition, sortJSON.condition === 'asc' ? '0' : '1'); - - return sort; -} - -export function withNumberSort(isAscending: boolean = true) { - const sort = new Y.Map() as YDatabaseSort; - const sortJSON = isAscending ? sortsJson.sort_asc_number_field : sortsJson.sort_desc_number_field; - - sort.set(YjsDatabaseKey.id, sortJSON.id); - sort.set(YjsDatabaseKey.field_id, sortJSON.field_id); - sort.set(YjsDatabaseKey.condition, sortJSON.condition === 'asc' ? '0' : '1'); - - return sort; -} - -export function withCheckboxSort(isAscending: boolean = true) { - const sort = new Y.Map() as YDatabaseSort; - const sortJSON = isAscending ? sortsJson.sort_asc_checkbox_field : sortsJson.sort_desc_checkbox_field; - - sort.set(YjsDatabaseKey.id, sortJSON.id); - sort.set(YjsDatabaseKey.field_id, sortJSON.field_id); - sort.set(YjsDatabaseKey.condition, sortJSON.condition === 'asc' ? '0' : '1'); - - return sort; -} - -export function withDateTimeSort(isAscending: boolean = true) { - const sort = new Y.Map() as YDatabaseSort; - const sortJSON = isAscending ? sortsJson.sort_asc_date_field : sortsJson.sort_desc_date_field; - - sort.set(YjsDatabaseKey.id, sortJSON.id); - sort.set(YjsDatabaseKey.field_id, sortJSON.field_id); - sort.set(YjsDatabaseKey.condition, sortJSON.condition === 'asc' ? '0' : '1'); - - return sort; -} - -export function withSingleSelectOptionSort(isAscending: boolean = true) { - const sort = new Y.Map() as YDatabaseSort; - const sortJSON = isAscending ? sortsJson.sort_asc_single_select_field : sortsJson.sort_desc_single_select_field; - - sort.set(YjsDatabaseKey.id, sortJSON.id); - sort.set(YjsDatabaseKey.field_id, sortJSON.field_id); - sort.set(YjsDatabaseKey.condition, sortJSON.condition === 'asc' ? '0' : '1'); - - return sort; -} - -export function withMultiSelectOptionSort(isAscending: boolean = true) { - const sort = new Y.Map() as YDatabaseSort; - const sortJSON = isAscending ? sortsJson.sort_asc_multi_select_field : sortsJson.sort_desc_multi_select_field; - - sort.set(YjsDatabaseKey.id, sortJSON.id); - sort.set(YjsDatabaseKey.field_id, sortJSON.field_id); - sort.set(YjsDatabaseKey.condition, sortJSON.condition === 'asc' ? '0' : '1'); - - return sort; -} - -export function withChecklistSort(isAscending: boolean = true) { - const sort = new Y.Map() as YDatabaseSort; - const sortJSON = isAscending ? sortsJson.sort_asc_checklist_field : sortsJson.sort_desc_checklist_field; - - sort.set(YjsDatabaseKey.id, sortJSON.id); - sort.set(YjsDatabaseKey.field_id, sortJSON.field_id); - sort.set(YjsDatabaseKey.condition, sortJSON.condition === 'asc' ? '0' : '1'); - - return sort; -} - -export function withCreatedAtSort(isAscending: boolean = true) { - const sort = new Y.Map() as YDatabaseSort; - const sortJSON = isAscending ? sortsJson.sort_asc_created_at : sortsJson.sort_desc_created_at; - - sort.set(YjsDatabaseKey.id, sortJSON.id); - sort.set(YjsDatabaseKey.field_id, sortJSON.field_id); - sort.set(YjsDatabaseKey.condition, sortJSON.condition === 'asc' ? '0' : '1'); - - return sort; -} - -export function withLastModifiedSort(isAscending: boolean = true) { - const sort = new Y.Map() as YDatabaseSort; - const sortJSON = isAscending ? sortsJson.sort_asc_updated_at : sortsJson.sort_desc_updated_at; - - sort.set(YjsDatabaseKey.id, sortJSON.id); - sort.set(YjsDatabaseKey.field_id, sortJSON.field_id); - sort.set(YjsDatabaseKey.condition, sortJSON.condition === 'asc' ? '0' : '1'); - - return sort; -} diff --git a/frontend/appflowy_web_app/src/application/database-yjs/cell.parse.ts b/frontend/appflowy_web_app/src/application/database-yjs/cell.parse.ts deleted file mode 100644 index 97bc5ca10d..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/cell.parse.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { YDatabaseCell, YjsDatabaseKey } from '@/application/types'; -import { FieldType } from '@/application/database-yjs/database.type'; -import { YArray } from 'yjs/dist/src/types/YArray'; -import { Cell, CheckboxCell, DateTimeCell, FileMediaCell, FileMediaCellData } from './cell.type'; - -export function parseYDatabaseCommonCellToCell (cell: YDatabaseCell): Cell { - return { - createdAt: Number(cell.get(YjsDatabaseKey.created_at)), - lastModified: Number(cell.get(YjsDatabaseKey.last_modified)), - fieldType: parseInt(cell.get(YjsDatabaseKey.field_type)) as FieldType, - data: cell.get(YjsDatabaseKey.data), - }; -} - -export function parseYDatabaseCellToCell (cell: YDatabaseCell): Cell { - const fieldType = parseInt(cell.get(YjsDatabaseKey.field_type)); - - if (fieldType === FieldType.DateTime) { - return parseYDatabaseDateTimeCellToCell(cell); - } - - if (fieldType === FieldType.Checkbox) { - return parseYDatabaseCheckboxCellToCell(cell); - } - - if (fieldType === FieldType.FileMedia) { - return parseYDatabaseFileMediaCellToCell(cell); - } - - return parseYDatabaseCommonCellToCell(cell); -} - -export function parseYDatabaseDateTimeCellToCell (cell: YDatabaseCell): DateTimeCell { - return { - ...parseYDatabaseCommonCellToCell(cell), - data: cell.get(YjsDatabaseKey.data) as string, - fieldType: FieldType.DateTime, - endTimestamp: cell.get(YjsDatabaseKey.end_timestamp), - includeTime: cell.get(YjsDatabaseKey.include_time), - isRange: cell.get(YjsDatabaseKey.is_range), - reminderId: cell.get(YjsDatabaseKey.reminder_id), - }; -} - -export function parseYDatabaseFileMediaCellToCell (cell: YDatabaseCell): FileMediaCell { - const data = cell.get(YjsDatabaseKey.data) as YArray; - const dataJson = data.toJSON().map((item: string) => JSON.parse(item)) as FileMediaCellData; - - return { - ...parseYDatabaseCommonCellToCell(cell), - data: dataJson, - fieldType: FieldType.FileMedia, - }; -} - -export function parseYDatabaseCheckboxCellToCell (cell: YDatabaseCell): CheckboxCell { - return { - ...parseYDatabaseCommonCellToCell(cell), - data: cell.get(YjsDatabaseKey.data) === 'Yes', - fieldType: FieldType.Checkbox, - }; -} diff --git a/frontend/appflowy_web_app/src/application/database-yjs/cell.type.ts b/frontend/appflowy_web_app/src/application/database-yjs/cell.type.ts deleted file mode 100644 index 67b991dc03..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/cell.type.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { FieldId, RowId } from '@/application/types'; -import { DateFormat, TimeFormat } from '@/application/database-yjs/index'; -import { FieldType } from '@/application/database-yjs/database.type'; -import React from 'react'; -import { YArray } from 'yjs/dist/src/types/YArray'; - -export interface Cell { - createdAt: number; - lastModified: number; - fieldType: FieldType; - data: unknown; -} - -export interface TextCell extends Cell { - fieldType: FieldType.RichText; - data: string; -} - -export interface NumberCell extends Cell { - fieldType: FieldType.Number; - data: string; -} - -export interface CheckboxCell extends Cell { - fieldType: FieldType.Checkbox; - data: boolean; -} - -export interface UrlCell extends Cell { - fieldType: FieldType.URL; - data: string; -} - -export type SelectionId = string; - -export interface SelectOptionCell extends Cell { - fieldType: FieldType.SingleSelect | FieldType.MultiSelect; - data: SelectionId; -} - -export interface DataTimeTypeOption { - timeFormat: TimeFormat; - dateFormat: DateFormat; -} - -export interface DateTimeCell extends Cell { - fieldType: FieldType.DateTime; - data: string; - endTimestamp?: string; - includeTime?: boolean; - isRange?: boolean; - reminderId?: string; -} - -export enum FileMediaType { - Image = 'Image', - Video = 'Video', - Link = 'Link', - Other = 'Other', -} - -export enum FileMediaUploadType { - CloudMedia = 'CloudMedia', - NetworkMedia = 'NetworkMedia', -} - -export interface FileMediaCellDataItem { - file_type: FileMediaType; - id: string; - name: string; - upload_type: FileMediaUploadType; - url: string; -} - -export type FileMediaCellData = FileMediaCellDataItem[] - -export interface FileMediaCell extends Cell { - fieldType: FieldType.FileMedia; - data: FileMediaCellData; -} - -export interface DateTimeCellData { - date?: string; - time?: string; - timestamp?: number; - includeTime?: boolean; - endDate?: string; - endTime?: string; - endTimestamp?: number; - isRange?: boolean; -} - -export interface ChecklistCell extends Cell { - fieldType: FieldType.Checklist; - data: string; -} - -export interface RelationCell extends Cell { - fieldType: FieldType.Relation; - data: YArray; -} - -export type RelationCellData = RowId[]; - -export interface CellProps { - cell?: T; - rowId: string; - fieldId: FieldId; - style?: React.CSSProperties; - readOnly?: boolean; - placeholder?: string; - className?: string; -} diff --git a/frontend/appflowy_web_app/src/application/database-yjs/const.ts b/frontend/appflowy_web_app/src/application/database-yjs/const.ts deleted file mode 100644 index 12deaaa218..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/const.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { RowId, YDatabaseRow, YDoc, YjsDatabaseKey, YjsEditorKey } from '@/application/types'; -import { RowMetaKey } from '@/application/database-yjs/database.type'; -import { v5 as uuidv5, parse as uuidParse } from 'uuid'; - -export const DEFAULT_ROW_HEIGHT = 36; -export const MIN_COLUMN_WIDTH = 150; - -export const getCell = (rowId: string, fieldId: string, rowMetas: Record) => { - const rowMeta = rowMetas[rowId]; - - const meta = rowMeta?.getMap(YjsEditorKey.data_section).get(YjsEditorKey.database_row) as YDatabaseRow; - - return meta?.get(YjsDatabaseKey.cells)?.get(fieldId); -}; - -export const getCellData = (rowId: string, fieldId: string, rowMetas: Record) => { - return getCell(rowId, fieldId, rowMetas)?.get(YjsDatabaseKey.data); -}; - -export const metaIdFromRowId = (rowId: string) => { - let namespace: Uint8Array; - - try { - namespace = uuidParse(rowId); - } catch (e) { - namespace = uuidParse(generateUUID()); - } - - return (key: RowMetaKey) => uuidv5(key, namespace).toString(); -}; - -export const generateUUID = () => uuidv5(Date.now().toString(), uuidv5.URL); diff --git a/frontend/appflowy_web_app/src/application/database-yjs/context.ts b/frontend/appflowy_web_app/src/application/database-yjs/context.ts deleted file mode 100644 index 0125e0f5c3..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/context.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { - CreateRowDoc, - LoadView, - LoadViewMeta, RowId, - YDatabase, - YDatabaseRow, - YDoc, - YjsDatabaseKey, - YjsEditorKey, -} from '@/application/types'; -import { createContext, useContext } from 'react'; - -export interface DatabaseContextState { - readOnly: boolean; - databaseDoc: YDoc; - iidIndex: string; - viewId: string; - rowDocMap: Record | null; - isDatabaseRowPage?: boolean; - scrollLeft?: number; - isDocumentBlock?: boolean; - navigateToRow?: (rowId: string) => void; - loadView?: LoadView; - createRowDoc?: CreateRowDoc; - loadViewMeta?: LoadViewMeta; - navigateToView?: (viewId: string, blockId?: string) => Promise; - onRendered?: (height: number) => void; - showActions?: boolean; -} - -export const DatabaseContext = createContext(null); - -export const useDatabaseContext = () => { - const context = useContext(DatabaseContext); - - if (!context) { - throw new Error('DatabaseContext is not provided'); - } - - return context; -}; - -export const useDatabase = () => { - const database = useDatabaseContext() - .databaseDoc?.getMap(YjsEditorKey.data_section) - .get(YjsEditorKey.database) as YDatabase; - - return database; -}; - -export const useNavigateToRow = () => { - return useDatabaseContext().navigateToRow; -}; - -export const useRowDocMap = () => { - return useDatabaseContext().rowDocMap; -}; - -export const useIsDatabaseRowPage = () => { - return useDatabaseContext().isDatabaseRowPage; -}; - -export const useRow = (rowId: string) => { - const rows = useRowDocMap(); - - return rows?.[rowId]?.getMap(YjsEditorKey.data_section); -}; - -export const useRowData = (rowId: string) => { - return useRow(rowId)?.get(YjsEditorKey.database_row) as YDatabaseRow; -}; - -export const useDatabaseViewId = () => { - const context = useDatabaseContext(); - - return context?.viewId; -}; - -export const useReadOnly = () => { - const context = useDatabaseContext(); - - return context?.readOnly || true; -}; - -export const useDatabaseView = () => { - const database = useDatabase(); - const viewId = useDatabaseViewId(); - - return viewId ? database?.get(YjsDatabaseKey.views)?.get(viewId) : undefined; -}; - -export function useDatabaseFields () { - const database = useDatabase(); - - return database.get(YjsDatabaseKey.fields); -} - -export const useDatabaseSelectedView = (viewId: string) => { - const database = useDatabase(); - - return database.get(YjsDatabaseKey.views).get(viewId); -}; \ No newline at end of file diff --git a/frontend/appflowy_web_app/src/application/database-yjs/database.type.ts b/frontend/appflowy_web_app/src/application/database-yjs/database.type.ts deleted file mode 100644 index c73ceb7bef..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/database.type.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { FieldId } from '@/application/types'; - -export enum FieldVisibility { - AlwaysShown = 0, - HideWhenEmpty = 1, - AlwaysHidden = 2, -} - -export enum FieldType { - RichText = 0, - Number = 1, - DateTime = 2, - SingleSelect = 3, - MultiSelect = 4, - Checkbox = 5, - URL = 6, - Checklist = 7, - LastEditedTime = 8, - CreatedTime = 9, - Relation = 10, - AISummaries = 11, - AITranslations = 12, - FileMedia = 14 -} - -export enum CalculationType { - Average = 0, - Max = 1, - Median = 2, - Min = 3, - Sum = 4, - Count = 5, - CountEmpty = 6, - CountNonEmpty = 7, -} - -export enum SortCondition { - Ascending = 0, - Descending = 1, -} - -export enum FilterType { - Data = 0, - And = 1, - Or = 2, -} - -export interface Filter { - fieldId: FieldId; - filterType: FilterType; - condition: number; - id: string; - content: string; -} - -export enum CalendarLayout { - MonthLayout = 0, - WeekLayout = 1, - DayLayout = 2, -} - -export interface CalendarLayoutSetting { - fieldId: string; - firstDayOfWeek: number; - showWeekNumbers: boolean; - showWeekends: boolean; - layout: CalendarLayout; -} - -export enum RowMetaKey { - DocumentId = 'document_id', - IconId = 'icon_id', - CoverId = 'cover_id', - IsDocumentEmpty = 'is_document_empty', -} diff --git a/frontend/appflowy_web_app/src/application/database-yjs/fields/checkbox/checkbox.type.ts b/frontend/appflowy_web_app/src/application/database-yjs/fields/checkbox/checkbox.type.ts deleted file mode 100644 index b9da4341f6..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/fields/checkbox/checkbox.type.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Filter } from '@/application/database-yjs'; - -export enum CheckboxFilterCondition { - IsChecked = 0, - IsUnChecked = 1, -} - -export interface CheckboxFilter extends Filter { - condition: CheckboxFilterCondition; -} diff --git a/frontend/appflowy_web_app/src/application/database-yjs/fields/checkbox/index.ts b/frontend/appflowy_web_app/src/application/database-yjs/fields/checkbox/index.ts deleted file mode 100644 index 9ccd409dc8..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/fields/checkbox/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './checkbox.type'; diff --git a/frontend/appflowy_web_app/src/application/database-yjs/fields/checklist/checklist.type.ts b/frontend/appflowy_web_app/src/application/database-yjs/fields/checklist/checklist.type.ts deleted file mode 100644 index 2b504ded8a..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/fields/checklist/checklist.type.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Filter } from '@/application/database-yjs'; - -export enum ChecklistFilterCondition { - IsComplete = 0, - IsIncomplete = 1, -} - -export interface ChecklistFilter extends Filter { - condition: ChecklistFilterCondition; -} diff --git a/frontend/appflowy_web_app/src/application/database-yjs/fields/checklist/index.ts b/frontend/appflowy_web_app/src/application/database-yjs/fields/checklist/index.ts deleted file mode 100644 index 15d37f912b..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/fields/checklist/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './checklist.type'; -export * from './parse'; diff --git a/frontend/appflowy_web_app/src/application/database-yjs/fields/checklist/parse.ts b/frontend/appflowy_web_app/src/application/database-yjs/fields/checklist/parse.ts deleted file mode 100644 index c93fee7a38..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/fields/checklist/parse.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { SelectOption } from '../select-option'; - -export interface ChecklistCellData { - selectedOptionIds?: string[]; - options?: SelectOption[]; - percentage: number; -} - -export function parseChecklistData(data: string): ChecklistCellData | null { - try { - const { options, selected_option_ids } = JSON.parse(data); - const percentage = selected_option_ids.length / options.length; - - return { - percentage, - options, - selectedOptionIds: selected_option_ids, - }; - } catch (e) { - return null; - } -} diff --git a/frontend/appflowy_web_app/src/application/database-yjs/fields/date/date.type.ts b/frontend/appflowy_web_app/src/application/database-yjs/fields/date/date.type.ts deleted file mode 100644 index 0db15f21eb..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/fields/date/date.type.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Filter } from '@/application/database-yjs'; - -export enum TimeFormat { - TwelveHour = 0, - TwentyFourHour = 1, -} - -export enum DateFormat { - Local = 0, - US = 1, - ISO = 2, - Friendly = 3, - DayMonthYear = 4, -} - -export enum DateFilterCondition { - DateIs = 0, - DateBefore = 1, - DateAfter = 2, - DateOnOrBefore = 3, - DateOnOrAfter = 4, - DateWithIn = 5, - DateIsEmpty = 6, - DateIsNotEmpty = 7, -} - -export interface DateFilter extends Filter { - condition: DateFilterCondition; - start?: number; - end?: number; - timestamp?: number; -} diff --git a/frontend/appflowy_web_app/src/application/database-yjs/fields/date/index.ts b/frontend/appflowy_web_app/src/application/database-yjs/fields/date/index.ts deleted file mode 100644 index 106279c949..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/fields/date/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './date.type'; -export * from './utils'; diff --git a/frontend/appflowy_web_app/src/application/database-yjs/fields/date/utils.test.ts b/frontend/appflowy_web_app/src/application/database-yjs/fields/date/utils.test.ts deleted file mode 100644 index 9d3821ba1c..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/fields/date/utils.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { getTimeFormat, getDateFormat } from './utils'; -import { expect } from '@jest/globals'; -import { DateFormat, TimeFormat } from '@/application/database-yjs'; - -describe('DateFormat', () => { - it('should return time format', () => { - expect(getTimeFormat(TimeFormat.TwelveHour)).toEqual('h:mm A'); - expect(getTimeFormat(TimeFormat.TwentyFourHour)).toEqual('HH:mm'); - expect(getTimeFormat(56)).toEqual('HH:mm'); - }); - - it('should return date format', () => { - expect(getDateFormat(DateFormat.US)).toEqual('YYYY/MM/DD'); - expect(getDateFormat(DateFormat.ISO)).toEqual('YYYY-MM-DD'); - expect(getDateFormat(DateFormat.Friendly)).toEqual('MMM DD, YYYY'); - expect(getDateFormat(DateFormat.Local)).toEqual('MM/DD/YYYY'); - expect(getDateFormat(DateFormat.DayMonthYear)).toEqual('DD/MM/YYYY'); - - expect(getDateFormat(56)).toEqual('YYYY-MM-DD'); - }); -}); diff --git a/frontend/appflowy_web_app/src/application/database-yjs/fields/date/utils.ts b/frontend/appflowy_web_app/src/application/database-yjs/fields/date/utils.ts deleted file mode 100644 index 985402768b..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/fields/date/utils.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { TimeFormat, DateFormat } from '@/application/database-yjs'; - -export function getTimeFormat(timeFormat?: TimeFormat) { - switch (timeFormat) { - case TimeFormat.TwelveHour: - return 'h:mm A'; - case TimeFormat.TwentyFourHour: - return 'HH:mm'; - default: - return 'HH:mm'; - } -} - -export function getDateFormat(dateFormat?: DateFormat) { - switch (dateFormat) { - case DateFormat.Friendly: - return 'MMM DD, YYYY'; - case DateFormat.ISO: - return 'YYYY-MM-DD'; - case DateFormat.US: - return 'YYYY/MM/DD'; - case DateFormat.Local: - return 'MM/DD/YYYY'; - case DateFormat.DayMonthYear: - return 'DD/MM/YYYY'; - default: - return 'YYYY-MM-DD'; - } -} diff --git a/frontend/appflowy_web_app/src/application/database-yjs/fields/index.ts b/frontend/appflowy_web_app/src/application/database-yjs/fields/index.ts deleted file mode 100644 index 5505f0e4ed..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/fields/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -export * from './type_option'; -export * from './date'; -export * from './number'; -export * from './select-option'; -export * from './text'; -export * from './checkbox'; -export * from './checklist'; -export * from './relation'; diff --git a/frontend/appflowy_web_app/src/application/database-yjs/fields/number/__tests__/format.test.ts b/frontend/appflowy_web_app/src/application/database-yjs/fields/number/__tests__/format.test.ts deleted file mode 100644 index f80b1db220..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/fields/number/__tests__/format.test.ts +++ /dev/null @@ -1,628 +0,0 @@ -import { currencyFormaterMap } from '../format'; -import { NumberFormat } from '../number.type'; -import { expect } from '@jest/globals'; - -const testCases = [0, 1, 0.5, 0.5666, 1000, 10000, 1000000, 10000000, 1000000.0]; -describe('currencyFormaterMap', () => { - test('should return the correct formatter for Num', () => { - const formater = currencyFormaterMap[NumberFormat.Num]; - const result = ['0', '1', '0.5', '0.5666', '1,000', '10,000', '1,000,000', '10,000,000', '1,000,000']; - testCases.forEach((testCase) => { - expect(formater(testCase)).toBe(result[testCases.indexOf(testCase)]); - }); - }); - - test('should return the correct formatter for Percent', () => { - const formater = currencyFormaterMap[NumberFormat.Percent]; - const result = ['0%', '1%', '0.5%', '0.57%', '1,000%', '10,000%', '1,000,000%', '10,000,000%', '1,000,000%']; - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - - test('should return the correct formatter for USD', () => { - const formater = currencyFormaterMap[NumberFormat.USD]; - const result = ['$0', '$1', '$0.5', '$0.57', '$1,000', '$10,000', '$1,000,000', '$10,000,000', '$1,000,000']; - - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - - test('should return the correct formatter for CanadianDollar', () => { - const formater = currencyFormaterMap[NumberFormat.CanadianDollar]; - const result = [ - 'CA$0', - 'CA$1', - 'CA$0.5', - 'CA$0.57', - 'CA$1,000', - 'CA$10,000', - 'CA$1,000,000', - 'CA$10,000,000', - 'CA$1,000,000', - ]; - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - - test('should return the correct formatter for EUR', () => { - const formater = currencyFormaterMap[NumberFormat.EUR]; - - const result = ['€0', '€1', '€0,5', '€0,57', '€1.000', '€10.000', '€1.000.000', '€10.000.000', '€1.000.000']; - - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - - test('should return the correct formatter for Pound', () => { - const formater = currencyFormaterMap[NumberFormat.Pound]; - - const result = ['£0', '£1', '£0.5', '£0.57', '£1,000', '£10,000', '£1,000,000', '£10,000,000', '£1,000,000']; - - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - - test('should return the correct formatter for Yen', () => { - const formater = currencyFormaterMap[NumberFormat.Yen]; - - const result = [ - '¥0', - '¥1', - '¥0.5', - '¥0.57', - '¥1,000', - '¥10,000', - '¥1,000,000', - '¥10,000,000', - '¥1,000,000', - ]; - - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - - test('should return the correct formatter for Ruble', () => { - const formater = currencyFormaterMap[NumberFormat.Ruble]; - - const result = [ - '0 RUB', - '1 RUB', - '0,5 RUB', - '0,57 RUB', - '1 000 RUB', - '10 000 RUB', - '1 000 000 RUB', - '10 000 000 RUB', - '1 000 000 RUB', - ]; - - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - - test('should return the correct formatter for Rupee', () => { - const formater = currencyFormaterMap[NumberFormat.Rupee]; - - const result = ['₹0', '₹1', '₹0.5', '₹0.57', '₹1,000', '₹10,000', '₹10,00,000', '₹1,00,00,000', '₹10,00,000']; - - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - - test('should return the correct formatter for Won', () => { - const formater = currencyFormaterMap[NumberFormat.Won]; - - const result = ['₩0', '₩1', '₩0.5', '₩0.57', '₩1,000', '₩10,000', '₩1,000,000', '₩10,000,000', '₩1,000,000']; - - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - - test('should return the correct formatter for Yuan', () => { - const formater = currencyFormaterMap[NumberFormat.Yuan]; - - const result = [ - 'CN¥0', - 'CN¥1', - 'CN¥0.5', - 'CN¥0.57', - 'CN¥1,000', - 'CN¥10,000', - 'CN¥1,000,000', - 'CN¥10,000,000', - 'CN¥1,000,000', - ]; - - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - - test('should return the correct formatter for Real', () => { - const formater = currencyFormaterMap[NumberFormat.Real]; - - const result = [ - 'R$ 0', - 'R$ 1', - 'R$ 0,5', - 'R$ 0,57', - 'R$ 1.000', - 'R$ 10.000', - 'R$ 1.000.000', - 'R$ 10.000.000', - 'R$ 1.000.000', - ]; - - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - - test('should return the correct formatter for Lira', () => { - const formater = currencyFormaterMap[NumberFormat.Lira]; - - const result = [ - 'TRY 0', - 'TRY 1', - 'TRY 0,5', - 'TRY 0,57', - 'TRY 1.000', - 'TRY 10.000', - 'TRY 1.000.000', - 'TRY 10.000.000', - 'TRY 1.000.000', - ]; - - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - - test('should return the correct formatter for Rupiah', () => { - const formater = currencyFormaterMap[NumberFormat.Rupiah]; - - const result = [ - 'IDR 0', - 'IDR 1', - 'IDR 0,5', - 'IDR 0,57', - 'IDR 1.000', - 'IDR 10.000', - 'IDR 1.000.000', - 'IDR 10.000.000', - 'IDR 1.000.000', - ]; - - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - - test('should return the correct formatter for Franc', () => { - const formater = currencyFormaterMap[NumberFormat.Franc]; - - const result = [ - 'CHF 0', - 'CHF 1', - 'CHF 0.5', - 'CHF 0.57', - `CHF 1’000`, - `CHF 10’000`, - `CHF 1’000’000`, - `CHF 10’000’000`, - `CHF 1’000’000`, - ]; - - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - - test('should return the correct formatter for HongKongDollar', () => { - const formater = currencyFormaterMap[NumberFormat.HongKongDollar]; - - const result = [ - 'HK$0', - 'HK$1', - 'HK$0.5', - 'HK$0.57', - 'HK$1,000', - 'HK$10,000', - 'HK$1,000,000', - 'HK$10,000,000', - 'HK$1,000,000', - ]; - - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - - test('should return the correct formatter for NewZealandDollar', () => { - const formater = currencyFormaterMap[NumberFormat.NewZealandDollar]; - - const result = [ - 'NZ$0', - 'NZ$1', - 'NZ$0.5', - 'NZ$0.57', - 'NZ$1,000', - 'NZ$10,000', - 'NZ$1,000,000', - 'NZ$10,000,000', - 'NZ$1,000,000', - ]; - - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - - test('should return the correct formatter for Krona', () => { - const formater = currencyFormaterMap[NumberFormat.Krona]; - - const result = [ - '0 SEK', - '1 SEK', - '0,5 SEK', - '0,57 SEK', - '1 000 SEK', - '10 000 SEK', - '1 000 000 SEK', - '10 000 000 SEK', - '1 000 000 SEK', - ]; - - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - test('should return the correct formatter for NorwegianKrone', () => { - const formater = currencyFormaterMap[NumberFormat.NorwegianKrone]; - - const result = [ - 'NOK 0', - 'NOK 1', - 'NOK 0,5', - 'NOK 0,57', - 'NOK 1 000', - 'NOK 10 000', - 'NOK 1 000 000', - 'NOK 10 000 000', - 'NOK 1 000 000', - ]; - - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - - test('should return the correct formatter for MexicanPeso', () => { - const formater = currencyFormaterMap[NumberFormat.MexicanPeso]; - - const result = [ - 'MX$0', - 'MX$1', - 'MX$0.5', - 'MX$0.57', - 'MX$1,000', - 'MX$10,000', - 'MX$1,000,000', - 'MX$10,000,000', - 'MX$1,000,000', - ]; - - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - - test('should return the correct formatter for Rand', () => { - const formater = currencyFormaterMap[NumberFormat.Rand]; - - const result = [ - 'ZAR 0', - 'ZAR 1', - 'ZAR 0,5', - 'ZAR 0,57', - 'ZAR 1 000', - 'ZAR 10 000', - 'ZAR 1 000 000', - 'ZAR 10 000 000', - 'ZAR 1 000 000', - ]; - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - - test('should return the correct formatter for NewTaiwanDollar', () => { - const formater = currencyFormaterMap[NumberFormat.NewTaiwanDollar]; - - const result = [ - 'NT$0', - 'NT$1', - 'NT$0.5', - 'NT$0.57', - 'NT$1,000', - 'NT$10,000', - 'NT$1,000,000', - 'NT$10,000,000', - 'NT$1,000,000', - ]; - - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - - test('should return the correct formatter for DanishKrone', () => { - const formater = currencyFormaterMap[NumberFormat.DanishKrone]; - - const result = [ - '0 DKK', - '1 DKK', - '0,5 DKK', - '0,57 DKK', - '1.000 DKK', - '10.000 DKK', - '1.000.000 DKK', - '10.000.000 DKK', - '1.000.000 DKK', - ]; - - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - test('should return the correct formatter for Baht', () => { - const formater = currencyFormaterMap[NumberFormat.Baht]; - - const result = [ - 'THB 0', - 'THB 1', - 'THB 0.5', - 'THB 0.57', - 'THB 1,000', - 'THB 10,000', - 'THB 1,000,000', - 'THB 10,000,000', - 'THB 1,000,000', - ]; - - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - test('should return the correct formatter for Forint', () => { - const formater = currencyFormaterMap[NumberFormat.Forint]; - - const result = [ - '0 HUF', - '1 HUF', - '0,5 HUF', - '0,57 HUF', - '1 000 HUF', - '10 000 HUF', - '1 000 000 HUF', - '10 000 000 HUF', - '1 000 000 HUF', - ]; - - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - - test('should return the correct formatter for Koruna', () => { - const formater = currencyFormaterMap[NumberFormat.Koruna]; - - const result = [ - '0 CZK', - '1 CZK', - '0,5 CZK', - '0,57 CZK', - '1 000 CZK', - '10 000 CZK', - '1 000 000 CZK', - '10 000 000 CZK', - '1 000 000 CZK', - ]; - - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - - test('should return the correct formatter for Shekel', () => { - const formater = currencyFormaterMap[NumberFormat.Shekel]; - - const result = [ - '‏0 ‏₪', - '‏1 ‏₪', - '‏0.5 ‏₪', - '‏0.57 ‏₪', - '‏1,000 ‏₪', - '‏10,000 ‏₪', - '‏1,000,000 ‏₪', - '‏10,000,000 ‏₪', - '‏1,000,000 ‏₪', - ]; - - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - test('should return the correct formatter for ChileanPeso', () => { - const formater = currencyFormaterMap[NumberFormat.ChileanPeso]; - - const result = [ - 'CLP 0', - 'CLP 1', - 'CLP 0,5', - 'CLP 0,57', - 'CLP 1.000', - 'CLP 10.000', - 'CLP 1.000.000', - 'CLP 10.000.000', - 'CLP 1.000.000', - ]; - - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - test('should return the correct formatter for PhilippinePeso', () => { - const formater = currencyFormaterMap[NumberFormat.PhilippinePeso]; - - const result = ['₱0', '₱1', '₱0.5', '₱0.57', '₱1,000', '₱10,000', '₱1,000,000', '₱10,000,000', '₱1,000,000']; - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - test('should return the correct formatter for Dirham', () => { - const formater = currencyFormaterMap[NumberFormat.Dirham]; - - const result = [ - '‏0 AED', - '‏1 AED', - '‏0.5 AED', - '‏0.57 AED', - '‏1,000 AED', - '‏10,000 AED', - '‏1,000,000 AED', - '‏10,000,000 AED', - '‏1,000,000 AED', - ]; - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - test('should return the correct formatter for ColombianPeso', () => { - const formater = currencyFormaterMap[NumberFormat.ColombianPeso]; - - const result = [ - 'COP 0', - 'COP 1', - 'COP 0,5', - 'COP 0,57', - 'COP 1.000', - 'COP 10.000', - 'COP 1.000.000', - 'COP 10.000.000', - 'COP 1.000.000', - ]; - - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - test('should return the correct formatter for Riyal', () => { - const formater = currencyFormaterMap[NumberFormat.Riyal]; - - const result = [ - 'SAR 0', - 'SAR 1', - 'SAR 0.5', - 'SAR 0.57', - 'SAR 1,000', - 'SAR 10,000', - 'SAR 1,000,000', - 'SAR 10,000,000', - 'SAR 1,000,000', - ]; - - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - - test('should return the correct formatter for Ringgit', () => { - const formater = currencyFormaterMap[NumberFormat.Ringgit]; - - const result = [ - 'RM 0', - 'RM 1', - 'RM 0.5', - 'RM 0.57', - 'RM 1,000', - 'RM 10,000', - 'RM 1,000,000', - 'RM 10,000,000', - 'RM 1,000,000', - ]; - - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - - test('should return the correct formatter for Leu', () => { - const formater = currencyFormaterMap[NumberFormat.Leu]; - - const result = [ - '0 RON', - '1 RON', - '0,5 RON', - '0,57 RON', - '1.000 RON', - '10.000 RON', - '1.000.000 RON', - '10.000.000 RON', - '1.000.000 RON', - ]; - - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - - test('should return the correct formatter for ArgentinePeso', () => { - const formater = currencyFormaterMap[NumberFormat.ArgentinePeso]; - - const result = [ - 'ARS 0', - 'ARS 1', - 'ARS 0,5', - 'ARS 0,57', - 'ARS 1.000', - 'ARS 10.000', - 'ARS 1.000.000', - 'ARS 10.000.000', - 'ARS 1.000.000', - ]; - - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); - - test('should return the correct formatter for UruguayanPeso', () => { - const formater = currencyFormaterMap[NumberFormat.UruguayanPeso]; - - const result = [ - 'UYU 0', - 'UYU 1', - 'UYU 0,5', - 'UYU 0,57', - 'UYU 1.000', - 'UYU 10.000', - 'UYU 1.000.000', - 'UYU 10.000.000', - 'UYU 1.000.000', - ]; - - testCases.forEach((testCase, index) => { - expect(formater(testCase)).toBe(result[index]); - }); - }); -}); diff --git a/frontend/appflowy_web_app/src/application/database-yjs/fields/number/format.ts b/frontend/appflowy_web_app/src/application/database-yjs/fields/number/format.ts deleted file mode 100644 index 61e0942b01..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/fields/number/format.ts +++ /dev/null @@ -1,236 +0,0 @@ -import { NumberFormat } from './number.type'; - -const commonProps = { - minimumFractionDigits: 0, - maximumFractionDigits: 2, - style: 'currency', - currencyDisplay: 'symbol', - useGrouping: true, -}; - -export const currencyFormaterMap: Record string> = { - [NumberFormat.Num]: (n: number) => - new Intl.NumberFormat('en-US', { - style: 'decimal', - minimumFractionDigits: 0, - maximumFractionDigits: 20, - }).format(n), - [NumberFormat.Percent]: (n: number) => - new Intl.NumberFormat('en-US', { - ...commonProps, - style: 'decimal', - }).format(n) + '%', - [NumberFormat.USD]: (n: number) => - new Intl.NumberFormat('en-US', { - ...commonProps, - currency: 'USD', - }).format(n), - [NumberFormat.CanadianDollar]: (n: number) => - new Intl.NumberFormat('en-CA', { - ...commonProps, - currency: 'CAD', - }) - .format(n) - .replace('$', 'CA$'), - [NumberFormat.EUR]: (n: number) => { - const formattedAmount = new Intl.NumberFormat('de-DE', { - ...commonProps, - currency: 'EUR', - }) - .format(n) - .replace('€', '') - .trim(); - - return `€${formattedAmount}`; - }, - - [NumberFormat.Pound]: (n: number) => - new Intl.NumberFormat('en-GB', { - ...commonProps, - currency: 'GBP', - }).format(n), - [NumberFormat.Yen]: (n: number) => - new Intl.NumberFormat('ja-JP', { - ...commonProps, - currency: 'JPY', - }).format(n), - [NumberFormat.Ruble]: (n: number) => - new Intl.NumberFormat('ru-RU', { - ...commonProps, - currency: 'RUB', - currencyDisplay: 'code', - }) - .format(n) - .replaceAll(' ', ' '), - [NumberFormat.Rupee]: (n: number) => - new Intl.NumberFormat('hi-IN', { - ...commonProps, - currency: 'INR', - }).format(n), - [NumberFormat.Won]: (n: number) => - new Intl.NumberFormat('ko-KR', { - ...commonProps, - currency: 'KRW', - }).format(n), - [NumberFormat.Yuan]: (n: number) => - new Intl.NumberFormat('zh-CN', { - ...commonProps, - currency: 'CNY', - }) - .format(n) - .replace('¥', 'CN¥'), - [NumberFormat.Real]: (n: number) => - new Intl.NumberFormat('pt-BR', { - ...commonProps, - currency: 'BRL', - }) - .format(n) - .replaceAll(' ', ' '), - [NumberFormat.Lira]: (n: number) => - new Intl.NumberFormat('tr-TR', { - ...commonProps, - currency: 'TRY', - currencyDisplay: 'code', - }) - .format(n) - .replaceAll(' ', ' '), - [NumberFormat.Rupiah]: (n: number) => - new Intl.NumberFormat('id-ID', { - ...commonProps, - currency: 'IDR', - currencyDisplay: 'code', - }) - .format(n) - .replaceAll(' ', ' '), - [NumberFormat.Franc]: (n: number) => - new Intl.NumberFormat('de-CH', { - ...commonProps, - currency: 'CHF', - }) - .format(n) - .replaceAll(' ', ' '), - [NumberFormat.HongKongDollar]: (n: number) => - new Intl.NumberFormat('zh-HK', { - ...commonProps, - currency: 'HKD', - }).format(n), - [NumberFormat.NewZealandDollar]: (n: number) => - new Intl.NumberFormat('en-NZ', { - ...commonProps, - currency: 'NZD', - }) - .format(n) - .replace('$', 'NZ$'), - [NumberFormat.Krona]: (n: number) => - new Intl.NumberFormat('sv-SE', { - ...commonProps, - currency: 'SEK', - currencyDisplay: 'code', - }).format(n), - [NumberFormat.NorwegianKrone]: (n: number) => - new Intl.NumberFormat('nb-NO', { - ...commonProps, - currency: 'NOK', - currencyDisplay: 'code', - }).format(n), - [NumberFormat.MexicanPeso]: (n: number) => - new Intl.NumberFormat('es-MX', { - ...commonProps, - currency: 'MXN', - }) - .format(n) - .replace('$', 'MX$'), - [NumberFormat.Rand]: (n: number) => - new Intl.NumberFormat('en-ZA', { - ...commonProps, - currency: 'ZAR', - currencyDisplay: 'code', - }).format(n), - [NumberFormat.NewTaiwanDollar]: (n: number) => - new Intl.NumberFormat('zh-TW', { - ...commonProps, - currency: 'TWD', - }) - .format(n) - .replace('$', 'NT$'), - [NumberFormat.DanishKrone]: (n: number) => - new Intl.NumberFormat('da-DK', { - ...commonProps, - currency: 'DKK', - currencyDisplay: 'code', - }).format(n), - [NumberFormat.Baht]: (n: number) => - new Intl.NumberFormat('th-TH', { - ...commonProps, - currency: 'THB', - currencyDisplay: 'code', - }).format(n), - [NumberFormat.Forint]: (n: number) => - new Intl.NumberFormat('hu-HU', { - ...commonProps, - currency: 'HUF', - currencyDisplay: 'code', - }).format(n), - [NumberFormat.Koruna]: (n: number) => - new Intl.NumberFormat('cs-CZ', { - ...commonProps, - currency: 'CZK', - currencyDisplay: 'code', - }).format(n), - [NumberFormat.Shekel]: (n: number) => - new Intl.NumberFormat('he-IL', { - ...commonProps, - currency: 'ILS', - }).format(n), - [NumberFormat.ChileanPeso]: (n: number) => - new Intl.NumberFormat('es-CL', { - ...commonProps, - currency: 'CLP', - currencyDisplay: 'code', - }).format(n), - [NumberFormat.PhilippinePeso]: (n: number) => - new Intl.NumberFormat('fil-PH', { - ...commonProps, - currency: 'PHP', - }).format(n), - [NumberFormat.Dirham]: (n: number) => - new Intl.NumberFormat('ar-AE', { - ...commonProps, - currency: 'AED', - currencyDisplay: 'code', - }).format(n), - [NumberFormat.ColombianPeso]: (n: number) => - new Intl.NumberFormat('es-CO', { - ...commonProps, - currency: 'COP', - currencyDisplay: 'code', - }).format(n), - [NumberFormat.Riyal]: (n: number) => - new Intl.NumberFormat('en-US', { - ...commonProps, - currency: 'SAR', - currencyDisplay: 'code', - }).format(n), - [NumberFormat.Ringgit]: (n: number) => - new Intl.NumberFormat('ms-MY', { - ...commonProps, - currency: 'MYR', - }).format(n), - [NumberFormat.Leu]: (n: number) => - new Intl.NumberFormat('ro-RO', { - ...commonProps, - currency: 'RON', - }).format(n), - [NumberFormat.ArgentinePeso]: (n: number) => - new Intl.NumberFormat('es-AR', { - ...commonProps, - currency: 'ARS', - currencyDisplay: 'code', - }).format(n), - [NumberFormat.UruguayanPeso]: (n: number) => - new Intl.NumberFormat('es-UY', { - ...commonProps, - currency: 'UYU', - currencyDisplay: 'code', - }).format(n), -}; diff --git a/frontend/appflowy_web_app/src/application/database-yjs/fields/number/index.ts b/frontend/appflowy_web_app/src/application/database-yjs/fields/number/index.ts deleted file mode 100644 index 27ca7cd8d8..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/fields/number/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './format'; -export * from './number.type'; -export * from './parse'; diff --git a/frontend/appflowy_web_app/src/application/database-yjs/fields/number/number.type.ts b/frontend/appflowy_web_app/src/application/database-yjs/fields/number/number.type.ts deleted file mode 100644 index 9140531325..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/fields/number/number.type.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Filter } from '@/application/database-yjs'; - -export enum NumberFormat { - Num = 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, -} - -export enum NumberFilterCondition { - Equal = 0, - NotEqual = 1, - GreaterThan = 2, - LessThan = 3, - GreaterThanOrEqualTo = 4, - LessThanOrEqualTo = 5, - NumberIsEmpty = 6, - NumberIsNotEmpty = 7, -} - -export interface NumberFilter extends Filter { - condition: NumberFilterCondition; - content: string; -} diff --git a/frontend/appflowy_web_app/src/application/database-yjs/fields/number/parse.ts b/frontend/appflowy_web_app/src/application/database-yjs/fields/number/parse.ts deleted file mode 100644 index d96ea87962..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/fields/number/parse.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { YDatabaseField } from '@/application/types'; -import { getTypeOptions } from '../type_option'; -import { NumberFormat } from './number.type'; - -export function parseNumberTypeOptions(field: YDatabaseField) { - const numberTypeOption = getTypeOptions(field)?.toJSON(); - - return { - format: parseInt(numberTypeOption.format) as NumberFormat, - }; -} diff --git a/frontend/appflowy_web_app/src/application/database-yjs/fields/relation/index.ts b/frontend/appflowy_web_app/src/application/database-yjs/fields/relation/index.ts deleted file mode 100644 index 4b94064b52..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/fields/relation/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './parse'; -export * from './relation.type'; diff --git a/frontend/appflowy_web_app/src/application/database-yjs/fields/relation/parse.ts b/frontend/appflowy_web_app/src/application/database-yjs/fields/relation/parse.ts deleted file mode 100644 index 42bbfe42e2..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/fields/relation/parse.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { YDatabaseField } from '@/application/types'; -import { RelationTypeOption } from './relation.type'; -import { getTypeOptions } from '../type_option'; - -export function parseRelationTypeOption(field: YDatabaseField) { - const relationTypeOption = getTypeOptions(field)?.toJSON(); - - return relationTypeOption as RelationTypeOption; -} diff --git a/frontend/appflowy_web_app/src/application/database-yjs/fields/relation/relation.type.ts b/frontend/appflowy_web_app/src/application/database-yjs/fields/relation/relation.type.ts deleted file mode 100644 index 31021afc38..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/fields/relation/relation.type.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Filter } from '@/application/database-yjs'; - -export interface RelationTypeOption { - database_id: string; -} - -export interface RelationFilter extends Filter { - condition: number; -} diff --git a/frontend/appflowy_web_app/src/application/database-yjs/fields/select-option/index.ts b/frontend/appflowy_web_app/src/application/database-yjs/fields/select-option/index.ts deleted file mode 100644 index a569b2ca47..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/fields/select-option/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './select_option.type'; -export * from './parse'; diff --git a/frontend/appflowy_web_app/src/application/database-yjs/fields/select-option/parse.ts b/frontend/appflowy_web_app/src/application/database-yjs/fields/select-option/parse.ts deleted file mode 100644 index 83446338d2..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/fields/select-option/parse.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { YDatabaseField, YjsDatabaseKey } from '@/application/types'; -import { getTypeOptions } from '../type_option'; -import { SelectTypeOption } from './select_option.type'; - -export function parseSelectOptionTypeOptions(field: YDatabaseField) { - const content = getTypeOptions(field)?.get(YjsDatabaseKey.content); - - if (!content) return null; - - try { - return JSON.parse(content) as SelectTypeOption; - } catch (e) { - return null; - } -} - -export function parseSelectOptionCellData(field: YDatabaseField, data: string) { - const typeOption = parseSelectOptionTypeOptions(field); - const selectedIds = typeof data === 'string' ? data.split(',') : []; - - return selectedIds - .map((id) => { - const option = typeOption?.options?.find((option) => option.id === id); - - return option?.name ?? ''; - }) - .join(', '); -} diff --git a/frontend/appflowy_web_app/src/application/database-yjs/fields/select-option/select_option.type.ts b/frontend/appflowy_web_app/src/application/database-yjs/fields/select-option/select_option.type.ts deleted file mode 100644 index 343941d588..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/fields/select-option/select_option.type.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Filter } from '@/application/database-yjs'; - -export enum SelectOptionColor { - Purple = 'Purple', - Pink = 'Pink', - LightPink = 'LightPink', - Orange = 'Orange', - Yellow = 'Yellow', - Lime = 'Lime', - Green = 'Green', - Aqua = 'Aqua', - Blue = 'Blue', -} - -export enum SelectOptionFilterCondition { - OptionIs = 0, - OptionIsNot = 1, - OptionContains = 2, - OptionDoesNotContain = 3, - OptionIsEmpty = 4, - OptionIsNotEmpty = 5, -} - -export interface SelectOptionFilter extends Filter { - condition: SelectOptionFilterCondition; - optionIds: string[]; -} - -export interface SelectOption { - id: string; - name: string; - color: SelectOptionColor; -} - -export interface SelectTypeOption { - disable_color: boolean; - options: SelectOption[]; -} diff --git a/frontend/appflowy_web_app/src/application/database-yjs/fields/text/index.ts b/frontend/appflowy_web_app/src/application/database-yjs/fields/text/index.ts deleted file mode 100644 index 7d0a52cd9d..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/fields/text/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './text.type'; diff --git a/frontend/appflowy_web_app/src/application/database-yjs/fields/text/text.type.ts b/frontend/appflowy_web_app/src/application/database-yjs/fields/text/text.type.ts deleted file mode 100644 index c2f230c738..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/fields/text/text.type.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Filter } from '@/application/database-yjs'; - -export enum TextFilterCondition { - TextIs = 0, - TextIsNot = 1, - TextContains = 2, - TextDoesNotContain = 3, - TextStartsWith = 4, - TextEndsWith = 5, - TextIsEmpty = 6, - TextIsNotEmpty = 7, -} - -export interface TextFilter extends Filter { - condition: TextFilterCondition; - content: string; -} diff --git a/frontend/appflowy_web_app/src/application/database-yjs/fields/type_option.ts b/frontend/appflowy_web_app/src/application/database-yjs/fields/type_option.ts deleted file mode 100644 index 11da994873..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/fields/type_option.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { YDatabaseField, YjsDatabaseKey } from '@/application/types'; -import { FieldType } from '@/application/database-yjs'; - -export function getTypeOptions(field: YDatabaseField) { - const fieldType = Number(field?.get(YjsDatabaseKey.type)) as FieldType; - - return field?.get(YjsDatabaseKey.type_option)?.get(String(fieldType)); -} diff --git a/frontend/appflowy_web_app/src/application/database-yjs/filter.ts b/frontend/appflowy_web_app/src/application/database-yjs/filter.ts deleted file mode 100644 index e3cb188cda..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/filter.ts +++ /dev/null @@ -1,234 +0,0 @@ -import { - RowId, - YDatabaseFields, - YDatabaseFilter, - YDatabaseFilters, - YDatabaseRow, - YDoc, - YjsDatabaseKey, - YjsEditorKey, -} from '@/application/types'; -import { FieldType } from '@/application/database-yjs/database.type'; -import { - CheckboxFilter, - CheckboxFilterCondition, - ChecklistFilter, - ChecklistFilterCondition, - DateFilter, - NumberFilter, - NumberFilterCondition, - parseChecklistData, - SelectOptionFilter, - SelectOptionFilterCondition, - TextFilter, - TextFilterCondition, -} from '@/application/database-yjs/fields'; -import { Row } from '@/application/database-yjs/selector'; -import Decimal from 'decimal.js'; -import { every, filter, some } from 'lodash-es'; - -export function parseFilter (fieldType: FieldType, filter: YDatabaseFilter) { - const fieldId = filter.get(YjsDatabaseKey.field_id); - const filterType = Number(filter.get(YjsDatabaseKey.filter_type)); - const id = filter.get(YjsDatabaseKey.id); - const content = filter.get(YjsDatabaseKey.content); - const condition = Number(filter.get(YjsDatabaseKey.condition)); - - const value = { - fieldId, - filterType, - condition, - id, - content, - }; - - switch (fieldType) { - case FieldType.URL: - case FieldType.RichText: - return value as TextFilter; - case FieldType.Number: - return value as NumberFilter; - case FieldType.Checklist: - return value as ChecklistFilter; - case FieldType.Checkbox: - return value as CheckboxFilter; - case FieldType.SingleSelect: - case FieldType.MultiSelect: - // eslint-disable-next-line no-case-declarations - const options = content.split(','); - - return { - ...value, - optionIds: options, - } as SelectOptionFilter; - case FieldType.DateTime: - case FieldType.CreatedTime: - case FieldType.LastEditedTime: - return value as DateFilter; - } - - return value; -} - -function createPredicate (conditions: ((row: Row) => boolean)[]) { - return function (item: Row) { - return every(conditions, (condition) => condition(item)); - }; -} - -export function filterBy (rows: Row[], filters: YDatabaseFilters, fields: YDatabaseFields, rowMetas: Record) { - const filterArray = filters.toArray(); - - if (filterArray.length === 0 || Object.keys(rowMetas).length === 0 || fields.size === 0) return rows; - - const conditions = filterArray.map((filter) => { - return (row: { id: string }) => { - const fieldId = filter.get(YjsDatabaseKey.field_id); - const field = fields.get(fieldId); - const fieldType = Number(field.get(YjsDatabaseKey.type)); - const rowId = row.id; - const rowMeta = rowMetas[rowId]; - - if (!rowMeta) return false; - const filterValue = parseFilter(fieldType, filter); - const meta = rowMeta.getMap(YjsEditorKey.data_section).get(YjsEditorKey.database_row) as YDatabaseRow; - - if (!meta) return false; - - const cells = meta.get(YjsDatabaseKey.cells); - const cell = cells.get(fieldId); - - if (!cell) return false; - const { condition, content } = filterValue; - - switch (fieldType) { - case FieldType.URL: - case FieldType.RichText: - return textFilterCheck(cell.get(YjsDatabaseKey.data) as string, content, condition); - case FieldType.Number: - return numberFilterCheck(cell.get(YjsDatabaseKey.data) as string, content, condition); - case FieldType.Checkbox: - return checkboxFilterCheck(cell.get(YjsDatabaseKey.data) as string, condition); - case FieldType.SingleSelect: - case FieldType.MultiSelect: - return selectOptionFilterCheck(cell.get(YjsDatabaseKey.data) as string, content, condition); - case FieldType.Checklist: - return checklistFilterCheck(cell.get(YjsDatabaseKey.data) as string, content, condition); - default: - return true; - } - }; - }); - const predicate = createPredicate(conditions); - - return filter(rows, predicate); -} - -export function textFilterCheck (data: string, content: string, condition: TextFilterCondition) { - switch (condition) { - case TextFilterCondition.TextContains: - return data.includes(content); - case TextFilterCondition.TextDoesNotContain: - return !data.includes(content); - case TextFilterCondition.TextIs: - return data === content; - case TextFilterCondition.TextIsNot: - return data !== content; - case TextFilterCondition.TextIsEmpty: - return data === ''; - case TextFilterCondition.TextIsNotEmpty: - return data !== ''; - default: - return false; - } -} - -export function numberFilterCheck (data: string, content: string, condition: number) { - if (isNaN(Number(data)) || isNaN(Number(content)) || data === '' || content === '') { - if (condition === NumberFilterCondition.NumberIsEmpty) { - return data === ''; - } - - if (condition === NumberFilterCondition.NumberIsNotEmpty) { - return data !== ''; - } - - return false; - } - - const decimal = new Decimal(data).toNumber(); - const filterDecimal = new Decimal(content).toNumber(); - - switch (condition) { - case NumberFilterCondition.Equal: - return decimal === filterDecimal; - case NumberFilterCondition.NotEqual: - return decimal !== filterDecimal; - case NumberFilterCondition.GreaterThan: - return decimal > filterDecimal; - case NumberFilterCondition.GreaterThanOrEqualTo: - return decimal >= filterDecimal; - case NumberFilterCondition.LessThan: - return decimal < filterDecimal; - case NumberFilterCondition.LessThanOrEqualTo: - return decimal <= filterDecimal; - default: - return false; - } -} - -export function checkboxFilterCheck (data: string, condition: number) { - switch (condition) { - case CheckboxFilterCondition.IsChecked: - return data === 'Yes'; - case CheckboxFilterCondition.IsUnChecked: - return data !== 'Yes'; - default: - return false; - } -} - -export function checklistFilterCheck (data: string, content: string, condition: number) { - const percentage = parseChecklistData(data)?.percentage ?? 0; - - if (condition === ChecklistFilterCondition.IsComplete) { - return percentage === 1; - } - - return percentage !== 1; -} - -export function selectOptionFilterCheck (data: string, content: string, condition: number) { - if (SelectOptionFilterCondition.OptionIsEmpty === condition) { - return data === ''; - } - - if (SelectOptionFilterCondition.OptionIsNotEmpty === condition) { - return data !== ''; - } - - const selectedOptionIds = data.split(','); - const filterOptionIds = content.split(','); - - switch (condition) { - // Ensure all filterOptionIds are included in selectedOptionIds - case SelectOptionFilterCondition.OptionIs: - return every(filterOptionIds, (option) => selectedOptionIds.includes(option)); - - // Ensure none of the filterOptionIds are included in selectedOptionIds - case SelectOptionFilterCondition.OptionIsNot: - return every(filterOptionIds, (option) => !selectedOptionIds.includes(option)); - - // Ensure at least one of the filterOptionIds is included in selectedOptionIds - case SelectOptionFilterCondition.OptionContains: - return some(filterOptionIds, (option) => selectedOptionIds.includes(option)); - - // Ensure at least one of the filterOptionIds is not included in selectedOptionIds - case SelectOptionFilterCondition.OptionDoesNotContain: - return some(filterOptionIds, (option) => !selectedOptionIds.includes(option)); - - // Default case, if no conditions match - default: - return false; - } -} diff --git a/frontend/appflowy_web_app/src/application/database-yjs/group.ts b/frontend/appflowy_web_app/src/application/database-yjs/group.ts deleted file mode 100644 index 461748605e..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/group.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { RowId, YDatabaseField, YDoc, YjsDatabaseKey } from '@/application/types'; -import { getCellData } from '@/application/database-yjs/const'; -import { FieldType } from '@/application/database-yjs/database.type'; -import { parseSelectOptionTypeOptions } from '@/application/database-yjs/fields'; -import { Row } from '@/application/database-yjs/selector'; - -export function groupByField (rows: Row[], rowMetas: Record, field: YDatabaseField) { - const fieldType = Number(field.get(YjsDatabaseKey.type)); - const isSelectOptionField = [FieldType.SingleSelect, FieldType.MultiSelect].includes(fieldType); - - if (isSelectOptionField) { - return groupBySelectOption(rows, rowMetas, field); - } - - if (fieldType === FieldType.Checkbox) { - return groupByCheckbox(rows, rowMetas, field); - } - - return; -} - -export function groupByCheckbox (rows: Row[], rowMetas: Record, field: YDatabaseField) { - const fieldId = field.get(YjsDatabaseKey.id); - const result = new Map(); - - rows.forEach((row) => { - const cellData = getCellData(row.id, fieldId, rowMetas); - - const groupName = cellData === 'Yes' ? 'Yes' : 'No'; - const group = result.get(groupName) ?? []; - - group.push(row); - result.set(groupName, group); - }); - return result; -} - -export function groupBySelectOption (rows: Row[], rowMetas: Record, field: YDatabaseField) { - const fieldId = field.get(YjsDatabaseKey.id); - const result = new Map(); - const typeOption = parseSelectOptionTypeOptions(field); - - if (!typeOption) { - return; - } - - if (typeOption.options.length === 0) { - result.set(fieldId, rows); - return result; - } - - rows.forEach((row) => { - const cellData = getCellData(row.id, fieldId, rowMetas); - - const selectedIds = (cellData as string)?.split(',') ?? []; - - if (selectedIds.length === 0) { - const group = result.get(fieldId) ?? []; - - group.push(row); - result.set(fieldId, group); - return; - } - - selectedIds.forEach((id) => { - const option = typeOption.options.find((option) => option.id === id); - const groupName = option?.id ?? fieldId; - const group = result.get(groupName) ?? []; - - group.push(row); - result.set(groupName, group); - }); - }); - - return result; -} diff --git a/frontend/appflowy_web_app/src/application/database-yjs/index.ts b/frontend/appflowy_web_app/src/application/database-yjs/index.ts deleted file mode 100644 index 1d5aa0ce3d..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './context'; -export * from './fields'; -export * from './context'; -export * from './selector'; -export * from './database.type'; -export * from './const'; diff --git a/frontend/appflowy_web_app/src/application/database-yjs/selector.ts b/frontend/appflowy_web_app/src/application/database-yjs/selector.ts deleted file mode 100644 index 63ec29d70a..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/selector.ts +++ /dev/null @@ -1,733 +0,0 @@ -import { - FieldId, RowCoverType, - SortId, - YDatabase, - YDatabaseField, YDatabaseMetas, YDatabaseRow, - YjsDatabaseKey, - YjsEditorKey, -} from '@/application/types'; -import { getCell, metaIdFromRowId, MIN_COLUMN_WIDTH } from '@/application/database-yjs/const'; -import { - useDatabase, - useDatabaseFields, - useDatabaseView, - useRowDocMap, - useDatabaseViewId, -} from '@/application/database-yjs/context'; -import { filterBy, parseFilter } from '@/application/database-yjs/filter'; -import { groupByField } from '@/application/database-yjs/group'; -import { sortBy } from '@/application/database-yjs/sort'; -import { parseYDatabaseCellToCell } from '@/application/database-yjs/cell.parse'; -import { DateTimeCell } from '@/application/database-yjs/cell.type'; -import dayjs from 'dayjs'; -import { debounce } from 'lodash-es'; -import { useCallback, useEffect, useMemo, useState } from 'react'; -import { CalendarLayoutSetting, FieldType, FieldVisibility, Filter, RowMetaKey, SortCondition } from './database.type'; - -export interface Column { - fieldId: string; - width: number; - visibility: FieldVisibility; - wrap?: boolean; -} - -export interface Row { - id: string; - height: number; -} - -const defaultVisible = [FieldVisibility.AlwaysShown, FieldVisibility.HideWhenEmpty]; - -export function useDatabaseViewsSelector (_iidIndex: string, visibleViewIds?: string[]) { - const database = useDatabase(); - - const views = database?.get(YjsDatabaseKey.views); - const [viewIds, setViewIds] = useState([]); - const childViews = useMemo(() => { - return viewIds.map((viewId) => views?.get(viewId)); - }, [viewIds, views]); - - useEffect(() => { - if (!views) return; - - const observerEvent = () => { - const viewsObj = views.toJSON() as Record< - string, - { - created_at: number; - } - >; - - const viewsSorted = Object.entries(viewsObj).sort((a, b) => { - const [, viewA] = a; - const [, viewB] = b; - - return Number(viewB.created_at) - Number(viewA.created_at); - }); - - setViewIds( - viewsSorted - .map(([key]) => key) - .filter((id) => { - return !visibleViewIds || visibleViewIds.includes(id); - }), - ); - }; - - observerEvent(); - views.observe(observerEvent); - - return () => { - views.unobserve(observerEvent); - }; - }, [views, visibleViewIds]); - - return { - childViews, - viewIds, - }; -} - -export function useFieldsSelector (visibilitys: FieldVisibility[] = defaultVisible) { - const viewId = useDatabaseViewId(); - const database = useDatabase(); - const [columns, setColumns] = useState([]); - - useEffect(() => { - if (!viewId) return; - const view = database?.get(YjsDatabaseKey.views)?.get(viewId); - const fields = database?.get(YjsDatabaseKey.fields); - const fieldsOrder = view?.get(YjsDatabaseKey.field_orders); - const fieldSettings = view?.get(YjsDatabaseKey.field_settings); - const getColumns = () => { - if (!fields || !fieldsOrder || !fieldSettings) return []; - - const fieldIds = (fieldsOrder.toJSON() as { id: string }[]).map((item) => item.id); - - return fieldIds - .map((fieldId) => { - const setting = fieldSettings.get(fieldId); - - return { - fieldId, - width: parseInt(setting?.get(YjsDatabaseKey.width)) || MIN_COLUMN_WIDTH, - visibility: Number( - setting?.get(YjsDatabaseKey.visibility) || FieldVisibility.AlwaysShown, - ) as FieldVisibility, - wrap: setting?.get(YjsDatabaseKey.wrap) ?? true, - }; - }) - .filter((column) => { - return visibilitys.includes(column.visibility); - }); - }; - - const observerEvent = () => setColumns(getColumns()); - - setColumns(getColumns()); - - fieldsOrder?.observe(observerEvent); - fieldSettings?.observe(observerEvent); - - return () => { - fieldsOrder?.unobserve(observerEvent); - fieldSettings?.unobserve(observerEvent); - }; - }, [database, viewId, visibilitys]); - - return columns; -} - -export function useFieldSelector (fieldId: string) { - const database = useDatabase(); - const [field, setField] = useState(null); - const [clock, setClock] = useState(0); - - useEffect(() => { - if (!database) return; - - const field = database.get(YjsDatabaseKey.fields)?.get(fieldId); - - setField(field || null); - const observerEvent = () => setClock((prev) => prev + 1); - - field?.observe(observerEvent); - - return () => { - field?.unobserve(observerEvent); - }; - }, [database, fieldId]); - - return { - field, - clock, - }; -} - -export function useFiltersSelector () { - const database = useDatabase(); - const viewId = useDatabaseViewId(); - const [filters, setFilters] = useState([]); - - useEffect(() => { - if (!viewId) return; - const view = database?.get(YjsDatabaseKey.views)?.get(viewId); - const filterOrders = view?.get(YjsDatabaseKey.filters); - - if (!filterOrders) return; - - const getFilters = () => { - return (filterOrders.toJSON() as { id: string }[]).map((item) => item.id); - }; - - const observerEvent = () => setFilters(getFilters()); - - setFilters(getFilters()); - - filterOrders.observe(observerEvent); - - return () => { - filterOrders.unobserve(observerEvent); - }; - }, [database, viewId]); - - return filters; -} - -export function useFilterSelector (filterId: string) { - const database = useDatabase(); - const viewId = useDatabaseViewId(); - const fields = database?.get(YjsDatabaseKey.fields); - const [filterValue, setFilterValue] = useState(null); - - useEffect(() => { - if (!viewId) return; - const view = database?.get(YjsDatabaseKey.views)?.get(viewId); - const filter = view - ?.get(YjsDatabaseKey.filters) - .toArray() - .find((filter) => filter.get(YjsDatabaseKey.id) === filterId); - const field = fields?.get(filter?.get(YjsDatabaseKey.field_id) as FieldId); - - const observerEvent = () => { - if (!filter || !field) return; - const fieldType = Number(field.get(YjsDatabaseKey.type)) as FieldType; - - setFilterValue(parseFilter(fieldType, filter)); - }; - - observerEvent(); - field?.observe(observerEvent); - filter?.observe(observerEvent); - return () => { - field?.unobserve(observerEvent); - filter?.unobserve(observerEvent); - }; - }, [fields, viewId, filterId, database]); - return filterValue; -} - -export function useSortsSelector () { - const database = useDatabase(); - const viewId = useDatabaseViewId(); - const [sorts, setSorts] = useState([]); - - useEffect(() => { - if (!viewId) return; - const view = database?.get(YjsDatabaseKey.views)?.get(viewId); - const sortOrders = view?.get(YjsDatabaseKey.sorts); - - if (!sortOrders) return; - - const getSorts = () => { - return (sortOrders.toJSON() as { id: string }[]).map((item) => item.id); - }; - - const observerEvent = () => setSorts(getSorts()); - - setSorts(getSorts()); - - sortOrders.observe(observerEvent); - - return () => { - sortOrders.unobserve(observerEvent); - }; - }, [database, viewId]); - - return sorts; -} - -export interface Sort { - fieldId: FieldId; - condition: SortCondition; - id: SortId; -} - -export function useSortSelector (sortId: SortId) { - const database = useDatabase(); - const viewId = useDatabaseViewId(); - const [sortValue, setSortValue] = useState(null); - const views = database?.get(YjsDatabaseKey.views); - - useEffect(() => { - if (!viewId) return; - const view = views?.get(viewId); - const sort = view - ?.get(YjsDatabaseKey.sorts) - .toArray() - .find((sort) => sort.get(YjsDatabaseKey.id) === sortId); - - const observerEvent = () => { - setSortValue({ - fieldId: sort?.get(YjsDatabaseKey.field_id) as FieldId, - condition: Number(sort?.get(YjsDatabaseKey.condition)), - id: sort?.get(YjsDatabaseKey.id) as SortId, - }); - }; - - observerEvent(); - sort?.observe(observerEvent); - - return () => { - sort?.unobserve(observerEvent); - }; - }, [viewId, sortId, views]); - - return sortValue; -} - -export function useGroupsSelector () { - const database = useDatabase(); - const viewId = useDatabaseViewId(); - const [groups, setGroups] = useState([]); - - useEffect(() => { - if (!viewId) return; - const view = database?.get(YjsDatabaseKey.views)?.get(viewId); - - const groupOrders = view?.get(YjsDatabaseKey.groups); - - if (!groupOrders) return; - - const getGroups = () => { - return (groupOrders.toJSON() as { id: string }[]).map((item) => item.id); - }; - - const observerEvent = () => setGroups(getGroups()); - - setGroups(getGroups()); - - groupOrders.observe(observerEvent); - - return () => { - groupOrders.unobserve(observerEvent); - }; - }, [database, viewId]); - - return groups; -} - -export interface GroupColumn { - id: string; - visible: boolean; -} - -export function useGroup (groupId: string) { - const database = useDatabase(); - const viewId = useDatabaseViewId(); - const view = database?.get(YjsDatabaseKey.views)?.get(viewId); - const group = view - ?.get(YjsDatabaseKey.groups) - ?.toArray() - .find((group) => group.get(YjsDatabaseKey.id) === groupId); - const groupColumns = group?.get(YjsDatabaseKey.groups); - const [fieldId, setFieldId] = useState(null); - const [columns, setColumns] = useState([]); - - useEffect(() => { - if (!viewId) return; - - const observerEvent = () => { - setFieldId(group?.get(YjsDatabaseKey.field_id) as string); - }; - - observerEvent(); - group?.observe(observerEvent); - - const observerColumns = () => { - if (!groupColumns) return; - setColumns(groupColumns.toJSON()); - }; - - observerColumns(); - groupColumns?.observe(observerColumns); - - return () => { - group?.unobserve(observerEvent); - groupColumns?.unobserve(observerColumns); - }; - }, [database, viewId, groupId, group, groupColumns]); - - return { - columns, - fieldId, - }; -} - -export function useRowsByGroup (groupId: string) { - const { columns, fieldId } = useGroup(groupId); - const rows = useRowDocMap(); - const rowOrders = useRowOrdersSelector(); - - const fields = useDatabaseFields(); - const [notFound, setNotFound] = useState(false); - const [groupResult, setGroupResult] = useState>(new Map()); - const view = useDatabaseView(); - const layoutSetting = view?.get(YjsDatabaseKey.layout_settings)?.get('1'); - - useEffect(() => { - if (!fieldId || !rowOrders || !rows) return; - - const onConditionsChange = () => { - const newResult = new Map(); - - const field = fields.get(fieldId); - - if (!field) { - setNotFound(true); - setGroupResult(newResult); - return; - } - - const groupResult = groupByField(rowOrders, rows, field); - - if (!groupResult) { - setGroupResult(newResult); - return; - } - - setGroupResult(groupResult); - }; - - onConditionsChange(); - - fields.observeDeep(onConditionsChange); - return () => { - fields.unobserveDeep(onConditionsChange); - }; - }, [fieldId, fields, rowOrders, rows]); - - const visibleColumns = columns.filter((column) => { - if (column.id === fieldId) return !layoutSetting?.get(YjsDatabaseKey.hide_ungrouped_column); - return column.visible; - }); - - return { - fieldId, - groupResult, - columns: visibleColumns, - notFound, - }; -} - -export function useRowOrdersSelector () { - const rows = useRowDocMap(); - const [rowOrders, setRowOrders] = useState(); - const view = useDatabaseView(); - const sorts = view?.get(YjsDatabaseKey.sorts); - const fields = useDatabaseFields(); - const filters = view?.get(YjsDatabaseKey.filters); - const onConditionsChange = useCallback(() => { - const originalRowOrders = view?.get(YjsDatabaseKey.row_orders).toJSON(); - - if (!originalRowOrders || !rows) return; - - if (sorts?.length === 0 && filters?.length === 0) { - setRowOrders(originalRowOrders); - return; - } - - let rowOrders: Row[] | undefined; - - if (sorts?.length) { - rowOrders = sortBy(originalRowOrders, sorts, fields, rows); - } - - if (filters?.length) { - rowOrders = filterBy(rowOrders ?? originalRowOrders, filters, fields, rows); - } - - if (rowOrders) { - setRowOrders(rowOrders); - } else { - setRowOrders(originalRowOrders); - } - }, [fields, filters, rows, sorts, view]); - - useEffect(() => { - onConditionsChange(); - }, [onConditionsChange]); - - useEffect(() => { - const throttleChange = debounce(onConditionsChange, 200); - - view?.get(YjsDatabaseKey.row_orders)?.observeDeep(throttleChange); - sorts?.observeDeep(throttleChange); - filters?.observeDeep(throttleChange); - fields?.observeDeep(throttleChange); - - return () => { - view?.get(YjsDatabaseKey.row_orders)?.unobserveDeep(throttleChange); - sorts?.unobserveDeep(throttleChange); - filters?.unobserveDeep(throttleChange); - fields?.unobserveDeep(throttleChange); - }; - }, [onConditionsChange, view, fields, filters, sorts]); - - return rowOrders; -} - -export function useRowDataSelector (rowId: string) { - const rowMap = useRowDocMap(); - const [row, setRow] = useState(null); - - useEffect(() => { - const rowDoc = rowMap?.[rowId]; - - if (!rowDoc || !rowDoc.share.has(YjsEditorKey.data_section)) return; - const rowSharedRoot = rowDoc?.getMap(YjsEditorKey.data_section); - const row = rowSharedRoot?.get(YjsEditorKey.database_row); - - setRow(row); - }, [rowId, rowMap]); - return { - row, - }; -} - -export function useCellSelector ({ rowId, fieldId }: { rowId: string; fieldId: string }) { - const { row } = useRowDataSelector(rowId); - const cell = row?.get(YjsDatabaseKey.cells)?.get(fieldId); - - const [cellValue, setCellValue] = useState(() => (cell ? parseYDatabaseCellToCell(cell) : undefined)); - - useEffect(() => { - if (!cell) return; - setCellValue(parseYDatabaseCellToCell(cell)); - const observerEvent = () => setCellValue(parseYDatabaseCellToCell(cell)); - - cell.observeDeep(observerEvent); - - return () => { - cell.unobserveDeep(observerEvent); - }; - }, [cell]); - - return cellValue; -} - -export interface CalendarEvent { - start?: Date; - end?: Date; - id: string; -} - -export function useCalendarEventsSelector () { - const setting = useCalendarLayoutSetting(); - const filedId = setting.fieldId; - const { field } = useFieldSelector(filedId); - const rowOrders = useRowOrdersSelector(); - const rows = useRowDocMap(); - const [events, setEvents] = useState([]); - const [emptyEvents, setEmptyEvents] = useState([]); - - useEffect(() => { - if (!field || !rowOrders || !rows) return; - const fieldType = Number(field?.get(YjsDatabaseKey.type)) as FieldType; - - if (fieldType !== FieldType.DateTime) return; - const newEvents: CalendarEvent[] = []; - const emptyEvents: CalendarEvent[] = []; - - rowOrders?.forEach((row) => { - const cell = getCell(row.id, filedId, rows); - - if (!cell) { - emptyEvents.push({ - id: `${row.id}:${filedId}`, - }); - return; - } - - const value = parseYDatabaseCellToCell(cell) as DateTimeCell; - - if (!value || !value.data) { - emptyEvents.push({ - id: `${row.id}:${filedId}`, - }); - return; - } - - const getDate = (timestamp: string) => { - const dayjsResult = timestamp.length === 10 ? dayjs.unix(Number(timestamp)) : dayjs(timestamp); - - return dayjsResult.toDate(); - }; - - newEvents.push({ - id: `${row.id}:${filedId}`, - start: getDate(value.data), - end: value.endTimestamp && value.isRange ? getDate(value.endTimestamp) : getDate(value.data), - }); - }); - - setEvents(newEvents); - setEmptyEvents(emptyEvents); - }, [field, rowOrders, rows, filedId]); - - return { events, emptyEvents }; -} - -export function useCalendarLayoutSetting () { - const view = useDatabaseView(); - const layoutSetting = view?.get(YjsDatabaseKey.layout_settings)?.get('2'); - const [setting, setSetting] = useState({ - fieldId: '', - firstDayOfWeek: 0, - showWeekNumbers: true, - showWeekends: true, - layout: 0, - }); - - useEffect(() => { - const observerHandler = () => { - setSetting({ - fieldId: layoutSetting?.get(YjsDatabaseKey.field_id) as string, - firstDayOfWeek: Number(layoutSetting?.get(YjsDatabaseKey.first_day_of_week)), - showWeekNumbers: Boolean(layoutSetting?.get(YjsDatabaseKey.show_week_numbers)), - showWeekends: Boolean(layoutSetting?.get(YjsDatabaseKey.show_weekends)), - layout: Number(layoutSetting?.get(YjsDatabaseKey.layout_ty)), - }); - }; - - observerHandler(); - layoutSetting?.observe(observerHandler); - return () => { - layoutSetting?.unobserve(observerHandler); - }; - }, [layoutSetting]); - - return setting; -} - -export function getPrimaryFieldId (database: YDatabase) { - const fields = database?.get(YjsDatabaseKey.fields); - - return Array.from(fields?.keys() || []).find((fieldId) => { - return fields?.get(fieldId)?.get(YjsDatabaseKey.is_primary); - }); -} - -export function usePrimaryFieldId () { - const database = useDatabase(); - const [primaryFieldId, setPrimaryFieldId] = useState(null); - - useEffect(() => { - setPrimaryFieldId(getPrimaryFieldId(database) || null); - }, [database]); - - return primaryFieldId; -} - -export interface RowMeta { - documentId: string; - cover: { - data: string, - cover_type: RowCoverType, - } | null; - icon: string; - isEmptyDocument: boolean; -} - -const metaIdMapFromRowIdMap = new Map>(); - -function getMetaIdMap (rowId: string) { - const hasMetaIdMap = metaIdMapFromRowIdMap.has(rowId); - - if (!hasMetaIdMap) { - const parser = metaIdFromRowId(rowId); - const map = new Map(); - - map.set(RowMetaKey.IconId, parser(RowMetaKey.IconId)); - map.set(RowMetaKey.CoverId, parser(RowMetaKey.CoverId)); - map.set(RowMetaKey.DocumentId, parser(RowMetaKey.DocumentId)); - map.set(RowMetaKey.IsDocumentEmpty, parser(RowMetaKey.IsDocumentEmpty)); - metaIdMapFromRowIdMap.set(rowId, map); - return map; - } - - return metaIdMapFromRowIdMap.get(rowId) as Map; -} - -export const useRowMetaSelector = (rowId: string) => { - const [meta, setMeta] = useState(); - const rowMap = useRowDocMap(); - - const updateMeta = useCallback(() => { - - const row = rowMap?.[rowId]; - - if (!row || !row.share.has(YjsEditorKey.data_section)) return; - - const rowSharedRoot = row.getMap(YjsEditorKey.data_section); - - const yMeta = rowSharedRoot?.get(YjsEditorKey.meta); - - if (!yMeta) return; - - const metaKeyMap = getMetaIdMap(rowId); - - const iconKey = metaKeyMap.get(RowMetaKey.IconId) ?? ''; - const coverKey = metaKeyMap.get(RowMetaKey.CoverId) ?? ''; - const documentId = metaKeyMap.get(RowMetaKey.DocumentId) ?? ''; - const isEmptyDocumentKey = metaKeyMap.get(RowMetaKey.IsDocumentEmpty) ?? ''; - const metaJson = yMeta.toJSON(); - - const icon = metaJson[iconKey]; - let cover = null; - - try { - cover = metaJson[coverKey] ? JSON.parse(metaJson[coverKey]) : null; - } catch (e) { - // do nothing - } - - const isEmptyDocument = metaJson[isEmptyDocumentKey]; - - setMeta({ - icon, - cover, - documentId, - isEmptyDocument, - }); - }, [rowId, rowMap]); - - useEffect(() => { - if (!rowMap) return; - updateMeta(); - const observerEvent = () => updateMeta(); - - const rowDoc = rowMap[rowId]; - - if (!rowDoc || !rowDoc.share.has(YjsEditorKey.data_section)) return; - const rowSharedRoot = rowDoc.getMap(YjsEditorKey.data_section); - const meta = rowSharedRoot?.get(YjsEditorKey.meta) as YDatabaseMetas; - - meta?.observeDeep(observerEvent); - return () => { - meta?.unobserveDeep(observerEvent); - }; - }, [rowId, rowMap, updateMeta]); - - return meta; -}; diff --git a/frontend/appflowy_web_app/src/application/database-yjs/sort.ts b/frontend/appflowy_web_app/src/application/database-yjs/sort.ts deleted file mode 100644 index 5e6e078d89..0000000000 --- a/frontend/appflowy_web_app/src/application/database-yjs/sort.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { - RowId, - YDatabaseField, - YDatabaseFields, - YDatabaseRow, - YDatabaseSorts, - YDoc, - YjsDatabaseKey, - YjsEditorKey, -} from '@/application/types'; -import { FieldType, SortCondition } from '@/application/database-yjs/database.type'; -import { parseChecklistData, parseSelectOptionCellData } from '@/application/database-yjs/fields'; -import { Row } from '@/application/database-yjs/selector'; -import { orderBy } from 'lodash-es'; - -export function sortBy (rows: Row[], sorts: YDatabaseSorts, fields: YDatabaseFields, rowMetas: Record) { - const sortArray = sorts.toArray(); - - if (sortArray.length === 0 || Object.keys(rowMetas).length === 0 || fields.size === 0) return rows; - const iteratees = sortArray.map((sort) => { - return (row: { id: string }) => { - const fieldId = sort.get(YjsDatabaseKey.field_id); - const field = fields.get(fieldId); - const fieldType = Number(field.get(YjsDatabaseKey.type)); - - const rowId = row.id; - const rowMeta = rowMetas[rowId]; - - const defaultData = parseCellDataForSort(field, ''); - - const meta = rowMeta?.getMap(YjsEditorKey.data_section).get(YjsEditorKey.database_row) as YDatabaseRow; - - if (!meta) return defaultData; - if (fieldType === FieldType.LastEditedTime) { - return meta.get(YjsDatabaseKey.last_modified); - } - - if (fieldType === FieldType.CreatedTime) { - return meta.get(YjsDatabaseKey.created_at); - } - - const cells = meta.get(YjsDatabaseKey.cells); - const cell = cells.get(fieldId); - - if (!cell) return defaultData; - - return parseCellDataForSort(field, cell.get(YjsDatabaseKey.data) ?? ''); - }; - }); - const orders = sortArray.map((sort) => { - const condition = Number(sort.get(YjsDatabaseKey.condition)); - - if (condition === SortCondition.Descending) return 'desc'; - return 'asc'; - }); - - return orderBy(rows, iteratees, orders); -} - -export function parseCellDataForSort (field: YDatabaseField, data: string | boolean | number | object) { - const fieldType = Number(field.get(YjsDatabaseKey.type)); - - switch (fieldType) { - case FieldType.RichText: - case FieldType.URL: - return data ? data : '\uFFFF'; - case FieldType.Number: - return data === 'string' && !isNaN(parseInt(data)) ? parseInt(data) : data; - case FieldType.Checkbox: - return data === 'Yes'; - case FieldType.SingleSelect: - case FieldType.MultiSelect: - return parseSelectOptionCellData(field, data as string); - case FieldType.Checklist: - return parseChecklistData(data as string)?.percentage ?? 0; - case FieldType.DateTime: - return Number(data); - case FieldType.Relation: - return ''; - } -} diff --git a/frontend/appflowy_web_app/src/application/db/index.ts b/frontend/appflowy_web_app/src/application/db/index.ts deleted file mode 100644 index 25406a4cf7..0000000000 --- a/frontend/appflowy_web_app/src/application/db/index.ts +++ /dev/null @@ -1,113 +0,0 @@ -import { userSchema, UserTable } from '@/application/db/tables/users'; -import { YDoc } from '@/application/types'; -import { databasePrefix } from '@/application/constants'; -import { IndexeddbPersistence } from 'y-indexeddb'; -import * as Y from 'yjs'; -import BaseDexie from 'dexie'; -import { viewMetasSchema, ViewMetasTable } from '@/application/db/tables/view_metas'; -import { rowSchema, rowTable } from '@/application/db/tables/rows'; - -type DexieTables = ViewMetasTable & UserTable & rowTable; - -export type Dexie = BaseDexie & T; - -export const db = new BaseDexie(`${databasePrefix}_cache`) as Dexie; -const schema = Object.assign({}, { ...viewMetasSchema, ...userSchema, ...rowSchema }); - -db.version(1).stores(schema); - -const openedSet = new Set(); - -/** - * Open the collaboration database, and return a function to close it - */ -export async function openCollabDB(docName: string): Promise { - const name = `${databasePrefix}_${docName}`; - const doc = new Y.Doc({ - guid: docName, - }); - - const provider = new IndexeddbPersistence(name, doc); - - let resolve: (value: unknown) => void; - const promise = new Promise((resolveFn) => { - resolve = resolveFn; - }); - - provider.on('synced', () => { - if (!openedSet.has(name)) { - openedSet.add(name); - } - - resolve(true); - }); - - await promise; - - return doc as YDoc; -} - -export async function closeCollabDB(docName: string) { - const name = `${databasePrefix}_${docName}`; - - if (openedSet.has(name)) { - openedSet.delete(name); - } - - const doc = new Y.Doc(); - - const provider = new IndexeddbPersistence(name, doc); - - await provider.destroy(); -} - -export async function clearData() { - const databases = await indexedDB.databases(); - - const deleteDatabase = async (dbInfo: IDBDatabaseInfo): Promise => { - const dbName = dbInfo.name; - - if (!dbName) return false; - - return new Promise((resolve) => { - const request = indexedDB.open(dbName); - - request.onsuccess = (event) => { - const db = (event.target as IDBOpenDBRequest).result; - - db.close(); - - const deleteRequest = indexedDB.deleteDatabase(dbName); - - deleteRequest.onsuccess = () => { - console.log(`Database ${dbName} deleted successfully`); - resolve(true); - }; - - deleteRequest.onerror = (event) => { - console.error(`Error deleting database ${dbName}`, event); - resolve(false); - }; - - deleteRequest.onblocked = () => { - console.warn(`Delete operation blocked for database ${dbName}`); - resolve(false); - }; - }; - - request.onerror = (event) => { - console.error(`Error opening database ${dbName}`, event); - resolve(false); - }; - }); - }; - - try { - const results = await Promise.all(databases.map(deleteDatabase)); - - return results.every(Boolean); - } catch (error) { - console.error('Error during database deletion process:', error); - return false; - } -} diff --git a/frontend/appflowy_web_app/src/application/db/tables/rows.ts b/frontend/appflowy_web_app/src/application/db/tables/rows.ts deleted file mode 100644 index 1275a347f4..0000000000 --- a/frontend/appflowy_web_app/src/application/db/tables/rows.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Table } from 'dexie'; - -export type rowTable = { - rows: Table<{ - row_id: string; - row_key: string; - version: number; - }>; -}; - -export const rowSchema = { - rows: 'row_key', -}; \ No newline at end of file diff --git a/frontend/appflowy_web_app/src/application/db/tables/users.ts b/frontend/appflowy_web_app/src/application/db/tables/users.ts deleted file mode 100644 index 2b84d1ad0e..0000000000 --- a/frontend/appflowy_web_app/src/application/db/tables/users.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { User } from '@/application/types'; -import { Table } from 'dexie'; - -export type UserTable = { - users: Table; -}; - -export const userSchema = { - users: 'uuid', -}; \ No newline at end of file diff --git a/frontend/appflowy_web_app/src/application/db/tables/view_metas.ts b/frontend/appflowy_web_app/src/application/db/tables/view_metas.ts deleted file mode 100644 index 9c851e6fb1..0000000000 --- a/frontend/appflowy_web_app/src/application/db/tables/view_metas.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Table } from 'dexie'; -import { ViewInfo } from '@/application/types'; - -export type ViewMeta = { - publish_name: string; - - child_views: ViewInfo[]; - ancestor_views: ViewInfo[]; - - visible_view_ids: string[]; - database_relations: Record; -} & ViewInfo; - -export type ViewMetasTable = { - view_metas: Table; -}; - -export const viewMetasSchema = { - view_metas: 'publish_name', -}; diff --git a/frontend/appflowy_web_app/src/application/publish/context.tsx b/frontend/appflowy_web_app/src/application/publish/context.tsx deleted file mode 100644 index bb33c38cec..0000000000 --- a/frontend/appflowy_web_app/src/application/publish/context.tsx +++ /dev/null @@ -1,397 +0,0 @@ -import { db } from '@/application/db'; -import { ViewMeta } from '@/application/db/tables/view_metas'; -import { - AppendBreadcrumb, - CreateRowDoc, - LoadView, - LoadViewMeta, - View, - ViewInfo, - ViewLayout, -} from '@/application/types'; -import { notify } from '@/components/_shared/notify'; -import { findAncestors, findView } from '@/components/_shared/outline/utils'; -import { useService } from '@/components/main/app.hooks'; -import { useLiveQuery } from 'dexie-react-hooks'; -import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; -import { useNavigate } from 'react-router-dom'; - -export interface PublishContextType { - namespace: string; - publishName: string; - isTemplate?: boolean; - isTemplateThumb?: boolean; - viewMeta?: ViewMeta; - toView: (viewId: string, blockId?: string) => Promise; - loadViewMeta: LoadViewMeta; - createRowDoc?: CreateRowDoc; - loadView: LoadView; - outline?: View[]; - appendBreadcrumb?: AppendBreadcrumb; - breadcrumbs: View[]; - rendered?: boolean; - onRendered?: () => void; -} - -export const PublishContext = createContext(null); - -export const PublishProvider = ({ - children, - namespace, - publishName, - isTemplateThumb, - isTemplate, -}: { - children: React.ReactNode; - namespace: string; - publishName: string; - isTemplateThumb?: boolean; - isTemplate?: boolean; -}) => { - const [outline, setOutline] = useState([]); - const createdRowKeys = useRef([]); - const [rendered, setRendered] = useState(false); - - const [subscribers, setSubscribers] = useState void>>(new Map()); - - useEffect(() => { - return () => { - setSubscribers(new Map()); - }; - }, []); - - const viewMeta = useLiveQuery(async () => { - const name = `${namespace}_${publishName}`; - - const view = await db.view_metas.get(name); - - if (!view) return; - - return { - ...view, - name: findView(outline, view.view_id)?.name || view.name, - }; - }, [namespace, publishName, outline]); - - const originalCrumbs = useMemo(() => { - if (!viewMeta || !outline) return []; - const ancestors = findAncestors(outline, viewMeta?.view_id); - - if (ancestors) return ancestors; - if (!viewMeta?.ancestor_views) return []; - const parseToView = (ancestor: ViewInfo): View => { - let extra = null; - - try { - extra = ancestor.extra ? JSON.parse(ancestor.extra) : null; - } catch (e) { - // do nothing - } - - return { - view_id: ancestor.view_id, - name: ancestor.name, - icon: ancestor.icon, - layout: ancestor.layout, - extra, - is_published: true, - children: [], - is_private: false, - }; - }; - - const currentView = parseToView(viewMeta); - - return viewMeta?.ancestor_views.slice(1).map(item => findView(outline, item.view_id) || parseToView(item)) || [currentView]; - }, [viewMeta, outline]); - - const [breadcrumbs, setBreadcrumbs] = useState([]); - - useEffect(() => { - setBreadcrumbs(originalCrumbs); - }, [originalCrumbs]); - - const appendBreadcrumb = useCallback((view?: View) => { - setBreadcrumbs((prev) => { - if (!view) { - return prev.slice(0, -1); - } - - const index = prev.findIndex((v) => v.view_id === view.view_id); - - if (index === -1) { - return [...prev, view]; - } - - const rest = prev.slice(0, index); - - return [...rest, view]; - }); - }, []); - - useEffect(() => { - db.view_metas.hook('creating', (primaryKey, obj) => { - const subscriber = subscribers.get(primaryKey); - - subscriber?.(obj); - - return obj; - }); - db.view_metas.hook('deleting', (primaryKey, obj) => { - const subscriber = subscribers.get(primaryKey); - - subscriber?.(obj); - - return; - }); - db.view_metas.hook('updating', (modifications, primaryKey, obj) => { - const subscriber = subscribers.get(primaryKey); - - subscriber?.({ - ...obj, - ...modifications, - }); - - return modifications; - }); - }, [subscribers]); - - const prevViewMeta = useRef(viewMeta); - - const service = useService(); - - useEffect(() => { - const rowKeys = createdRowKeys.current; - - createdRowKeys.current = []; - - if (!rowKeys.length) return; - rowKeys.forEach((rowKey) => { - try { - service?.deleteRowDoc(rowKey); - } catch (e) { - console.error(e); - } - }); - - }, [service, publishName]); - const navigate = useNavigate(); - - const loadViewMeta = useCallback( - async (viewId: string, callback?: (meta: View) => void) => { - try { - const info = await service?.getPublishInfo(viewId); - - if (!info) { - throw new Error('View has not been published yet'); - } - - const { namespace, publishName } = info; - - const name = `${namespace}_${publishName}`; - - const meta = await service?.getPublishViewMeta(namespace, publishName); - - if (!meta) { - return Promise.reject(new Error('View meta has not been published yet')); - } - - const parseMetaToView = (meta: ViewInfo | ViewMeta): View => { - return { - is_private: false, - view_id: meta.view_id, - name: meta.name, - layout: meta.layout, - extra: meta.extra ? JSON.parse(meta.extra) : undefined, - icon: meta.icon, - children: meta.child_views?.map(parseMetaToView) || [], - is_published: true, - database_relations: 'database_relations' in meta ? meta.database_relations : undefined, - }; - }; - - const res = parseMetaToView(meta); - - callback?.(res); - - if (callback) { - setSubscribers((prev) => { - prev.set(name, (meta) => { - return callback?.(parseMetaToView(meta)); - }); - - return prev; - }); - } - - return res; - } catch (e) { - return Promise.reject(e); - } - }, - [service], - ); - - const toView = useCallback( - async (viewId: string, blockId?: string) => { - try { - const view = await loadViewMeta(viewId); - - const res = await service?.getPublishInfo(viewId); - - if (!res) { - throw new Error('View has not been published yet'); - } - - const { namespace: viewNamespace, publishName } = res; - - prevViewMeta.current = undefined; - const searchParams = new URLSearchParams(''); - - if (blockId) { - switch (view.layout) { - case ViewLayout.Document: - searchParams.set('blockId', blockId); - break; - case ViewLayout.Grid: - case ViewLayout.Board: - case ViewLayout.Calendar: - searchParams.set('r', blockId); - break; - default: - break; - } - } - - if (isTemplate) { - searchParams.set('template', 'true'); - } - - let url = `/${viewNamespace}/${publishName}`; - - if (searchParams.toString()) { - url += `?${searchParams.toString()}`; - } - - navigate(url, { - replace: true, - }); - return; - } catch (e) { - return Promise.reject(e); - } - }, - [loadViewMeta, service, isTemplate, navigate], - ); - - const loadOutline = useCallback(async () => { - if (!service || !namespace) return; - try { - const res = await service?.getPublishOutline(namespace); - - if (!res) { - throw new Error('Publish outline not found'); - } - - setOutline(res); - } catch (e) { - notify.error('Publish outline not found'); - } - }, [namespace, service]); - - const createRowDoc = useCallback( - async (rowKey: string) => { - try { - const doc = await service?.createRowDoc(rowKey); - - if (!doc) { - throw new Error('Failed to create row doc'); - } - - createdRowKeys.current.push(rowKey); - return doc; - } catch (e) { - return Promise.reject(e); - } - }, - [service], - ); - - const loadView = useCallback( - async (viewId: string, isSubDocument?: boolean) => { - if (isSubDocument) { - const data = await service?.getPublishRowDocument(viewId); - - if (!data) { - return Promise.reject(new Error('View has not been published yet')); - } - - return data; - } - - try { - const res = await service?.getPublishInfo(viewId); - - if (!res) { - throw new Error('View has not been published yet'); - } - - const { namespace, publishName } = res; - - const data = service?.getPublishView(namespace, publishName); - - if (!data) { - throw new Error('View has not been published yet'); - } - - return data; - } catch (e) { - return Promise.reject(e); - } - }, - [service], - ); - - const onRendered = useCallback(() => { - setRendered(true); - }, []); - - useEffect(() => { - if (!viewMeta && prevViewMeta.current) { - window.location.reload(); - return; - } - - prevViewMeta.current = viewMeta; - }, [viewMeta]); - - useEffect(() => { - void loadOutline(); - }, [loadOutline]); - - return ( - - {children} - - ); -}; - -export function usePublishContext () { - return useContext(PublishContext); -} diff --git a/frontend/appflowy_web_app/src/application/publish/index.ts b/frontend/appflowy_web_app/src/application/publish/index.ts deleted file mode 100644 index c38e8e8215..0000000000 --- a/frontend/appflowy_web_app/src/application/publish/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './context'; diff --git a/frontend/appflowy_web_app/src/application/services/index.ts b/frontend/appflowy_web_app/src/application/services/index.ts deleted file mode 100644 index 70c63ed3cd..0000000000 --- a/frontend/appflowy_web_app/src/application/services/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { AFService, AFServiceConfig } from '@/application/services/services.type'; -import { AFClientService } from '$client-services'; - -let service: AFService; - -export function getService (config: AFServiceConfig) { - if (service) return service; - - service = new AFClientService(config); - return service; -} diff --git a/frontend/appflowy_web_app/src/application/services/js-services/__tests__/fetch.test.ts b/frontend/appflowy_web_app/src/application/services/js-services/__tests__/fetch.test.ts deleted file mode 100644 index b80434a93d..0000000000 --- a/frontend/appflowy_web_app/src/application/services/js-services/__tests__/fetch.test.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { expect } from '@jest/globals'; -import { fetchPublishView, fetchPublishViewMeta, fetchViewInfo } from '../fetch'; -import { APIService } from '@/application/services/js-services/http'; - -jest.mock('@/application/services/js-services/http', () => { - return { - APIService: { - getPublishView: jest.fn(), - getPublishViewMeta: jest.fn(), - getPublishInfoWithViewId: jest.fn(), - }, - }; -}); - -describe('Collab fetch functions with deduplication', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - describe('fetchPublishView', () => { - it('should fetch publish view without duplicating requests', async () => { - const namespace = 'namespace1'; - const publishName = 'publish1'; - const mockResponse = { data: 'mockData' }; - - (APIService.getPublishView as jest.Mock).mockResolvedValue(mockResponse); - - const result1 = fetchPublishView(namespace, publishName); - const result2 = fetchPublishView(namespace, publishName); - - expect(result1).toBe(result2); - await expect(result1).resolves.toEqual(mockResponse); - expect(APIService.getPublishView).toHaveBeenCalledTimes(1); - }); - - it('should fetch publish view with different params', async () => { - const namespace = 'namespace1'; - const publishName = 'publish1'; - const mockResponse = { data: 'mockData' }; - - (APIService.getPublishView as jest.Mock).mockResolvedValue(mockResponse); - - const result1 = fetchPublishView(namespace, publishName); - const result2 = fetchPublishView(namespace, 'publish2'); - - expect(result1).not.toBe(result2); - await expect(result1).resolves.toEqual(mockResponse); - await expect(result2).resolves.toEqual(mockResponse); - expect(APIService.getPublishView).toHaveBeenCalledTimes(2); - }); - }); - - describe('fetchViewInfo', () => { - it('should fetch view info without duplicating requests', async () => { - const viewId = 'view1'; - const mockResponse = { data: 'mockData' }; - - (APIService.getPublishInfoWithViewId as jest.Mock).mockResolvedValue(mockResponse); - - const result1 = fetchViewInfo(viewId); - const result2 = fetchViewInfo(viewId); - - expect(result1).toBe(result2); - await expect(result1).resolves.toEqual(mockResponse); - expect(APIService.getPublishInfoWithViewId).toHaveBeenCalledTimes(1); - }); - - it('should fetch view info with different params', async () => { - const viewId = 'view1'; - const mockResponse = { data: 'mockData' }; - - (APIService.getPublishInfoWithViewId as jest.Mock).mockResolvedValue(mockResponse); - - const result1 = fetchViewInfo(viewId); - const result2 = fetchViewInfo('view2'); - - expect(result1).not.toBe(result2); - await expect(result1).resolves.toEqual(mockResponse); - await expect(result2).resolves.toEqual(mockResponse); - expect(APIService.getPublishInfoWithViewId).toHaveBeenCalledTimes(2); - }); - }); - - describe('fetchPublishViewMeta', () => { - it('should fetch publish view meta without duplicating requests', async () => { - const namespace = 'namespace1'; - const publishName = 'publish1'; - const mockResponse = { data: 'mockData' }; - - (APIService.getPublishViewMeta as jest.Mock).mockResolvedValue(mockResponse); - - const result1 = fetchPublishViewMeta(namespace, publishName); - const result2 = fetchPublishViewMeta(namespace, publishName); - - expect(result1).toBe(result2); - await expect(result1).resolves.toEqual(mockResponse); - expect(APIService.getPublishViewMeta).toHaveBeenCalledTimes(1); - }); - - it('should fetch publish view meta with different params', async () => { - const namespace = 'namespace1'; - const publishName = 'publish1'; - const mockResponse = { data: 'mockData' }; - - (APIService.getPublishViewMeta as jest.Mock).mockResolvedValue(mockResponse); - - const result1 = fetchPublishViewMeta(namespace, publishName); - const result2 = fetchPublishViewMeta(namespace, 'publish2'); - - expect(result1).not.toBe(result2); - await expect(result1).resolves.toEqual(mockResponse); - await expect(result2).resolves.toEqual(mockResponse); - expect(APIService.getPublishViewMeta).toHaveBeenCalledTimes(2); - }); - }); -}); diff --git a/frontend/appflowy_web_app/src/application/services/js-services/__tests__/index.test.ts b/frontend/appflowy_web_app/src/application/services/js-services/__tests__/index.test.ts deleted file mode 100644 index 9bab9c9352..0000000000 --- a/frontend/appflowy_web_app/src/application/services/js-services/__tests__/index.test.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { withTestingYDoc } from '@/application/slate-yjs/__tests__/withTestingYjsEditor'; -import * as Y from 'yjs'; -import { AFClientService } from '../index'; -import { fetchViewInfo } from '@/application/services/js-services/fetch'; -import { expect, jest } from '@jest/globals'; -import { getPublishView, getPublishViewMeta } from '@/application/services/js-services/cache'; - -jest.mock('@/application/services/js-services/http/http_api', () => { - return { - initAPIService: jest.fn(), - }; -}); -jest.mock('nanoid', () => { - return { - nanoid: jest.fn().mockReturnValue('12345678'), - }; -}); -jest.mock('@/application/services/js-services/fetch', () => { - return { - fetchPublishView: jest.fn(), - fetchPublishViewMeta: jest.fn(), - fetchViewInfo: jest.fn(), - }; -}); - -jest.mock('@/application/services/js-services/cache', () => { - return { - getPublishView: jest.fn(), - getPublishViewMeta: jest.fn(), - getBatchCollabs: jest.fn(), - }; -}); -describe('AFClientService', () => { - let service: AFClientService; - beforeEach(() => { - jest.clearAllMocks(); - service = new AFClientService({ - cloudConfig: { - baseURL: 'http://localhost:3000', - gotrueURL: 'http://localhost:3000', - wsURL: 'ws://localhost:3000', - }, - }); - }); - - it('should get view meta', async () => { - const namespace = 'namespace'; - const publishName = 'publishName'; - const mockResponse = { - view_id: 'view_id', - publish_name: publishName, - metadata: { - view: { - name: 'viewName', - view_id: 'view_id', - }, - child_views: [], - ancestor_views: [], - }, - }; - - // @ts-ignore - (getPublishViewMeta as jest.Mock).mockResolvedValue(mockResponse); - - const result = await service.getPublishViewMeta(namespace, publishName); - - expect(result).toEqual(mockResponse); - }); - - it('should get view', async () => { - const namespace = 'namespace'; - const publishName = 'publishName'; - const rowDoc = new Y.Doc(); - const mockResponse = { - doc: withTestingYDoc('1'), - rowDocMap: rowDoc.getMap(), - }; - - // @ts-ignore - (getPublishView as jest.Mock).mockResolvedValue(mockResponse); - - const result = await service.getPublishView(namespace, publishName); - - expect(result).toEqual(mockResponse.doc); - }); - - it('should get view info', async () => { - const viewId = 'viewId'; - const mockResponse = { - namespace: 'namespace', - publish_name: 'publishName', - }; - - // @ts-ignore - (fetchViewInfo as jest.Mock).mockResolvedValue(mockResponse); - - const result = await service.getPublishInfo(viewId); - - expect(result).toEqual({ - namespace: 'namespace', - publishName: 'publishName', - }); - }); -}); diff --git a/frontend/appflowy_web_app/src/application/services/js-services/cache/__tests__/cache.test.ts b/frontend/appflowy_web_app/src/application/services/js-services/cache/__tests__/cache.test.ts deleted file mode 100644 index 2acb4a8b3b..0000000000 --- a/frontend/appflowy_web_app/src/application/services/js-services/cache/__tests__/cache.test.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { Types } from '@/application/types'; -import { withTestingYDoc } from '@/application/slate-yjs/__tests__/withTestingYjsEditor'; -import { expect } from '@jest/globals'; -import { collabTypeToDBType, getPublishView, getPublishViewMeta } from '@/application/services/js-services/cache'; -import { openCollabDB, db } from '@/application/db'; -import { StrategyType } from '@/application/services/js-services/cache/types'; - -jest.mock('@/application/ydoc/apply', () => ({ - applyYDoc: jest.fn(), -})); - -jest.mock('@/application/db', () => ({ - openCollabDB: jest.fn(), - db: { - view_metas: { - get: jest.fn(), - put: jest.fn(), - }, - }, -})); - -const normalDoc = withTestingYDoc('1'); -const mockFetcher = jest.fn(); - -async function runTestWithStrategy (strategy: StrategyType) { - return getPublishView( - mockFetcher, - { - namespace: 'appflowy', - publishName: 'test', - }, - strategy, - ); -} - -async function runGetPublishViewMetaWithStrategy (strategy: StrategyType) { - return getPublishViewMeta( - mockFetcher, - { - namespace: 'appflowy', - publishName: 'test', - }, - strategy, - ); -} - -describe('Cache functions', () => { - beforeEach(() => { - jest.clearAllMocks(); - mockFetcher.mockClear(); - (openCollabDB as jest.Mock).mockClear(); - }); - - describe('getPublishView', () => { - it('should call fetcher when no cache found', async () => { - (openCollabDB as jest.Mock).mockResolvedValue(normalDoc); - mockFetcher.mockResolvedValue({ data: [1, 2, 3], meta: { metadata: { view: { id: '1' } } } }); - (db.view_metas.get as jest.Mock).mockResolvedValue(undefined); - await runTestWithStrategy(StrategyType.CACHE_FIRST); - expect(mockFetcher).toBeCalledTimes(1); - - await runTestWithStrategy(StrategyType.CACHE_AND_NETWORK); - expect(mockFetcher).toBeCalledTimes(2); - await expect(runTestWithStrategy(StrategyType.CACHE_ONLY)).rejects.toThrow('No cache found'); - }); - it('should call fetcher when cache is invalid or strategy is CACHE_AND_NETWORK', async () => { - (openCollabDB as jest.Mock).mockResolvedValue(normalDoc); - (db.view_metas.get as jest.Mock).mockResolvedValue({ view_id: '1' }); - mockFetcher.mockResolvedValue({ data: [1, 2, 3], meta: { metadata: { view: { id: '1' } } } }); - await runTestWithStrategy(StrategyType.CACHE_ONLY); - expect(openCollabDB).toBeCalledTimes(1); - - await runTestWithStrategy(StrategyType.CACHE_FIRST); - expect(openCollabDB).toBeCalledTimes(2); - expect(mockFetcher).toBeCalledTimes(0); - - await runTestWithStrategy(StrategyType.CACHE_AND_NETWORK); - expect(openCollabDB).toBeCalledTimes(3); - expect(mockFetcher).toBeCalledTimes(1); - }); - }); - - describe('getPublishViewMeta', () => { - it('should call fetcher when no cache found', async () => { - mockFetcher.mockResolvedValue({ metadata: { view: { id: '1' }, child_views: [], ancestor_views: [] } }); - (db.view_metas.get as jest.Mock).mockResolvedValue(undefined); - await runGetPublishViewMetaWithStrategy(StrategyType.CACHE_FIRST); - expect(mockFetcher).toBeCalledTimes(1); - - await runGetPublishViewMetaWithStrategy(StrategyType.CACHE_AND_NETWORK); - expect(mockFetcher).toBeCalledTimes(2); - - await expect(runGetPublishViewMetaWithStrategy(StrategyType.CACHE_ONLY)).rejects.toThrow('No cache found'); - }); - - it('should call fetcher when cache is invalid or strategy is CACHE_AND_NETWORK', async () => { - (openCollabDB as jest.Mock).mockResolvedValue(normalDoc); - (db.view_metas.get as jest.Mock).mockResolvedValue({ view_id: '1' }); - - mockFetcher.mockResolvedValue({ metadata: { view: { id: '1' }, child_views: [], ancestor_views: [] } }); - const meta = await runGetPublishViewMetaWithStrategy(StrategyType.CACHE_ONLY); - expect(openCollabDB).toBeCalledTimes(0); - expect(meta).toBeDefined(); - - await runGetPublishViewMetaWithStrategy(StrategyType.CACHE_FIRST); - expect(openCollabDB).toBeCalledTimes(0); - expect(mockFetcher).toBeCalledTimes(0); - - await runGetPublishViewMetaWithStrategy(StrategyType.CACHE_AND_NETWORK); - expect(openCollabDB).toBeCalledTimes(0); - expect(mockFetcher).toBeCalledTimes(1); - }); - }); -}); - -describe('collabTypeToDBType', () => { - it('should return correct DB type', () => { - expect(collabTypeToDBType(Types.Document)).toBe('document'); - expect(collabTypeToDBType(Types.Folder)).toBe('folder'); - expect(collabTypeToDBType(Types.Database)).toBe('database'); - expect(collabTypeToDBType(Types.WorkspaceDatabase)).toBe('databases'); - expect(collabTypeToDBType(Types.DatabaseRow)).toBe('database_row'); - expect(collabTypeToDBType(Types.UserAwareness)).toBe('user_awareness'); - expect(collabTypeToDBType(Types.Empty)).toBe(''); - }); -}); diff --git a/frontend/appflowy_web_app/src/application/services/js-services/cache/index.ts b/frontend/appflowy_web_app/src/application/services/js-services/cache/index.ts deleted file mode 100644 index ba34ebbfaa..0000000000 --- a/frontend/appflowy_web_app/src/application/services/js-services/cache/index.ts +++ /dev/null @@ -1,421 +0,0 @@ -import { closeCollabDB, db, openCollabDB } from '@/application/db'; -import { Fetcher, StrategyType } from '@/application/services/js-services/cache/types'; -import { - DatabaseId, - PublishViewMetaData, - RowId, - Types, - User, - ViewId, - ViewInfo, - YDoc, - YjsEditorKey, - YSharedRoot, -} from '@/application/types'; -import { applyYDoc } from '@/application/ydoc/apply'; - -export function collabTypeToDBType (type: Types) { - switch (type) { - case Types.Folder: - return 'folder'; - case Types.Document: - return 'document'; - case Types.Database: - return 'database'; - case Types.WorkspaceDatabase: - return 'databases'; - case Types.DatabaseRow: - return 'database_row'; - case Types.UserAwareness: - return 'user_awareness'; - default: - return ''; - } -} - -const collabSharedRootKeyMap = { - [Types.Folder]: YjsEditorKey.folder, - [Types.Document]: YjsEditorKey.document, - [Types.Database]: YjsEditorKey.database, - [Types.WorkspaceDatabase]: YjsEditorKey.workspace_database, - [Types.DatabaseRow]: YjsEditorKey.database_row, - [Types.UserAwareness]: YjsEditorKey.user_awareness, - [Types.Empty]: YjsEditorKey.empty, -}; - -export function hasCollabCache (doc: YDoc) { - const data = doc.getMap(YjsEditorKey.data_section) as YSharedRoot; - - return Object.values(collabSharedRootKeyMap).some((key) => { - return data.has(key); - }); -} - -export async function hasViewMetaCache (name: string) { - const data = await db.view_metas.get(name); - - return !!data; -} - -export async function hasUserCache (userId: string) { - const data = await db.users.get(userId); - - return !!data; -} - -export async function getPublishViewMeta< - T extends { - view: ViewInfo; - child_views: ViewInfo[]; - ancestor_views: ViewInfo[]; - } -> ( - fetcher: Fetcher, - { - namespace, - publishName, - }: { - namespace: string; - publishName: string; - }, - strategy: StrategyType = StrategyType.CACHE_AND_NETWORK, -) { - const name = `${namespace}_${publishName}`; - const exist = await hasViewMetaCache(name); - const meta = await db.view_metas.get(name); - - switch (strategy) { - case StrategyType.CACHE_ONLY: { - if (!exist) { - throw new Error('No cache found'); - } - - return meta; - } - - case StrategyType.CACHE_FIRST: { - if (!exist) { - return revalidatePublishViewMeta(name, fetcher); - } - - return meta; - } - - case StrategyType.CACHE_AND_NETWORK: { - if (!exist) { - return revalidatePublishViewMeta(name, fetcher); - } else { - void revalidatePublishViewMeta(name, fetcher); - } - - return meta; - } - - default: { - return revalidatePublishViewMeta(name, fetcher); - } - } -} - -export async function getUser< - T extends User -> ( - fetcher: Fetcher, - userId?: string, - strategy: StrategyType = StrategyType.CACHE_AND_NETWORK, -) { - const exist = userId && (await hasUserCache(userId)); - const data = await db.users.get(userId); - - switch (strategy) { - case StrategyType.CACHE_ONLY: { - if (!exist) { - throw new Error('No cache found'); - } - - return data; - } - - case StrategyType.CACHE_FIRST: { - if (!exist) { - return revalidateUser(fetcher); - } - - return data; - } - - case StrategyType.CACHE_AND_NETWORK: { - if (!exist) { - return revalidateUser(fetcher); - } else { - void revalidateUser(fetcher); - } - - return data; - } - - default: { - return revalidateUser(fetcher); - } - } -} - -export async function getPublishView< - T extends { - data: Uint8Array; - rows?: Record; - visibleViewIds?: ViewId[]; - relations?: Record; - subDocuments?: Record; - meta: { - view: ViewInfo; - child_views: ViewInfo[]; - ancestor_views: ViewInfo[]; - }; - } -> ( - fetcher: Fetcher, - { - namespace, - publishName, - }: { - namespace: string; - publishName: string; - }, - strategy: StrategyType = StrategyType.CACHE_AND_NETWORK, -) { - const name = `${namespace}_${publishName}`; - - const doc = await openCollabDB(name); - - const exist = (await hasViewMetaCache(name)) && hasCollabCache(doc); - - switch (strategy) { - case StrategyType.CACHE_ONLY: { - if (!exist) { - throw new Error('No cache found'); - } - - break; - } - - case StrategyType.CACHE_FIRST: { - if (!exist) { - await revalidatePublishView(name, fetcher, doc); - } - - break; - } - - case StrategyType.CACHE_AND_NETWORK: { - if (!exist) { - await revalidatePublishView(name, fetcher, doc); - } else { - void revalidatePublishView(name, fetcher, doc); - } - - break; - } - - default: { - await revalidatePublishView(name, fetcher, doc); - break; - } - } - - return { doc }; -} - -export async function getPageDoc; -}> (fetcher: Fetcher, name: string, strategy: StrategyType = StrategyType.CACHE_AND_NETWORK) { - - const doc = await openCollabDB(name); - - const exist = hasCollabCache(doc); - - switch (strategy) { - case StrategyType.CACHE_ONLY: { - if (!exist) { - throw new Error('No cache found'); - } - - break; - } - - case StrategyType.CACHE_FIRST: { - if (!exist) { - await revalidateView(fetcher, doc); - } - - break; - } - - case StrategyType.CACHE_AND_NETWORK: { - if (!exist) { - await revalidateView(fetcher, doc); - } else { - void revalidateView(fetcher, doc); - } - - break; - } - - default: { - await revalidateView(fetcher, doc); - break; - } - } - - return { doc }; -} - -async function updateRows (collab: YDoc, rows: Record) { - const bulkData = []; - - for (const [key, value] of Object.entries(rows)) { - const rowKey = `${collab.guid}_rows_${key}`; - const doc = await createRowDoc(rowKey); - - const dbRow = await db.rows.get(key); - - applyYDoc(doc, new Uint8Array(value)); - - bulkData.push({ - row_id: key, - version: (dbRow?.version || 0) + 1, - row_key: rowKey, - }); - } - - await db.rows.bulkPut(bulkData); -} - -export async function revalidateView< - T extends { - data: Uint8Array; - rows?: Record; - }> (fetcher: Fetcher, collab: YDoc) { - try { - const { data, rows } = await fetcher(); - - if (rows) { - await updateRows(collab, rows); - } - - applyYDoc(collab, data); - } catch (e) { - return Promise.reject(e); - } - -} - -export async function revalidatePublishViewMeta< - T extends { - view: ViewInfo; - child_views: ViewInfo[]; - ancestor_views: ViewInfo[]; - } -> (name: string, fetcher: Fetcher) { - const { view, child_views, ancestor_views } = await fetcher(); - - const dbView = await db.view_metas.get(name); - - await db.view_metas.put( - { - publish_name: name, - ...view, - child_views: child_views, - ancestor_views: ancestor_views, - visible_view_ids: dbView?.visible_view_ids ?? [], - database_relations: dbView?.database_relations ?? {}, - }, - name, - ); - - return db.view_metas.get(name); -} - -export async function revalidatePublishView< - T extends { - data: Uint8Array; - rows?: Record; - visibleViewIds?: ViewId[]; - relations?: Record; - subDocuments?: Record; - meta: PublishViewMetaData; - } -> (name: string, fetcher: Fetcher, collab: YDoc) { - const { data, meta, rows, visibleViewIds = [], relations = {}, subDocuments } = await fetcher(); - - await db.view_metas.put( - { - publish_name: name, - ...meta.view, - child_views: meta.child_views, - ancestor_views: meta.ancestor_views, - visible_view_ids: visibleViewIds, - database_relations: relations, - }, - name, - ); - - if (rows) { - await updateRows(collab, rows); - } - - if (subDocuments) { - for (const [key, value] of Object.entries(subDocuments)) { - const doc = await openCollabDB(key); - - applyYDoc(doc, new Uint8Array(value)); - } - } - - applyYDoc(collab, data); -} - -export async function deleteViewMeta (name: string) { - try { - await db.view_metas.delete(name); - - } catch (e) { - console.error(e); - } -} - -export async function deleteView (name: string) { - console.log('deleteView', name); - await deleteViewMeta(name); - await closeCollabDB(name); - - await closeCollabDB(`${name}_rows`); -} - -export async function revalidateUser< - T extends User> (fetcher: Fetcher) { - const data = await fetcher(); - - await db.users.put(data, data.uuid); - - return data; -} - -const rowDocs = new Map(); - -export async function createRowDoc (rowKey: string) { - if (rowDocs.has(rowKey)) { - return rowDocs.get(rowKey) as YDoc; - } - - const doc = await openCollabDB(rowKey); - - rowDocs.set(rowKey, doc); - - return doc; -} - -export function deleteRowDoc (rowKey: string) { - rowDocs.delete(rowKey); -} diff --git a/frontend/appflowy_web_app/src/application/services/js-services/cache/types.ts b/frontend/appflowy_web_app/src/application/services/js-services/cache/types.ts deleted file mode 100644 index 1c1a949723..0000000000 --- a/frontend/appflowy_web_app/src/application/services/js-services/cache/types.ts +++ /dev/null @@ -1,12 +0,0 @@ -export enum StrategyType { - // Cache only: return the cache if it exists, otherwise throw an error - CACHE_ONLY = 'CACHE_ONLY', - // Cache first: return the cache if it exists, otherwise fetch from the network - CACHE_FIRST = 'CACHE_FIRST', - // Cache and network: return the cache if it exists, otherwise fetch from the network and update the cache - CACHE_AND_NETWORK = 'CACHE_AND_NETWORK', - // Network only: fetch from the network and update the cache - NETWORK_ONLY = 'NETWORK_ONLY', -} - -export type Fetcher = () => Promise; diff --git a/frontend/appflowy_web_app/src/application/services/js-services/fetch.ts b/frontend/appflowy_web_app/src/application/services/js-services/fetch.ts deleted file mode 100644 index 783ba76475..0000000000 --- a/frontend/appflowy_web_app/src/application/services/js-services/fetch.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { APIService } from '@/application/services/js-services/http'; - -const pendingRequests = new Map(); - -function generateRequestKey (url: string, params: T) { - if (!params) return url; - - try { - return `${url}_${JSON.stringify(params)}`; - } catch (_e) { - return `${url}_${params}`; - } -} - -// Deduplication fetch requests -// When multiple requests are made to the same URL with the same params, only one request is made -// and the result is shared with all the requests -function fetchWithDeduplication (url: string, params: Req, fetchFunction: () => Promise): Promise { - const requestKey = generateRequestKey(url, params); - - if (pendingRequests.has(requestKey)) { - return pendingRequests.get(requestKey); - } - - const fetchPromise = fetchFunction().finally(() => { - pendingRequests.delete(requestKey); - }); - - pendingRequests.set(requestKey, fetchPromise); - return fetchPromise; -} - -export function fetchPublishView (namespace: string, publishName: string) { - const fetchFunction = () => APIService.getPublishView(namespace, publishName); - - return fetchWithDeduplication(`fetchPublishView_${namespace}`, { publishName }, fetchFunction); -} - -export function fetchPageCollab (workspaceId: string, viewId: string) { - const fetchFunction = () => APIService.getPageCollab(workspaceId, viewId); - - return fetchWithDeduplication(`fetchPageCollab_${workspaceId}`, { viewId }, fetchFunction); -} - -export function fetchViewInfo (viewId: string) { - const fetchFunction = () => APIService.getPublishInfoWithViewId(viewId); - - return fetchWithDeduplication(`fetchViewInfo`, { viewId }, fetchFunction); -} - -export function fetchPublishViewMeta (namespace: string, publishName: string) { - const fetchFunction = () => APIService.getPublishViewMeta(namespace, publishName); - - return fetchWithDeduplication(`fetchPublishViewMeta_${namespace}`, { publishName }, fetchFunction); -} diff --git a/frontend/appflowy_web_app/src/application/services/js-services/http/gotrue.ts b/frontend/appflowy_web_app/src/application/services/js-services/http/gotrue.ts deleted file mode 100644 index 32ff40bbfa..0000000000 --- a/frontend/appflowy_web_app/src/application/services/js-services/http/gotrue.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { refreshToken as refreshSessionToken } from '@/application/session/token'; -import axios, { AxiosInstance } from 'axios'; - -let axiosInstance: AxiosInstance | null = null; - -export function initGrantService(baseURL: string) { - if (axiosInstance) { - return; - } - - axiosInstance = axios.create({ - baseURL, - }); - - axiosInstance.interceptors.request.use((config) => { - Object.assign(config.headers, { - 'Content-Type': 'application/json', - }); - - return config; - }); -} - -export async function refreshToken(refresh_token: string) { - const response = await axiosInstance?.post<{ - access_token: string; - expires_at: number; - refresh_token: string; - }>('/token?grant_type=refresh_token', { - refresh_token, - }); - - const newToken = response?.data; - - if (newToken) { - refreshSessionToken(JSON.stringify(newToken)); - } else { - return Promise.reject('Failed to refresh token'); - } - - return newToken; -} - -export async function signInWithMagicLink(email: string, authUrl: string) { - const res = await axiosInstance?.post( - '/magiclink', - { - code_challenge: '', - code_challenge_method: '', - data: {}, - email, - }, - { - headers: { - Redirect_to: authUrl, - }, - }, - ); - - return res?.data; -} - -export async function settings() { - const res = await axiosInstance?.get('/settings'); - - return res?.data; -} - -export function signInGoogle(authUrl: string) { - const provider = 'google'; - const redirectTo = encodeURIComponent(authUrl); - const accessType = 'offline'; - const prompt = 'consent'; - const baseURL = axiosInstance?.defaults.baseURL; - const url = `${baseURL}/authorize?provider=${provider}&redirect_to=${redirectTo}&access_type=${accessType}&prompt=${prompt}`; - - window.open(url, '_current'); -} - -export function signInApple(authUrl: string) { - const provider = 'apple'; - const redirectTo = encodeURIComponent(authUrl); - const baseURL = axiosInstance?.defaults.baseURL; - const url = `${baseURL}/authorize?provider=${provider}&redirect_to=${redirectTo}`; - - window.open(url, '_current'); -} - -export function signInGithub(authUrl: string) { - const provider = 'github'; - const redirectTo = encodeURIComponent(authUrl); - const baseURL = axiosInstance?.defaults.baseURL; - const url = `${baseURL}/authorize?provider=${provider}&redirect_to=${redirectTo}`; - - window.open(url, '_current'); -} - -export function signInDiscord(authUrl: string) { - const provider = 'discord'; - const redirectTo = encodeURIComponent(authUrl); - const baseURL = axiosInstance?.defaults.baseURL; - const url = `${baseURL}/authorize?provider=${provider}&redirect_to=${redirectTo}`; - - window.open(url, '_current'); -} diff --git a/frontend/appflowy_web_app/src/application/services/js-services/http/http_api.ts b/frontend/appflowy_web_app/src/application/services/js-services/http/http_api.ts deleted file mode 100644 index ebf6506fc0..0000000000 --- a/frontend/appflowy_web_app/src/application/services/js-services/http/http_api.ts +++ /dev/null @@ -1,1642 +0,0 @@ -import { - DatabaseId, - FolderView, - RowId, - User, - View, - ViewId, - ViewLayout, - Workspace, - Invitation, - Types, - AFWebUser, - GetRequestAccessInfoResponse, - Subscriptions, - SubscriptionPlan, - SubscriptionInterval, - RequestAccessInfoStatus, - ViewInfo, - UpdatePagePayload, - CreatePagePayload, - CreateSpacePayload, - UpdateSpacePayload, - Role, - WorkspaceMember, QuickNote, QuickNoteEditorData, -} from '@/application/types'; -import { GlobalComment, Reaction } from '@/application/comment.type'; -import { initGrantService, refreshToken } from '@/application/services/js-services/http/gotrue'; -import { blobToBytes } from '@/application/services/js-services/http/utils'; -import { AFCloudConfig } from '@/application/services/services.type'; -import { getTokenParsed, invalidToken } from '@/application/session/token'; -import { - Template, - TemplateCategory, - TemplateCategoryFormValues, - TemplateCreator, TemplateCreatorFormValues, TemplateSummary, - UploadTemplatePayload, -} from '@/application/template.type'; -import axios, { AxiosInstance } from 'axios'; -import dayjs from 'dayjs'; -import { omit } from 'lodash-es'; -import { nanoid } from 'nanoid'; -import { notify } from '@/components/_shared/notify'; - -export * from './gotrue'; - -let axiosInstance: AxiosInstance | null = null; - -export function initAPIService(config: AFCloudConfig) { - if (axiosInstance) { - return; - } - - axiosInstance = axios.create({ - baseURL: config.baseURL, - headers: { - 'Content-Type': 'application/json', - }, - }); - - initGrantService(config.gotrueURL); - - axiosInstance.interceptors.request.use( - async (config) => { - const token = getTokenParsed(); - - if (!token) { - return config; - } - - const isExpired = dayjs().isAfter(dayjs.unix(token.expires_at)); - - let access_token = token.access_token; - const refresh_token = token.refresh_token; - - if (isExpired) { - try { - const newToken = await refreshToken(refresh_token); - - access_token = newToken?.access_token || ''; - } catch (e) { - invalidToken(); - return config; - } - } - - if (access_token) { - Object.assign(config.headers, { - Authorization: `Bearer ${access_token}`, - }); - } - - return config; - }, - (error) => { - return Promise.reject(error); - }, - ); - - axiosInstance.interceptors.response.use(async (response) => { - const status = response.status; - - if (status === 401) { - const token = getTokenParsed(); - - if (!token) { - invalidToken(); - return response; - } - - const refresh_token = token.refresh_token; - - try { - await refreshToken(refresh_token); - } catch (e) { - invalidToken(); - } - } - - return response; - }); -} - -export async function signInWithUrl(url: string) { - const hash = new URL(url).hash; - - if (!hash) { - return Promise.reject('No hash found'); - } - - const params = new URLSearchParams(hash.slice(1)); - const accessToken = params.get('access_token'); - const refresh_token = params.get('refresh_token'); - - if (!accessToken || !refresh_token) { - return Promise.reject({ - code: -1, - message: 'No access token or refresh token found', - }); - } - - try { - await verifyToken(accessToken); - } catch (e) { - return Promise.reject({ - code: -1, - message: 'Verify token failed', - }); - } - - try { - await refreshToken(refresh_token); - } catch (e) { - return Promise.reject({ - code: -1, - message: 'Refresh token failed', - }); - } -} - -export async function verifyToken(accessToken: string) { - const url = `/api/user/verify/${accessToken}`; - const response = await axiosInstance?.get<{ - code: number; - data?: { - is_new: boolean; - }; - message: string; - }>(url); - - const data = response?.data; - - if (data?.code === 0 && data.data) { - return data.data; - } - - return Promise.reject(data); -} - -export async function getCurrentUser(): Promise { - const url = '/api/user/profile'; - const response = await axiosInstance?.get<{ - code: number; - data?: { - uid: number; - uuid: string; - email: string; - name: string; - metadata: { - icon_url: string; - }; - encryption_sign: null; - latest_workspace_id: string; - updated_at: number; - }; - message: string; - }>(url); - - const data = response?.data; - - if (data?.code === 0 && data.data) { - const { uid, uuid, email, name, metadata } = data.data; - - return { - uid: String(uid), - uuid, - email, - name, - avatar: metadata.icon_url, - latestWorkspaceId: data.data.latest_workspace_id, - }; - } - - return Promise.reject(data); -} - -interface AFWorkspace { - workspace_id: string, - owner_uid: number, - owner_name: string, - workspace_name: string, - icon: string, - created_at: string, - member_count: number, - database_storage_id: string, -} - -function afWorkspace2Workspace(workspace: AFWorkspace): Workspace { - return { - id: workspace.workspace_id, - owner: { - uid: workspace.owner_uid, - name: workspace.owner_name, - }, - name: workspace.workspace_name, - icon: workspace.icon, - memberCount: workspace.member_count, - databaseStorageId: workspace.database_storage_id, - createdAt: workspace.created_at, - }; -} - -export async function openWorkspace(workspaceId: string) { - const url = `/api/workspace/${workspaceId}/open`; - const response = await axiosInstance?.put<{ - code: number; - message: string; - }>(url); - - if (response?.data.code === 0) { - return; - } - - return Promise.reject(response?.data); -} - -export async function getUserWorkspaceInfo(): Promise<{ - user_id: string; - selected_workspace: Workspace; - workspaces: Workspace[]; -}> { - const url = '/api/user/workspace'; - const response = await axiosInstance?.get<{ - code: number, - message: string, - data: { - user_profile: { - uuid: string; - }, - visiting_workspace: AFWorkspace, - workspaces: AFWorkspace[] - } - - }>(url); - - const data = response?.data; - - if (data?.code === 0) { - const { visiting_workspace, workspaces, user_profile } = data.data; - - return { - user_id: user_profile.uuid, - selected_workspace: afWorkspace2Workspace(visiting_workspace), - workspaces: workspaces.map(afWorkspace2Workspace), - }; - } - - return Promise.reject(data); -} - -export async function getPublishViewMeta(namespace: string, publishName: string) { - const url = `/api/workspace/v1/published/${namespace}/${publishName}`; - const response = await axiosInstance?.get<{ - code: number; - data: { - view: ViewInfo; - child_views: ViewInfo[]; - ancestor_views: ViewInfo[]; - }; - message: string; - }>(url); - - if (response?.data.code !== 0) { - return Promise.reject(response?.data); - } - - return response?.data.data; -} - -export async function getPublishViewBlob(namespace: string, publishName: string) { - const url = `/api/workspace/published/${namespace}/${publishName}/blob`; - const response = await axiosInstance?.get(url, { - responseType: 'blob', - }); - - return blobToBytes(response?.data); -} - -export async function updateCollab(workspaceId: string, objectId: string, collabType: Types, docState: Uint8Array, context: { - version_vector: number; -}) { - const url = `/api/workspace/v1/${workspaceId}/collab/${objectId}/web-update`; - let deviceId = localStorage.getItem('x-device-id'); - - if (!deviceId) { - deviceId = nanoid(8); - localStorage.setItem('x-device-id', deviceId); - } - - const response = await axiosInstance?.post<{ - code: number; - message: string; - }>(url, { - doc_state: Array.from(docState), - collab_type: collabType, - }, { - headers: { - 'client-version': 'web', - 'device-id': deviceId, - }, - }); - - if (response?.data.code !== 0) { - return Promise.reject(response?.data); - } - - return context; -} - -export async function getCollab(workspaceId: string, objectId: string, collabType: Types) { - const url = `/api/workspace/v1/${workspaceId}/collab/${objectId}`; - const response = await axiosInstance?.get<{ - code: number; - data: { - doc_state: number[]; - object_id: string; - }; - message: string; - }>(url, { - params: { - collab_type: collabType, - }, - }); - - if (response?.data.code !== 0) { - return Promise.reject(response?.data); - } - - const docState = response?.data.data.doc_state; - - return { - data: new Uint8Array(docState), - }; -} - -export async function getPageCollab(workspaceId: string, viewId: string) { - const url = `/api/workspace/${workspaceId}/page-view/${viewId}`; - const response = await axiosInstance?.get<{ - code: number; - data: { - view: View; - data: { - encoded_collab: number[]; - row_data: Record; - owner?: User; - last_editor?: User; - } - }; - message: string; - }>(url); - - if (!response) { - return Promise.reject('No response'); - } - - if (response.data.code !== 0) { - return Promise.reject(response?.data); - } - - const { encoded_collab, row_data, owner, last_editor } = response.data.data.data; - - return { - data: new Uint8Array(encoded_collab), - rows: row_data, - owner, - lastEditor: last_editor, - }; -} - -export async function getPublishView(publishNamespace: string, publishName: string) { - const meta = await getPublishViewMeta(publishNamespace, publishName); - const blob = await getPublishViewBlob(publishNamespace, publishName); - - if (meta.view.layout === ViewLayout.Document) { - return { - data: blob, - meta, - }; - } - - try { - const decoder = new TextDecoder('utf-8'); - - const jsonStr = decoder.decode(blob); - - const res = JSON.parse(jsonStr) as { - database_collab: Uint8Array; - database_row_collabs: Record; - database_row_document_collabs: Record; - visible_database_view_ids: ViewId[]; - database_relations: Record; - }; - - return { - data: new Uint8Array(res.database_collab), - rows: res.database_row_collabs, - visibleViewIds: res.visible_database_view_ids, - relations: res.database_relations, - subDocuments: res.database_row_document_collabs, - meta, - }; - } catch (e) { - return Promise.reject(e); - } -} - -export async function getPublishInfoWithViewId(viewId: string) { - const url = `/api/workspace/published-info/${viewId}`; - const response = await axiosInstance?.get<{ - code: number; - data?: { - namespace: string; - publish_name: string; - }; - message: string; - }>(url); - - const data = response?.data; - - if (data?.code === 0 && data.data) { - return data.data; - } - - return Promise.reject(data); -} - -export async function getAppFavorites(workspaceId: string) { - const url = `/api/workspace/${workspaceId}/favorite`; - const response = await axiosInstance?.get<{ - code: number; - data?: { - views: View[] - }; - message: string; - }>(url); - - const data = response?.data; - - if (data?.code === 0 && data.data) { - return data.data.views; - } - - return Promise.reject(data); -} - -export async function getAppTrash(workspaceId: string) { - const url = `/api/workspace/${workspaceId}/trash`; - const response = await axiosInstance?.get<{ - code: number; - data?: { - views: View[] - }; - message: string; - }>(url); - - const data = response?.data; - - if (data?.code === 0 && data.data) { - return data.data.views; - } - - return Promise.reject(data); -} - -export async function getAppRecent(workspaceId: string) { - const url = `/api/workspace/${workspaceId}/recent`; - const response = await axiosInstance?.get<{ - code: number; - data?: { - views: View[] - }; - message: string; - }>(url); - - const data = response?.data; - - if (data?.code === 0 && data.data) { - return data.data.views; - } - - return Promise.reject(data); -} - -export async function getAppOutline(workspaceId: string) { - const url = `/api/workspace/${workspaceId}/folder?depth=10`; - - const response = await axiosInstance?.get<{ - code: number; - data?: View; - message: string; - }>(url); - - const data = response?.data; - - if (data?.code === 0 && data.data) { - return data.data.children; - } - - return Promise.reject(data); -} - -export async function getView(workspaceId: string, viewId: string, depth: number = 1) { - const url = `/api/workspace/${workspaceId}/folder?depth=${depth}&root_view_id=${viewId}`; - const response = await axiosInstance?.get<{ - code: number; - data?: View; - message: string; - }>(url); - - const data = response?.data; - - if (data?.code === 0 && data.data) { - return data.data; - } - - return Promise.reject(data); -} - -export async function getPublishOutline(publishNamespace: string) { - const url = `/api/workspace/published-outline/${publishNamespace}`; - const response = await axiosInstance?.get<{ - code: number; - data?: View; - message: string; - }>(url); - - const data = response?.data; - - if (data?.code === 0 && data.data) { - return data.data.children; - } - - return Promise.reject(data); -} - -export async function getPublishViewComments(viewId: string): Promise { - const url = `/api/workspace/published-info/${viewId}/comment`; - const response = await axiosInstance?.get<{ - code: number; - data?: { - comments: { - comment_id: string; - user: { - uuid: string; - name: string; - avatar_url: string | null; - }; - content: string; - created_at: string; - last_updated_at: string; - reply_comment_id: string | null; - is_deleted: boolean; - can_be_deleted: boolean; - }[]; - }; - message: string; - }>(url); - - const data = response?.data; - - if (data?.code === 0 && data.data) { - const { comments } = data.data; - - return comments.map((comment) => { - return { - commentId: comment.comment_id, - user: { - uuid: comment.user?.uuid || '', - name: comment.user?.name || '', - avatarUrl: comment.user?.avatar_url || null, - }, - content: comment.content, - createdAt: comment.created_at, - lastUpdatedAt: comment.last_updated_at, - replyCommentId: comment.reply_comment_id, - isDeleted: comment.is_deleted, - canDeleted: comment.can_be_deleted, - }; - }); - } - - return Promise.reject(data); -} - -export async function getReactions(viewId: string, commentId?: string): Promise> { - let url = `/api/workspace/published-info/${viewId}/reaction`; - - if (commentId) { - url += `?comment_id=${commentId}`; - } - - const response = await axiosInstance?.get<{ - code: number; - data?: { - reactions: { - reaction_type: string; - react_users: { - uuid: string; - name: string; - avatar_url: string | null; - }[]; - comment_id: string; - }[]; - }; - message: string; - }>(url); - - const data = response?.data; - - if (data?.code === 0 && data.data) { - const { reactions } = data.data; - const reactionsMap: Record = {}; - - for (const reaction of reactions) { - if (!reactionsMap[reaction.comment_id]) { - reactionsMap[reaction.comment_id] = []; - } - - reactionsMap[reaction.comment_id].push({ - reactionType: reaction.reaction_type, - commentId: reaction.comment_id, - reactUsers: reaction.react_users.map((user) => ({ - uuid: user.uuid, - name: user.name, - avatarUrl: user.avatar_url, - })), - }); - } - - return reactionsMap; - } - - return Promise.reject(data); -} - -export async function createGlobalCommentOnPublishView(viewId: string, content: string, replyCommentId?: string) { - const url = `/api/workspace/published-info/${viewId}/comment`; - const response = await axiosInstance?.post<{ code: number; message: string }>(url, { - content, - reply_comment_id: replyCommentId, - }); - - if (response?.data.code === 0) { - return; - } - - return Promise.reject(response?.data.message); -} - -export async function deleteGlobalCommentOnPublishView(viewId: string, commentId: string) { - const url = `/api/workspace/published-info/${viewId}/comment`; - const response = await axiosInstance?.delete<{ code: number; message: string }>(url, { - data: { - comment_id: commentId, - }, - }); - - if (response?.data.code === 0) { - return; - } - - return Promise.reject(response?.data.message); -} - -export async function addReaction(viewId: string, commentId: string, reactionType: string) { - const url = `/api/workspace/published-info/${viewId}/reaction`; - const response = await axiosInstance?.post<{ code: number; message: string }>(url, { - comment_id: commentId, - reaction_type: reactionType, - }); - - if (response?.data.code === 0) { - return; - } - - return Promise.reject(response?.data.message); -} - -export async function removeReaction(viewId: string, commentId: string, reactionType: string) { - const url = `/api/workspace/published-info/${viewId}/reaction`; - const response = await axiosInstance?.delete<{ code: number; message: string }>(url, { - data: { - comment_id: commentId, - reaction_type: reactionType, - }, - }); - - if (response?.data.code === 0) { - return; - } - - return Promise.reject(response?.data.message); -} - -export async function getWorkspaces(): Promise { - const query = new URLSearchParams({ - include_member_count: 'true', - }); - - const url = `/api/workspace?${query.toString()}`; - const response = await axiosInstance?.get<{ - code: number; - data?: AFWorkspace[]; - message: string; - }>(url); - - const data = response?.data; - - if (data?.code === 0 && data.data) { - return data.data.map(afWorkspace2Workspace); - } - - return Promise.reject(data); -} - -export interface WorkspaceFolder { - view_id: string; - icon: string | null; - name: string; - is_space: boolean; - is_private: boolean; - extra: { - is_space: boolean; - space_created_at: number; - space_icon: string; - space_icon_color: string; - space_permission: number; - }; - - children: WorkspaceFolder[]; -} - -function iterateFolder(folder: WorkspaceFolder): FolderView { - return { - id: folder.view_id, - name: folder.name, - icon: folder.icon, - isSpace: folder.is_space, - extra: folder.extra ? JSON.stringify(folder.extra) : null, - isPrivate: folder.is_private, - children: folder.children.map((child: WorkspaceFolder) => { - return iterateFolder(child); - }), - }; -} - -export async function getWorkspaceFolder(workspaceId: string): Promise { - const url = `/api/workspace/${workspaceId}/folder`; - const response = await axiosInstance?.get<{ - code: number; - data?: WorkspaceFolder; - message: string; - }>(url); - - const data = response?.data; - - if (data?.code === 0 && data.data) { - return iterateFolder(data.data); - } - - return Promise.reject(data); -} - -export interface DuplicatePublishViewPayload { - published_collab_type: 0 | 1 | 2 | 3 | 4 | 5 | 6; - published_view_id: string; - dest_view_id: string; -} - -export async function duplicatePublishView(workspaceId: string, payload: DuplicatePublishViewPayload) { - const url = `/api/workspace/${workspaceId}/published-duplicate`; - - const res = await axiosInstance?.post<{ - code: number; - message: string; - }>(url, payload); - - if (res?.data.code === 0) { - return; - } - - return Promise.reject(res?.data.message); -} - -export async function createTemplate(template: UploadTemplatePayload) { - const url = '/api/template-center/template'; - const response = await axiosInstance?.post<{ - code: number; - message: string; - }>(url, template); - - if (response?.data.code === 0) { - return; - } - - return Promise.reject(response?.data.message); -} - -export async function updateTemplate(viewId: string, template: UploadTemplatePayload) { - const url = `/api/template-center/template/${viewId}`; - const response = await axiosInstance?.put<{ - code: number; - message: string; - }>(url, template); - - if (response?.data.code === 0) { - return; - } - - return Promise.reject(response?.data.message); -} - -export async function getTemplates({ - categoryId, - nameContains, -}: { - categoryId?: string; - nameContains?: string; -}) { - const url = `/api/template-center/template`; - - const response = await axiosInstance?.get<{ - code: number; - data?: { - templates: TemplateSummary[]; - }; - message: string; - }>(url, { - params: { - category_id: categoryId, - name_contains: nameContains, - }, - }); - - const data = response?.data; - - if (data?.code === 0 && data.data) { - return data.data.templates; - } - - return Promise.reject(data); -} - -export async function getTemplateById(viewId: string) { - const url = `/api/template-center/template/${viewId}`; - const response = await axiosInstance?.get<{ - code: number; - data?: Template; - message: string; - }>(url); - - const data = response?.data; - - if (data?.code === 0 && data.data) { - return data.data; - } - - return Promise.reject(data); -} - -export async function deleteTemplate(viewId: string) { - const url = `/api/template-center/template/${viewId}`; - const response = await axiosInstance?.delete<{ - code: number; - message: string; - }>(url); - - if (response?.data.code === 0) { - return; - } - - return Promise.reject(response?.data.message); -} - -export async function getTemplateCategories() { - const url = '/api/template-center/category'; - const response = await axiosInstance?.get<{ - code: number; - data?: { - categories: TemplateCategory[] - - }; - message: string; - }>(url); - - const data = response?.data; - - if (data?.code === 0 && data.data) { - return data.data.categories; - } - - return Promise.reject(data); -} - -export async function addTemplateCategory(category: TemplateCategoryFormValues) { - const url = '/api/template-center/category'; - const response = await axiosInstance?.post<{ - code: number; - message: string; - }>(url, category); - - if (response?.data.code === 0) { - return; - } - - return Promise.reject(response?.data.message); -} - -export async function updateTemplateCategory(id: string, category: TemplateCategoryFormValues) { - const url = `/api/template-center/category/${id}`; - const response = await axiosInstance?.put<{ - code: number; - message: string; - }>(url, category); - - if (response?.data.code === 0) { - return; - } - - return Promise.reject(response?.data.message); -} - -export async function deleteTemplateCategory(categoryId: string) { - const url = `/api/template-center/category/${categoryId}`; - const response = await axiosInstance?.delete<{ - code: number; - message: string; - }>(url); - - if (response?.data.code === 0) { - return; - } - - return Promise.reject(response?.data.message); -} - -export async function getTemplateCreators() { - const url = '/api/template-center/creator'; - const response = await axiosInstance?.get<{ - code: number; - data?: { - creators: TemplateCreator[]; - }; - message: string; - }>(url); - - const data = response?.data; - - if (data?.code === 0 && data.data) { - return data.data.creators; - } - - return Promise.reject(data); -} - -export async function createTemplateCreator(creator: TemplateCreatorFormValues) { - const url = '/api/template-center/creator'; - const response = await axiosInstance?.post<{ - code: number; - message: string; - }>(url, creator); - - if (response?.data.code === 0) { - return; - } - - return Promise.reject(response?.data.message); -} - -export async function updateTemplateCreator(creatorId: string, creator: TemplateCreatorFormValues) { - const url = `/api/template-center/creator/${creatorId}`; - const response = await axiosInstance?.put<{ - code: number; - message: string; - }>(url, creator); - - if (response?.data.code === 0) { - return; - } - - return Promise.reject(response?.data.message); -} - -export async function deleteTemplateCreator(creatorId: string) { - const url = `/api/template-center/creator/${creatorId}`; - const response = await axiosInstance?.delete<{ - code: number; - message: string; - }>(url); - - if (response?.data.code === 0) { - return; - } - - return Promise.reject(response?.data.message); -} - -export async function uploadTemplateAvatar(file: File) { - const url = '/api/template-center/avatar'; - const formData = new FormData(); - - formData.append('avatar', file); - - const response = await axiosInstance?.request<{ - code: number; - data?: { - file_id: string; - }; - message: string; - }>({ - method: 'PUT', - url, - data: formData, - headers: { - 'Content-Type': 'multipart/form-data', - }, - }); - - const data = response?.data; - - if (data?.code === 0 && data.data) { - return axiosInstance?.defaults.baseURL + '/api/template-center/avatar/' + data.data.file_id; - } - - return Promise.reject(data); -} - -export async function getInvitation(invitationId: string) { - const url = `/api/workspace/invite/${invitationId}`; - const response = await axiosInstance?.get<{ - code: number; - data?: Invitation; - message: string; - }>(url); - - const data = response?.data; - - if (data?.code === 0 && data.data) { - return data.data; - } - - return Promise.reject(data); -} - -export async function acceptInvitation(invitationId: string) { - const url = `/api/workspace/accept-invite/${invitationId}`; - const response = await axiosInstance?.post<{ - code: number; - message: string; - }>(url); - - if (response?.data.code === 0) { - return; - } - - return Promise.reject(response?.data.message); -} - -export async function getRequestAccessInfo(requestId: string): Promise { - const url = `/api/access-request/${requestId}`; - const response = await axiosInstance?.get<{ - code: number; - data?: { - request_id: string; - workspace: AFWorkspace; - requester: AFWebUser & { - email: string; - }; - view: View; - status: RequestAccessInfoStatus; - }; - message: string; - }>(url); - - const data = response?.data; - - if (data?.code === 0 && data.data) { - const workspace = data.data.workspace; - - return { - ...data.data, - workspace: afWorkspace2Workspace(workspace), - }; - } - - return Promise.reject(data); -} - -export async function approveRequestAccess(requestId: string) { - const url = `/api/access-request/${requestId}/approve`; - const response = await axiosInstance?.post<{ - code: number; - message: string; - }>(url, { - is_approved: true, - }); - - if (response?.data.code === 0) { - return; - } - - return Promise.reject(response?.data); -} - -export async function sendRequestAccess(workspaceId: string, viewId: string) { - const url = `/api/access-request`; - const response = await axiosInstance?.post<{ - code: number; - message: string; - }>(url, { - workspace_id: workspaceId, - view_id: viewId, - }); - - if (response?.data.code === 0) { - return; - } - - return Promise.reject(response?.data); -} - -export async function getSubscriptionLink(workspaceId: string, plan: SubscriptionPlan, interval: SubscriptionInterval) { - const url = `/billing/api/v1/subscription-link`; - const response = await axiosInstance?.get<{ - code: number; - data?: string; - message: string; - }>(url, { - params: { - workspace_subscription_plan: plan, - recurring_interval: interval, - workspace_id: workspaceId, - success_url: window.location.href, - }, - }); - - const data = response?.data; - - if (data?.code === 0 && data.data) { - return data.data; - } - - return Promise.reject(data); -} - -export async function getSubscriptions() { - const url = `/billing/api/v1/subscriptions`; - const response = await axiosInstance?.get<{ - code: number; - data: Subscriptions; - message: string; - }>(url); - - if (response?.data.code === 0) { - return response?.data.data; - } - - return Promise.reject(response?.data); - -} - -export async function getWorkspaceSubscriptions(workspaceId: string) { - try { - const plans = await getActiveSubscription(workspaceId); - const subscriptions = await getSubscriptions(); - - return subscriptions?.filter((subscription) => plans?.includes(subscription.plan)); - - } catch (e) { - return Promise.reject(e); - } -} - -export async function getActiveSubscription(workspaceId: string) { - const url = `/billing/api/v1/active-subscription/${workspaceId}`; - - const response = await axiosInstance?.get<{ - code: number; - data: SubscriptionPlan[]; - message: string; - }>(url); - - if (response?.data.code === 0) { - return response?.data.data; - } - - return Promise.reject(response?.data); -} - -export async function createImportTask(file: File) { - const url = `/api/import/create`; - const fileName = file.name.split('.').slice(0, -1).join('.') || crypto.randomUUID(); - - const res = await axiosInstance?.post<{ - code: number; - data: { - task_id: string; - presigned_url: string; - }; - message: string; - }>(url, { - workspace_name: fileName, - content_length: file.size, - }); - - if (res?.data.code === 0) { - return { - taskId: res?.data.data.task_id, - presignedUrl: res?.data.data.presigned_url, - }; - } - - return Promise.reject(res?.data); -} - -export async function uploadImportFile(presignedUrl: string, file: File, onProgress: (progress: number) => void) { - const response = await axios.put(presignedUrl, file, { - onUploadProgress: (progressEvent) => { - const { progress = 0 } = progressEvent; - - console.log(`Upload progress: ${progress * 100}%`); - onProgress(progress); - }, - headers: { - 'Content-Type': 'application/zip', - }, - }); - - if (response.status === 200) { - return; - } - - return Promise.reject({ - code: -1, - message: `Upload file failed. ${response.statusText}`, - }); -} - -export async function addAppPage(workspaceId: string, parentViewId: string, { - layout, - name, -}: CreatePagePayload) { - const url = `/api/workspace/${workspaceId}/page-view`; - const response = await axiosInstance?.post<{ - code: number; - data: { - view_id: string; - }; - message: string; - }>(url, { - parent_view_id: parentViewId, - layout, - name, - }); - - if (response?.data.code === 0) { - return response?.data.data.view_id; - } - - return Promise.reject(response?.data); -} - -export async function updatePage(workspaceId: string, viewId: string, data: UpdatePagePayload) { - const url = `/api/workspace/${workspaceId}/page-view/${viewId}`; - - const res = await axiosInstance?.patch<{ - code: number; - message: string; - }>(url, data); - - if (res?.data.code === 0) { - return; - } - - return Promise.reject(res?.data); -} - -export async function deleteTrash(workspaceId: string, viewId?: string) { - if (viewId) { - const url = `/api/workspace/${workspaceId}/trash/${viewId}`; - const response = await axiosInstance?.delete<{ - code: number; - message: string; - }>(url); - - if (response?.data.code === 0) { - return; - } - - return Promise.reject(response?.data); - } else { - const url = `/api/workspace/${workspaceId}/delete-all-pages-from-trash`; - const response = await axiosInstance?.post<{ - code: number; - message: string; - }>(url); - - if (response?.data.code === 0) { - return; - } - - return Promise.reject(response?.data); - } - -} - -export async function moveToTrash(workspaceId: string, viewId: string) { - const url = `/api/workspace/${workspaceId}/page-view/${viewId}/move-to-trash`; - const response = await axiosInstance?.post<{ - code: number; - message: string; - }>(url); - - if (response?.data.code === 0) { - return; - } - - return Promise.reject(response?.data); -} - -export async function movePageTo(workspaceId: string, viewId: string, parentViewId: string, prevViewId?: string) { - const url = `/api/workspace/${workspaceId}/page-view/${viewId}/move`; - const response = await axiosInstance?.post<{ - code: number; - message: string; - }>(url, { - new_parent_view_id: parentViewId, - prev_view_id: prevViewId, - }); - - if (response?.data.code === 0) { - return; - } - - return Promise.reject(response?.data); -} - -export async function restorePage(workspaceId: string, viewId?: string) { - const url = viewId ? `/api/workspace/${workspaceId}/page-view/${viewId}/restore-from-trash` : `/api/workspace/${workspaceId}/restore-all-pages-from-trash`; - const response = await axiosInstance?.post<{ - code: number; - message: string; - }>(url); - - if (response?.data.code === 0) { - return; - } - - return Promise.reject(response?.data); -} - -export async function createSpace(workspaceId: string, payload: CreateSpacePayload) { - const url = `/api/workspace/${workspaceId}/space`; - const response = await axiosInstance?.post<{ - code: number; - data: { - view_id: string; - }; - message: string; - }>(url, payload); - - if (response?.data.code === 0) { - return response?.data.data.view_id; - } - - return Promise.reject(response?.data); -} - -export async function updateSpace(workspaceId: string, payload: UpdateSpacePayload) { - const url = `/api/workspace/${workspaceId}/space/${payload.view_id}`; - const data = omit(payload, ['view_id']); - const response = await axiosInstance?.patch<{ - code: number; - message: string; - }>(url, data); - - if (response?.data.code === 0) { - return; - } - - return Promise.reject(response?.data); -} - -export async function uploadFile(workspaceId: string, viewId: string, file: File, onProgress?: (progress: number) => void) { - const url = `/api/file_storage/${workspaceId}/v1/blob/${viewId}`; - - // Check file size, if over 7MB, check subscription plan - if (file.size > 7 * 1024 * 1024) { - const plan = await getActiveSubscription(workspaceId); - - if (plan?.length === 0 || plan?.[0] === SubscriptionPlan.Free) { - notify.error('Your file is over 7 MB limit of the Free plan. Upgrade for unlimited uploads.'); - - return Promise.reject({ - code: 413, - message: 'File size is too large. Please upgrade your plan for unlimited uploads.', - }); - } - } - - try { - const response = await axiosInstance?.put<{ - code: number; - message: string; - data: { - file_id: string; - } - }>(url, file, { - onUploadProgress: (progressEvent) => { - const { progress = 0 } = progressEvent; - - onProgress?.(progress); - }, - headers: { - 'Content-Type': file.type || 'application/octet-stream', - }, - }); - - if (response?.data.code === 0) { - const baseURL = axiosInstance?.defaults.baseURL; - const url = `${baseURL}/api/file_storage/${workspaceId}/v1/blob/${viewId}/${response?.data.data.file_id}`; - - return url; - } - - return Promise.reject(response?.data); - // eslint-disable-next-line - } catch (e: any) { - - if (e.response.status === 413) { - return Promise.reject({ - code: 413, - message: 'File size is too large. Please upgrade your plan for unlimited uploads.', - }); - } - } - - return Promise.reject({ - code: -1, - message: 'Upload file failed.', - }); - -} - -export async function inviteMembers(workspaceId: string, emails: string[]) { - const url = `/api/workspace/${workspaceId}/invite`; - - const payload = emails.map(e => ({ - email: e, - role: Role.Member, - })); - - const res = await axiosInstance?.post<{ - code: number; - message: string; - }>(url, payload); - - if (res?.data.code === 0) { - return; - } - - return Promise.reject(res?.data); -} - -export async function getMembers(workspaceId: string) { - const url = `/api/workspace/${workspaceId}/member`; - const res = await axiosInstance?.get<{ - code: number; - data: WorkspaceMember[]; - message: string; - }>(url); - - if (res?.data.code === 0) { - return res?.data.data; - } - - return Promise.reject(res?.data); -} - -export async function leaveWorkspace(workspaceId: string) { - const url = `/api/workspace/${workspaceId}/leave`; - const res = await axiosInstance?.post<{ - code: number; - message: string; - }>(url); - - if (res?.data.code === 0) { - return; - } - - return Promise.reject(res?.data); -} - -export async function deleteWorkspace(workspaceId: string) { - const url = `/api/workspace/${workspaceId}`; - const res = await axiosInstance?.delete<{ - code: number; - message: string; - }>(url); - - if (res?.data.code === 0) { - return; - } - - return Promise.reject(res?.data); -} - -export async function getQuickNoteList(workspaceId: string, params: { - offset?: number; - limit?: number; - searchTerm?: string; -}) { - const url = `/api/workspace/${workspaceId}/quick-note`; - const res = await axiosInstance?.get<{ - code: number; - data: { - quick_notes: QuickNote[]; - has_more: boolean; - }; - message: string; - }>(url, { - params: { - offset: params.offset, - limit: params.limit, - search_term: params.searchTerm || undefined, - }, - }); - - if (res?.data.code === 0) { - return { - data: res?.data.data.quick_notes, - has_more: res?.data.data.has_more, - }; - } - - return Promise.reject(res?.data); -} - -export async function createQuickNote(workspaceId: string, payload: QuickNoteEditorData[]): Promise { - const url = `/api/workspace/${workspaceId}/quick-note`; - const res = await axiosInstance?.post<{ - code: number; - data: QuickNote; - message: string; - }>(url, { - data: payload, - }); - - if (res?.data.code === 0) { - return res?.data.data; - } - - return Promise.reject(res?.data); -} - -export async function updateQuickNote(workspaceId: string, noteId: string, payload: QuickNoteEditorData[]) { - const url = `/api/workspace/${workspaceId}/quick-note/${noteId}`; - const res = await axiosInstance?.put<{ - code: number; - message: string; - }>(url, { - data: payload, - }); - - if (res?.data.code === 0) { - return; - } - - return Promise.reject(res?.data); -} - -export async function deleteQuickNote(workspaceId: string, noteId: string) { - const url = `/api/workspace/${workspaceId}/quick-note/${noteId}`; - const res = await axiosInstance?.delete<{ - code: number; - message: string; - }>(url); - - if (res?.data.code === 0) { - return; - } - - return Promise.reject(res?.data); -} - -export async function cancelSubscription(workspaceId: string, plan: SubscriptionPlan, reason?: string) { - const url = `/billing/api/v1/cancel-subscription`; - const res = await axiosInstance?.post<{ - code: number; - message: string; - }>(url, { - workspace_id: workspaceId, - plan, - sync: true, - reason, - }); - - if (res?.data.code === 0) { - return; - } - - return Promise.reject(res?.data); -} \ No newline at end of file diff --git a/frontend/appflowy_web_app/src/application/services/js-services/http/index.ts b/frontend/appflowy_web_app/src/application/services/js-services/http/index.ts deleted file mode 100644 index e170c830a4..0000000000 --- a/frontend/appflowy_web_app/src/application/services/js-services/http/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * as APIService from './http_api'; diff --git a/frontend/appflowy_web_app/src/application/services/js-services/http/utils.ts b/frontend/appflowy_web_app/src/application/services/js-services/http/utils.ts deleted file mode 100644 index aa197a7516..0000000000 --- a/frontend/appflowy_web_app/src/application/services/js-services/http/utils.ts +++ /dev/null @@ -1,17 +0,0 @@ -export function blobToBytes (blob: Blob): Promise { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - - reader.onloadend = () => { - if (!(reader.result instanceof ArrayBuffer)) { - reject(new Error('Failed to convert blob to bytes')); - return; - } - - resolve(new Uint8Array(reader.result)); - }; - - reader.onerror = reject; - reader.readAsArrayBuffer(blob); - }); -} diff --git a/frontend/appflowy_web_app/src/application/services/js-services/index.ts b/frontend/appflowy_web_app/src/application/services/js-services/index.ts deleted file mode 100644 index 14f635ab80..0000000000 --- a/frontend/appflowy_web_app/src/application/services/js-services/index.ts +++ /dev/null @@ -1,563 +0,0 @@ -import { GlobalComment, Reaction } from '@/application/comment.type'; -import { openCollabDB } from '@/application/db'; -import { - createRowDoc, deleteRowDoc, - deleteView, - getPageDoc, - getPublishView, - getPublishViewMeta, - getUser, hasCollabCache, - hasViewMetaCache, -} from '@/application/services/js-services/cache'; -import { StrategyType } from '@/application/services/js-services/cache/types'; -import { - fetchPageCollab, - fetchPublishView, - fetchPublishViewMeta, - fetchViewInfo, -} from '@/application/services/js-services/fetch'; -import { APIService } from '@/application/services/js-services/http'; -import { SyncManager } from '@/application/services/js-services/sync'; - -import { AFService, AFServiceConfig } from '@/application/services/services.type'; -import { emit, EventType } from '@/application/session'; -import { afterAuth, AUTH_CALLBACK_URL, withSignIn } from '@/application/session/sign_in'; -import { getTokenParsed } from '@/application/session/token'; -import { - TemplateCategoryFormValues, - TemplateCreatorFormValues, - UploadTemplatePayload, -} from '@/application/template.type'; -import { - CreatePagePayload, CreateSpacePayload, - DatabaseRelations, - DuplicatePublishView, QuickNoteEditorData, - SubscriptionInterval, SubscriptionPlan, - Types, UpdatePagePayload, UpdateSpacePayload, WorkspaceMember, - YjsEditorKey, -} from '@/application/types'; -import { applyYDoc } from '@/application/ydoc/apply'; -import { nanoid } from 'nanoid'; -import * as Y from 'yjs'; - -export class AFClientService implements AFService { - private deviceId: string = nanoid(8); - - private clientId: string = 'web'; - - private viewLoaded: Set = new Set(); - - private publishViewLoaded: Set = new Set(); - - private publishViewInfo: Map< - string, - { - namespace: string; - publishName: string; - } - > = new Map(); - - constructor(config: AFServiceConfig) { - APIService.initAPIService(config.cloudConfig); - } - - getClientId() { - return this.clientId; - } - - async getPublishViewMeta(namespace: string, publishName: string) { - const name = `${namespace}_${publishName}`; - - const isLoaded = this.publishViewLoaded.has(name); - const viewMeta = await getPublishViewMeta( - () => { - return fetchPublishViewMeta(namespace, publishName); - }, - { - namespace, - publishName, - }, - isLoaded ? StrategyType.CACHE_FIRST : StrategyType.CACHE_AND_NETWORK, - ); - - if (!viewMeta) { - return Promise.reject(new Error('View has not been published yet')); - } - - return viewMeta; - } - - async getPublishView(namespace: string, publishName: string) { - const name = `${namespace}_${publishName}`; - - const isLoaded = this.publishViewLoaded.has(name); - - const { doc } = await getPublishView( - async () => { - try { - return await fetchPublishView(namespace, publishName); - } catch (e) { - console.error(e); - void (async () => { - if (await hasViewMetaCache(name)) { - this.publishViewLoaded.delete(name); - void deleteView(name); - } - })(); - - return Promise.reject(e); - } - }, - { - namespace, - publishName, - }, - isLoaded ? StrategyType.CACHE_FIRST : StrategyType.CACHE_AND_NETWORK, - ); - - if (!isLoaded) { - this.publishViewLoaded.add(name); - } - - return doc; - } - - async getPublishRowDocument(viewId: string) { - const doc = await openCollabDB(viewId); - - if (hasCollabCache(doc)) { - return doc; - } - - return Promise.reject(new Error('Document not found')); - - } - - async createRowDoc(rowKey: string) { - return createRowDoc(rowKey); - } - - deleteRowDoc(rowKey: string) { - return deleteRowDoc(rowKey); - } - - async getAppDatabaseViewRelations(workspaceId: string, databaseStorageId: string) { - - const res = await APIService.getCollab(workspaceId, databaseStorageId, Types.WorkspaceDatabase); - const doc = new Y.Doc(); - - applyYDoc(doc, res.data); - - const { databases } = doc.getMap(YjsEditorKey.data_section).toJSON(); - const result: DatabaseRelations = {}; - - databases.forEach((database: { - database_id: string; - views: string[] - }) => { - result[database.database_id] = database.views[0]; - }); - return result; - } - - async getPublishInfo(viewId: string) { - if (this.publishViewInfo.has(viewId)) { - return this.publishViewInfo.get(viewId) as { - namespace: string; - publishName: string; - }; - } - - const info = await fetchViewInfo(viewId); - - const namespace = info.namespace; - - if (!namespace) { - return Promise.reject(new Error('View not found')); - } - - const data = { - namespace, - publishName: info.publish_name, - }; - - this.publishViewInfo.set(viewId, data); - - return data; - } - - async getPublishOutline(namespace: string) { - return APIService.getPublishOutline(namespace); - } - - async getAppOutline(workspaceId: string) { - return APIService.getAppOutline(workspaceId); - } - - async getAppView(workspaceId: string, viewId: string) { - return APIService.getView(workspaceId, viewId); - } - - async getAppFavorites(workspaceId: string) { - return APIService.getAppFavorites(workspaceId); - } - - async getAppRecent(workspaceId: string) { - return APIService.getAppRecent(workspaceId); - } - - async getAppTrash(workspaceId: string) { - return APIService.getAppTrash(workspaceId); - } - - async loginAuth(url: string) { - try { - await APIService.signInWithUrl(url); - emit(EventType.SESSION_VALID); - afterAuth(); - return; - } catch (e) { - emit(EventType.SESSION_INVALID); - return Promise.reject(e); - } - } - - @withSignIn() - async signInMagicLink({ email }: { email: string; redirectTo: string }) { - return await APIService.signInWithMagicLink(email, AUTH_CALLBACK_URL); - } - - @withSignIn() - async signInGoogle(_: { redirectTo: string }) { - return APIService.signInGoogle(AUTH_CALLBACK_URL); - } - - @withSignIn() - async signInApple(_: { redirectTo: string }) { - return APIService.signInApple(AUTH_CALLBACK_URL); - } - - @withSignIn() - async signInGithub(_: { redirectTo: string }) { - return APIService.signInGithub(AUTH_CALLBACK_URL); - } - - @withSignIn() - async signInDiscord(_: { redirectTo: string }) { - return APIService.signInDiscord(AUTH_CALLBACK_URL); - } - - async getWorkspaces() { - const data = APIService.getWorkspaces(); - - return data; - } - - async getWorkspaceFolder(workspaceId: string) { - const data = await APIService.getWorkspaceFolder(workspaceId); - - return data; - } - - async getCurrentUser() { - const token = getTokenParsed(); - const userId = token?.user?.id; - - const user = await getUser( - () => APIService.getCurrentUser(), - userId, - StrategyType.CACHE_AND_NETWORK, - ); - - if (!user) { - return Promise.reject(new Error('User not found')); - } - - return user; - } - - async openWorkspace(workspaceId: string) { - return APIService.openWorkspace(workspaceId); - } - - async getUserWorkspaceInfo() { - const workspaceInfo = await APIService.getUserWorkspaceInfo(); - - if (!workspaceInfo) { - return Promise.reject(new Error('Workspace info not found')); - } - - return { - userId: workspaceInfo.user_id, - selectedWorkspace: workspaceInfo.selected_workspace, - workspaces: workspaceInfo.workspaces, - }; - } - - async duplicatePublishView(params: DuplicatePublishView) { - return APIService.duplicatePublishView(params.workspaceId, { - dest_view_id: params.spaceViewId, - published_view_id: params.viewId, - published_collab_type: params.collabType, - }); - } - - createCommentOnPublishView(viewId: string, content: string, replyCommentId: string | undefined): Promise { - return APIService.createGlobalCommentOnPublishView(viewId, content, replyCommentId); - } - - deleteCommentOnPublishView(viewId: string, commentId: string): Promise { - return APIService.deleteGlobalCommentOnPublishView(viewId, commentId); - } - - getPublishViewGlobalComments(viewId: string): Promise { - return APIService.getPublishViewComments(viewId); - } - - getPublishViewReactions(viewId: string, commentId?: string): Promise> { - return APIService.getReactions(viewId, commentId); - } - - addPublishViewReaction(viewId: string, commentId: string, reactionType: string): Promise { - return APIService.addReaction(viewId, commentId, reactionType); - } - - removePublishViewReaction(viewId: string, commentId: string, reactionType: string): Promise { - return APIService.removeReaction(viewId, commentId, reactionType); - } - - async getTemplateCategories() { - return APIService.getTemplateCategories(); - } - - async getTemplateCreators() { - return APIService.getTemplateCreators(); - } - - async createTemplate(template: UploadTemplatePayload) { - return APIService.createTemplate(template); - } - - async updateTemplate(id: string, template: UploadTemplatePayload) { - return APIService.updateTemplate(id, template); - } - - async getTemplateById(id: string) { - return APIService.getTemplateById(id); - } - - async getTemplates(params: { - categoryId?: string; - nameContains?: string; - }) { - return APIService.getTemplates(params); - } - - async deleteTemplate(id: string) { - return APIService.deleteTemplate(id); - } - - async addTemplateCategory(category: TemplateCategoryFormValues) { - return APIService.addTemplateCategory(category); - } - - async updateTemplateCategory(categoryId: string, category: TemplateCategoryFormValues) { - return APIService.updateTemplateCategory(categoryId, category); - } - - async deleteTemplateCategory(categoryId: string) { - return APIService.deleteTemplateCategory(categoryId); - } - - async updateTemplateCreator(creatorId: string, creator: TemplateCreatorFormValues) { - return APIService.updateTemplateCreator(creatorId, creator); - } - - async createTemplateCreator(creator: TemplateCreatorFormValues) { - return APIService.createTemplateCreator(creator); - } - - async deleteTemplateCreator(creatorId: string) { - return APIService.deleteTemplateCreator(creatorId); - } - - async uploadTemplateAvatar(file: File) { - return APIService.uploadTemplateAvatar(file); - } - - async getPageDoc(workspaceId: string, viewId: string, errorCallback?: (error: { - code: number; - }) => void) { - - const token = getTokenParsed(); - const userId = token?.user.id; - - if (!userId) { - throw new Error('User not found'); - } - - const name = `${userId}_${workspaceId}_${viewId}`; - - const isLoaded = this.viewLoaded.has(name); - - const { doc } = await getPageDoc( - async () => { - try { - return await fetchPageCollab(workspaceId, viewId); - // eslint-disable-next-line - } catch (e: any) { - console.error(e); - - errorCallback?.(e); - void (async () => { - this.viewLoaded.delete(name); - void deleteView(name); - })(); - - return Promise.reject(e); - } - }, - name, - isLoaded ? StrategyType.CACHE_FIRST : StrategyType.CACHE_AND_NETWORK, - ); - - if (!isLoaded) { - this.viewLoaded.add(name); - } - - return doc; - } - - async getInvitation(invitationId: string) { - return APIService.getInvitation(invitationId); - } - - async acceptInvitation(invitationId: string) { - return APIService.acceptInvitation(invitationId); - } - - approveRequestAccess(requestId: string): Promise { - return APIService.approveRequestAccess(requestId); - } - - getRequestAccessInfo(requestId: string) { - return APIService.getRequestAccessInfo(requestId); - } - - sendRequestAccess(workspaceId: string, viewId: string): Promise { - return APIService.sendRequestAccess(workspaceId, viewId); - } - - getSubscriptionLink(workspaceId: string, plan: SubscriptionPlan, interval: SubscriptionInterval) { - return APIService.getSubscriptionLink(workspaceId, plan, interval); - } - - cancelSubscription(workspaceId: string, plan: SubscriptionPlan, reason?: string) { - return APIService.cancelSubscription(workspaceId, plan, reason); - } - - getSubscriptions() { - return APIService.getSubscriptions(); - } - - getActiveSubscription(workspaceId: string) { - return APIService.getActiveSubscription(workspaceId); - } - - getWorkspaceSubscriptions(workspaceId: string) { - return APIService.getWorkspaceSubscriptions(workspaceId); - } - - registerDocUpdate(doc: Y.Doc, context: { - workspaceId: string, objectId: string, collabType: Types - }) { - const token = getTokenParsed(); - const userId = token?.user.id; - - if (!userId) { - throw new Error('User not found'); - } - - const sync = new SyncManager(doc, { userId, ...context }); - - sync.initialize(); - } - - async importFile(file: File, onProgress: (progress: number) => void) { - const task = await APIService.createImportTask(file); - - await APIService.uploadImportFile(task.presignedUrl, file, onProgress); - } - - async createSpace(workspaceId: string, payload: CreateSpacePayload) { - return APIService.createSpace(workspaceId, payload); - } - - async updateSpace(workspaceId: string, payload: UpdateSpacePayload) { - return APIService.updateSpace(workspaceId, payload); - } - - async addAppPage(workspaceId: string, parentViewId: string, payload: CreatePagePayload) { - return APIService.addAppPage(workspaceId, parentViewId, payload); - } - - async updateAppPage(workspaceId: string, viewId: string, data: UpdatePagePayload) { - return APIService.updatePage(workspaceId, viewId, data); - } - - async deleteTrash(workspaceId: string, viewId?: string) { - return APIService.deleteTrash(workspaceId, viewId); - } - - async moveToTrash(workspaceId: string, viewId: string) { - return APIService.moveToTrash(workspaceId, viewId); - } - - async restoreFromTrash(workspaceId: string, viewId?: string) { - return APIService.restorePage(workspaceId, viewId); - } - - async movePage(workspaceId: string, viewId: string, parentId: string, prevViewId?: string) { - return APIService.movePageTo(workspaceId, viewId, parentId, prevViewId); - } - - async uploadFile(workspaceId: string, viewId: string, file: File, onProgress?: (progress: number) => void) { - return APIService.uploadFile(workspaceId, viewId, file, onProgress); - } - - deleteWorkspace(workspaceId: string): Promise { - return APIService.deleteWorkspace(workspaceId); - } - - leaveWorkspace(workspaceId: string): Promise { - return APIService.leaveWorkspace(workspaceId); - } - - inviteMembers(workspaceId: string, emails: string[]): Promise { - return APIService.inviteMembers(workspaceId, emails); - } - - getWorkspaceMembers(workspaceId: string): Promise { - return APIService.getMembers(workspaceId); - } - - getQuickNoteList(workspaceId: string, params: { - offset?: number; - limit?: number; - searchTerm?: string; - }) { - return APIService.getQuickNoteList(workspaceId, params); - } - - createQuickNote(workspaceId: string, data: QuickNoteEditorData[]) { - return APIService.createQuickNote(workspaceId, data); - } - - updateQuickNote(workspaceId: string, id: string, data: QuickNoteEditorData[]) { - return APIService.updateQuickNote(workspaceId, id, data); - } - - deleteQuickNote(workspaceId: string, id: string) { - return APIService.deleteQuickNote(workspaceId, id); - } -} diff --git a/frontend/appflowy_web_app/src/application/services/js-services/sync.ts b/frontend/appflowy_web_app/src/application/services/js-services/sync.ts deleted file mode 100644 index 231d560581..0000000000 --- a/frontend/appflowy_web_app/src/application/services/js-services/sync.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { updateCollab } from '@/application/services/js-services/http/http_api'; -import { CollabOrigin, Types } from '@/application/types'; -import { debounce } from 'lodash-es'; -import * as Y from 'yjs'; - -const VERSION_VECTOR_KEY = 'ydoc_version_vector'; -const UNSYNCED_FLAG_KEY = 'ydoc_unsynced_changes'; -const LAST_SYNCED_AT_KEY = 'ydoc_last_synced_at'; - -export class SyncManager { - - private versionVector: number; - - private lastSyncedAt: string; - - private hasUnsyncedChanges: boolean = false; - - private isSending = false; - - constructor (private doc: Y.Doc, private context: { - userId: string, workspaceId: string, objectId: string, collabType: Types - }) { - this.versionVector = this.loadVersionVector(); - this.hasUnsyncedChanges = this.loadUnsyncedFlag(); - this.lastSyncedAt = this.loadLastSyncedAt(); - this.setupListener(); - } - - private setupListener () { - this.doc.on('update', (_update: Uint8Array, origin: CollabOrigin) => { - if (origin === CollabOrigin.Remote) return; - - this.debouncedSendUpdate(); - }); - } - - private getStorageKey (baseKey: string): string { - return `${this.context.userId}_${baseKey}_${this.context.workspaceId}_${this.context.objectId}`; - } - - private loadVersionVector (): number { - const storedVector = localStorage.getItem(this.getStorageKey(VERSION_VECTOR_KEY)); - - return storedVector ? parseInt(storedVector, 10) : 0; - } - - private saveVersionVector () { - localStorage.setItem(this.getStorageKey(VERSION_VECTOR_KEY), this.versionVector.toString()); - } - - private loadUnsyncedFlag (): boolean { - return localStorage.getItem(this.getStorageKey(UNSYNCED_FLAG_KEY)) === 'true'; - } - - private saveUnsyncedFlag () { - localStorage.setItem(this.getStorageKey(UNSYNCED_FLAG_KEY), this.hasUnsyncedChanges.toString()); - } - - private loadLastSyncedAt (): string { - return localStorage.getItem(this.getStorageKey(LAST_SYNCED_AT_KEY)) || ''; - } - - private saveLastSyncedAt () { - localStorage.setItem(this.getStorageKey(LAST_SYNCED_AT_KEY), this.lastSyncedAt); - } - - private debouncedSendUpdate = debounce(() => { - this.hasUnsyncedChanges = true; - this.saveUnsyncedFlag(); - - void this.sendUpdate(); - }, 1000); // 1 second debounce - - private async sendUpdate () { - if (this.isSending) return; - this.isSending = true; - - try { - // Increment version vector before sending - this.versionVector++; - this.saveVersionVector(); - - const update = Y.encodeStateAsUpdate(this.doc); - const context = { version_vector: this.versionVector }; - - const response = await updateCollab(this.context.workspaceId, this.context.objectId, this.context.collabType, update, context); - - if (response) { - console.log(`Update sent successfully. Server version: ${response.version_vector}`); - - // Update last synced time - this.lastSyncedAt = String(Date.now()); - this.saveLastSyncedAt(); - - if (response.version_vector === this.versionVector) { - // Our update was the latest - this.hasUnsyncedChanges = false; - this.saveUnsyncedFlag(); - console.log('Local changes fully synced'); - } else { - // There are still unsynced changes (possibly from other clients) - this.hasUnsyncedChanges = true; - this.saveUnsyncedFlag(); - console.log('There are still unsynced changes'); - } - } else { - return Promise.reject(response); - } - } catch (error) { - console.error('Failed to send update:', error); - // Keep the unsynced flag as true - this.hasUnsyncedChanges = true; - this.saveUnsyncedFlag(); - } finally { - this.isSending = false; - } - } - - public initialize () { - if (this.hasUnsyncedChanges) { - // Send an update if there are unsynced changes - this.debouncedSendUpdate(); - } - } - - public getUnsyncedStatus (): boolean { - return this.hasUnsyncedChanges; - } - - public getLastSyncedAt (): string { - return this.lastSyncedAt; - } - - public getCurrentVersionVector (): number { - return this.versionVector; - } -} \ No newline at end of file diff --git a/frontend/appflowy_web_app/src/application/services/services.type.ts b/frontend/appflowy_web_app/src/application/services/services.type.ts deleted file mode 100644 index 8b329be754..0000000000 --- a/frontend/appflowy_web_app/src/application/services/services.type.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { - Invitation, - DuplicatePublishView, - FolderView, - User, - UserWorkspaceInfo, - View, - Workspace, - YDoc, - DatabaseRelations, - GetRequestAccessInfoResponse, - Subscriptions, - SubscriptionPlan, - SubscriptionInterval, - Types, - UpdatePagePayload, - CreatePagePayload, - CreateSpacePayload, - UpdateSpacePayload, - WorkspaceMember, - QuickNoteEditorData, - QuickNote, Subscription, -} from '@/application/types'; -import { GlobalComment, Reaction } from '@/application/comment.type'; -import { ViewMeta } from '@/application/db/tables/view_metas'; -import { - Template, - TemplateCategory, - TemplateCategoryFormValues, - TemplateCreator, TemplateCreatorFormValues, TemplateSummary, - UploadTemplatePayload, -} from '@/application/template.type'; - -export type AFService = PublishService & AppService & WorkspaceService & TemplateService & QuickNoteService & { - getClientId: () => string; -}; - -export interface AFServiceConfig { - cloudConfig: AFCloudConfig; -} - -export interface AFCloudConfig { - baseURL: string; - gotrueURL: string; - wsURL: string; -} - -export interface WorkspaceService { - openWorkspace: (workspaceId: string) => Promise; - leaveWorkspace: (workspaceId: string) => Promise; - deleteWorkspace: (workspaceId: string) => Promise; - getWorkspaceMembers: (workspaceId: string) => Promise; - inviteMembers: (workspaceId: string, emails: string[]) => Promise; -} - -export interface AppService { - getPageDoc: (workspaceId: string, viewId: string, errorCallback?: (error: { - code: number; - }) => void) => Promise; - createRowDoc: (rowKey: string) => Promise; - deleteRowDoc: (rowKey: string) => void; - getAppDatabaseViewRelations: (workspaceId: string, databaseStorageId: string) => Promise; - getAppOutline: (workspaceId: string) => Promise; - getAppView: (workspaceId: string, viewId: string) => Promise; - getAppFavorites: (workspaceId: string) => Promise; - getAppRecent: (workspaceId: string) => Promise; - getAppTrash: (workspaceId: string) => Promise; - loginAuth: (url: string) => Promise; - signInMagicLink: (params: { email: string; redirectTo: string }) => Promise; - signInGoogle: (params: { redirectTo: string }) => Promise; - signInGithub: (params: { redirectTo: string }) => Promise; - signInDiscord: (params: { redirectTo: string }) => Promise; - signInApple: (params: { redirectTo: string }) => Promise; - getWorkspaces: () => Promise; - getWorkspaceFolder: (workspaceId: string) => Promise; - getCurrentUser: () => Promise; - getUserWorkspaceInfo: () => Promise; - uploadTemplateAvatar: (file: File) => Promise; - getInvitation: (invitationId: string) => Promise; - acceptInvitation: (invitationId: string) => Promise; - getRequestAccessInfo: (requestId: string) => Promise; - approveRequestAccess: (requestId: string) => Promise; - sendRequestAccess: (workspaceId: string, viewId: string) => Promise; - getSubscriptionLink: (workspaceId: string, plan: SubscriptionPlan, interval: SubscriptionInterval) => Promise; - getSubscriptions: () => Promise; - cancelSubscription: (workspaceId: string, plan: SubscriptionPlan, reason?: string) => Promise; - getActiveSubscription: (workspaceId: string) => Promise; - getWorkspaceSubscriptions: (workspaceId: string) => Promise; - registerDocUpdate: (doc: YDoc, context: { - workspaceId: string, objectId: string, collabType: Types - }) => void; - importFile: (file: File, onProgress: (progress: number) => void) => Promise; - createSpace: (workspaceId: string, payload: CreateSpacePayload) => Promise; - updateSpace: (workspaceId: string, payload: UpdateSpacePayload) => Promise; - addAppPage: (workspaceId: string, parentViewId: string, payload: CreatePagePayload) => Promise; - updateAppPage: (workspaceId: string, viewId: string, data: UpdatePagePayload) => Promise; - deleteTrash: (workspaceId: string, viewId?: string) => Promise; - moveToTrash: (workspaceId: string, viewId: string) => Promise; - restoreFromTrash: (workspaceId: string, viewId?: string) => Promise; - movePage: (workspaceId: string, viewId: string, parentId: string, prevViewId?: string) => Promise; - uploadFile: (workspaceId: string, viewId: string, file: File, onProgress?: (progress: number) => void) => Promise; -} - -export interface QuickNoteService { - getQuickNoteList: (workspaceId: string, params: { - offset?: number; - limit?: number; - searchTerm?: string; - }) => Promise<{ - data: QuickNote[]; - has_more: boolean; - }>; - createQuickNote: (workspaceId: string, data: QuickNoteEditorData[]) => Promise; - updateQuickNote: (workspaceId: string, id: string, data: QuickNoteEditorData[]) => Promise; - deleteQuickNote: (workspaceId: string, id: string) => Promise; -} - -export interface TemplateService { - getTemplateCategories: () => Promise; - addTemplateCategory: (category: TemplateCategoryFormValues) => Promise; - deleteTemplateCategory: (categoryId: string) => Promise; - getTemplateCreators: () => Promise; - createTemplateCreator: (creator: TemplateCreatorFormValues) => Promise; - deleteTemplateCreator: (creatorId: string) => Promise; - getTemplateById: (id: string) => Promise